Hibernate Intermediate
Agenda
- Collections in detail
- Immutability Identifiers:
- Simple and Composite key
- @EmbeddedId and @IdClass
- @MapsId
- Inheritance:
- Joined subclass
- Super class
- Table per class
- Table per class hierarchy
- Session scope and Transaction
- Flushing commit and roll back
Collections in Hibernate
- Hibernate also allows to persist collections. These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, components and references to other entities.
- The owner of the collection is always an entity.
- There can be:
(1) value type collections
(2) embeddable type collections
(3) entity collections
- Hibernate uses its own collection implementations which are enriched with lazy-loading, caching or state change detection semantics.
- For this reason, persistent collections must be declared as an interface type.
Collection as value type
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Person {
@Id
private Integer id;
@ElementCollection
List<String> phoneList = new ArrayList<>();
// getters and setters
}
Person person=new Person();
person.setId(1);
person.setPhoneList(Arrays.asList("1234","5678"));
session.save(person);
Delete Associated collection
Person person=session.get(Person.class,1);
person.getPhoneList().clear();
Person person=session.get(Person.class,1);
person.getPhoneList().remove(0);
Deleting entire collections
Deleting an element from collection
Removing elements more efficiently using order column annotation
@Entity
public static class Person {
@Id
private Long id;
@ElementCollection
@OrderColumn(name = "order_id")
private List<String> phones = new ArrayList<>();
public List<String> getPhones() {
return phones;
}
}
Embeddable type collection
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Person {
@Id
private Integer id;
@ElementCollection
@OrderColumn(name = "order_id")
List<Phone> phoneList =
new ArrayList<>();
// getters and setters
}
import javax.persistence.*;
@Embeddable
public class Phone {
private String type;
private String number;
public Phone() {
}
public Phone(String type, String number) {
this.type = type;
this.number = number;
}
// getters an setters
}
Person person=new Person();
person.setId(1);
person.setPhoneList(Arrays.asList(new Phone("landline","1234"),new Phone("office","56789")));
session.save(person);
Exercise 1
- Create a class Employee with instance variables name(String) , age(Integer) and hobbies(List).
- Persist Employee entity
- Delete remove first hobby from employee.
- Introduce instance variable List of Address in Employee which should be of embeddable type. Address class should contain type, country, city and pincode.
- Save current and temporary address for Employee.
Collections of entities
- bags : Bags are unordered lists and we can have unidirectional bags or bidirectional ones.
- Unidirectional bags : The unidirectional bag is mapped using a single @OneToMany annotation on the parent side of the association.
- Bidirectional bags : The bidirectional bag is the most common type of entity collection. The @ManyToOne side is the owning side of the bidirectional bag association, while the @OneToMany is the inverse side, being marked with the mappedBy attribute.
Unidirectional bag
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@OneToMany(cascade = CascadeType.ALL)
List<Phone> phoneList = new ArrayList<>();
//getters and setters
}
import javax.persistence.*;
@Entity
public class Phone {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
private String number;
public Phone(String type, String number) {
this.type = type;
this.number = number;
}
//getters and setters
}
Unidirectional bag (Cont.)
Person person=new Person();
person.setPhoneList(Arrays.asList(new Phone("landline","1234"),new Phone("office","567")));
session.persist(person);
Bidirectional bags
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@OneToMany(mappedBy = "person",cascade = CascadeType.ALL)
@OrderBy("id")
List<Phone> phoneList = new ArrayList<>();
// getters and setters
}
import javax.persistence.*;
@Entity
public class Phone {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
private String number;
@ManyToOne
private Person person;
public Phone(String type, String number, Person person) {
this.type = type;
this.number = number;
this.person = person;
} // getters and setters
}
Person person=new Person();
person.setPhoneList(Arrays.asList(
new Phone("landline","123",person),
new Phone("office","567",person)));
session.persist(person);
Bidirectional bags (Cont.)
Exercise 2
- From the previous exercise make Address class hibernate entity.
- Perform Unidirectional and bidirectional bag concept for Employee and Address one to many relationship.
Ordered List
To preserve the collection element order, there are two possibilities:
-
@OrderBy: the collection is ordered upon retrieval using a child entity property
-
@OrderColumn: the collection uses a dedicated order column in the collection link table
Set
- Sets are collections that don’t allow duplicate entries and Hibernate supports both the unordered Set and the natural-ordering SortedSet.
- Unidirectional sets : The unidirectional set uses a link table to hold the parent-child associations.
- Bidirectional sets : the bidirectional set doesn’t use a link table, and the child table has a foreign key referencing the parent table primary key.
Unidirectional Set
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@OneToMany(cascade = CascadeType.ALL)
Set<Phone> phoneList = new HashSet<>();
//getters and setters
}
import javax.persistence.*;
@Entity
public class Phone {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
private String number;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Phone)) return false;
Phone phone = (Phone) o;
return getNumber().equals(phone.getNumber());
}
@Override
public int hashCode() {
return getNumber().hashCode();
}
}
Unidirectional Set (cont.)
Person person=new Person();
Phone phone1=new Phone();
phone1.setNumber("1234");
phone1.setType("landline");
Phone phone2=new Phone();
phone2.setNumber("12345");
phone2.setType("office");
Set<Phone> setOfPhone=person.getPhoneList();
setOfPhone.add(phone1);
setOfPhone.add(phone2);
session.persist(person);
Unidirectional Set (cont.)
Bidirectional Set
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@OneToMany(mappedBy = "person",cascade = CascadeType.ALL)
Set<Phone> phoneList = new HashSet<>();
// getters and setters
}
import javax.persistence.*;
@Entity
public class Phone {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
private String number;
@ManyToOne
private Person person;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Phone)) return false;
Phone phone = (Phone) o;
return getNumber().equals(phone.getNumber());
}
@Override
public int hashCode() {
return getNumber().hashCode();
}
}
Bidirectional Set (cont.)
Person person=new Person();
Phone phone1=new Phone();
phone1.setNumber("1234");
phone1.setType("landline");
phone1.setPerson(person);
Phone phone2=new Phone();
phone2.setNumber("1234");
phone2.setType("office");
phone2.setPerson(person);
Set<Phone> setOfPhone=person.getPhoneList();
setOfPhone.add(phone1);
setOfPhone.add(phone2);
session.persist(person);
Bidirectional Set (cont.)
Exercise 3
- From the previous exercise convert the Address entity into set and perform unidirectional and bidirectional set concept on one to many relationship between Employee and Address
Sorted Set
- For sorted sets, the entity mapping must use the SortedSet interface
- According to the SortedSet contract, all elements must implement the Comparable interface and therefore provide the sorting logic.
import org.hibernate.annotations.SortComparator;
import javax.persistence.*;
import java.util.Set;
import java.util.TreeSet;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@OneToMany(cascade = CascadeType.ALL)
@SortComparator(PhoneComparator.class)
Set<Phone> phoneList = new TreeSet<>();
// getters and setters
}
Sorted Set (cont.)
import javax.persistence.*;
@Entity
public class Phone implements Comparable<Phone>{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
private String number;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Phone)) return false;
Phone phone = (Phone) o;
return getNumber().equals(phone.getNumber());
}
@Override
public int hashCode() {
return getNumber().hashCode();
}
@Override
public int compareTo(Phone o) {
return number.compareTo(o.getNumber());
}
}
Sorted Set (cont.)
import java.util.Comparator;
public class PhoneComparator implements Comparator<Phone> {
@Override
public int compare(Phone o1, Phone o2) {
return o2.compareTo(o1);
}
}
Person person=new Person();
Phone phone1=new Phone();
phone1.setNumber("11");
phone1.setType("landline");
Phone phone2=new Phone();
phone2.setNumber("22");
phone2.setType("office");
Set<Phone> setOfPhone=person.getPhoneList();
setOfPhone.add(phone1);
setOfPhone.add(phone2);
session.persist(person);
Sorted Set (cont.)
Exercise 4
- Make sure that Address Entity is always sorted form employee on the basic of pincode of Address.
Map
A java.util.Map is ternary association because it required a parent entity a map key and a value. An entity can either be a map key or a map value, depending on the mapping.
MapKeyColumn
For value type maps, the map key is a column in the link table that defines the grouping logic
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
@Entity
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ElementCollection
@MapKeyColumn
Map<Integer,String> map= new HashMap<>();
//getters and setters
}
SessionFactory sessionFactory =new Configuration()
.configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Employee employee=new Employee();
employee.setName("John");
Map map=employee.getMap();
map.put(1,"Delhi");
map.put(2,"Mumbai");
session.save(employee);
session.getTransaction().commit();
session.close();
MapKeyColumn (Cont.)
MapKey
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
@Entity
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(cascade = CascadeType.ALL)
@MapKeyColumn(name = "myColumn")
Map<Integer,Phone> map= new HashMap<>();
// getters and setters
}
the map key is either the primary key or another property of the entity stored as a map entry value
import javax.persistence.*;
@Entity
public class Phone implements Comparable<Phone>{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
private String number;
private Integer myColumn;
//getters and setters
}
MapKey (cont.)
SessionFactory sessionFactory =new Configuration()
.configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Employee employee=new Employee();
employee.setName("John");
Phone phone1 = new Phone();
phone1.setType("office");
phone1.setNumber("12345");
Phone phone2 = new Phone();
phone2.setType("landline");
phone2.setNumber("56789");
Map<Integer, Phone> map=employee.getMap();
map.put(11,phone1);
map.put(22,phone2);
session.save(employee);
session.getTransaction().commit();
session.close();
MapKey (cont.)
Exercise 5
- Introduce a Map in Employee class which stores hobbies of the employee with its priority key should be Integer and value should be String use MapKeyColumn for the persistence of map with Employee.
- For Address List entity use Map instead for One to many relationship. User Interger for key which is priority and Address entity as value using KeyMap
Immutability
- Immutability can be specified for both entities and collections.
- Entity immutability : If a specific entity is immutable, it is good practice to mark it with the @Immutable annotation.
import org.hibernate.annotations.Immutable;
import javax.persistence.*;
@Entity
@Immutable
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
// getters and setters
}
Internally, Hibernate is going to perform several optimizations, such as:
- reducing memory footprint since there is no need to retain the dehydrated state for the dirty checking mechanism
- speeding-up the Persistence Context flushing phase since immutable entities can skip the dirty checking process
Immutability (cont.)
Immutability (Collections)
Just like entities, collections can also be marked with the @Immutable annotation.
import org.hibernate.annotations.Immutable;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ElementCollection
@Immutable
private List<String> phoneList=new ArrayList<>();
}
Composite identifiers with @EmbeddedId
Modeling a composite identifier using an EmbeddedId simply means defining an embeddable to be a composition for the one or more attributes making up the identifier, and then exposing an attribute of that embeddable type on the entity.
import javax.persistence.*;
@Entity
public class Person {
@EmbeddedId
PersonPK personPK;
Integer age;
//getters and setters
}
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class PersonPK
implements Serializable{
Integer id;
String name;
//getters and setters
}
Composite identifiers with @IdClass
import javax.persistence.*;
@Entity
@IdClass(PersonPK.class)
public class Person {
@Id
Integer id;
@Id
String name;
Integer age;
// getters and setters
}
import java.io.Serializable;
public class PersonPK
implements Serializable{
Integer id;
String name;
//getters and setters
}
Modeling a composite identifier using an IdClass differs from using an EmbeddedId in that the entity defines each individual attribute making up the composition. The IdClass simply acts as a "shadow".
Exercise 6
- Introduce a phone list for Employee entity and make it immutable.
- Create composite key for Employee by combining its name and id. First do it by EmbeddedId and then by IdClass
@MapId
MapId annotation in JPA is used in ManyToOne and OneToOne relationships when mapping using EmbeddedId is involved.
import javax.persistence.*;
@Entity
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
//getters and setters
}
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class DependentId implements Serializable{
private String name;
private Integer empId;
//getters and setters
}
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
@Entity
public class Dependent {
@EmbeddedId
private DependentId id;
@ManyToOne
@MapsId("empId")
private Employee employee;
//getters and setters
}
@MapId (cont.)
Employee employee=new Employee();
employee.setName("Peter");
DependentId dependentId=new DependentId();
dependentId.setName("john");
Dependent dependent=new Dependent();
dependent.setId(dependentId);
dependent.setEmployee(employee);
session.save(dependent);
session.save(employee);
@MapId (cont.)
Inheritance
Hibernate provides several strategies to leverage this object-oriented trait onto domain model entities.
MappedSuperclass
When using MappedSuperclass, the inheritance is visible in the domain model only and each database table contains both the base class and the subclass properties.
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public class Account {
@Id
private Integer id;
private String owner;
private Integer balance;
private Integer interestRate;
//getters and setters
}
import javax.persistence.Entity;
@Entity
public class DebitAccount extends Account{
private Integer overdraftFee;
//getter and setter
}
DebitAccount debitAccount=new DebitAccount();
debitAccount.setOwner("Peter");
debitAccount.setBalance(23000);
debitAccount.setInterestRate(23);
debitAccount.setId(2);
debitAccount.setOverdraftFee(2300);
session.save(debitAccount);
Single Table
The single table inheritance strategy maps all subclasses to only one database table. Each subclass declares its own persistent properties.
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Account {
@Id
private Integer id;
private String owner;
private Integer balance;
private Integer interestRate;
}
import javax.persistence.Entity;
@Entity
public class DebitAccount extends Account{
private Integer overdraftFee;
// getter and setter
}
Joined Table
Each subclass can also be mapped to its own table. This is also called table-per-subclass mapping strategy. An inherited state is retrieved by joining with the table of the superclass.
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Account {
@Id
private Integer id;
private String owner;
private Integer balance;
private Integer interestRate;
}
import javax.persistence.Entity;
@Entity
public class DebitAccount extends Account{
private Integer overdraftFee;
// getter and setter
}
Table per class
This is called the table-per-concrete-class strategy. Each table defines all persistent states of the class, including the inherited state.
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Account {
@Id
private Integer id;
private String owner;
private Integer balance;
private Integer interestRate;
}
import javax.persistence.Entity;
@Entity
public class DebitAccount extends Account{
private Integer overdraftFee;
// getter and setter
}
Exercise 7
- Use @MapId for Employee and Dependent relationship which we did earlier.
- Create an Class Vehicle with instance variables name and colour.
- Create to Two classes TwoWheeler (isMechanical) and FourWheeler(hasSafetyBag) with instance variable doors which inherits Vehicle class
Flush and Commit
flush() will synchronize your database with the current state of object/objects held in the memory but it does not commit the transaction. So, if you get any exception after flush() is called, then the transaction will be rolled back. You can synchronize your database with small chunks of data using flush() instead of committing a large data at once using commit() and face the risk of getting an Out Of Memory Exception.
commit() will make data stored in the database permanent. There is no way you can rollback your transaction once the commit() succeeds.
SessionFactory sessionFactory =new Configuration()
.configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Employee employee = session.get(Employee.class,1);
employee.setName("John123");
session.flush();
// If exception is thrown here then changes will not persist
session.getTransaction().commit();
session.close();
// Changes will persisit even if the exception is thrown here
Rollback a transaction
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class Test {
public static void main(String[] args) {
SessionFactory sessionFactory =new Configuration()
.configure().buildSessionFactory();
Session session = sessionFactory.openSession();
try {
session.beginTransaction();
Employee employee = session.get(Employee.class, 1);
employee.setName("John");
System.out.println(1/0);
session.getTransaction().commit();
}catch (Exception ex){
try {
session.getTransaction().rollback();
} catch(Exception re) {
System.err.println("Error when trying to rollback transaction:");
re.printStackTrace();
}
}finally {
session.close();
}
}
}
Hibernate Intermediate
By Pulkit Pushkarna
Hibernate Intermediate
- 1,250