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,265