Agenda
Collections in Hibernate
(1) value type collections
(2) embeddable type collections
(3) entity collections
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
Collections of entities
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
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
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
Sorted Set
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
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
Immutability
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:
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
@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
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();
}
}
}