Advance Hibernate Part-2

Criteria Builder

  • Hibernate offers an older, legacy org.hibernate.Criteria API which should be considered deprecated. No feature development will target those APIs.
  • The javax.persistence.criteria.CriteriaBuilder interface is the first thing with which you need to become acquainted to begin using criteria queries. Its role is that of a factory for all the individual pieces of the criteria.
  • You obtain a javax.persistence.criteria.CriteriaBuilder instance by calling the getCriteriaBuilder() method of either javax.persistence.EntityManagerFactory or javax.persistence.EntityManager.
  SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory();
  Session session = sessionFactory.openSession();
  session.beginTransaction();

  CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
  CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(Employee.class);
  Root<Employee> root = criteriaQuery.from(Employee.class);
  criteriaQuery.select(root);

  System.out.println(
          session.createQuery(criteriaQuery).getResultList()
      );

This is probably the most common form of query. The application wants to select entity instances.

The example uses createQuery() passing in the Employee class reference as the results of the query will be Employee objects.

SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(Employee.class);
Root<Employee> root = criteriaQuery.from(Employee.class);
criteriaQuery.select(root.get("name"));

System.out.println(
     session.createQuery(criteriaQuery).getResultList()
);

Selecting an expression

The simplest form of selecting an expression is selecting a particular attribute from an entity

Selecting multiple values

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(Employee.class);
Root<Employee> root = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.array(root.get("name"),root.get("id")));

List<Object []> list =  session.createQuery(criteriaQuery).getResultList();

list.forEach((Object [] element)->{
     System.out.println(element[0]);
     System.out.println(element[1]);
});

Using wrapper

 CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();

 CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(EmployeeWrapper.class);

 Root root = criteriaQuery.from(Employee.class);

 criteriaQuery.select(criteriaBuilder.construct(EmployeeWrapper.class,root.get("name")));

 List<EmployeeWrapper> list =  session.createQuery(criteriaQuery).getResultList();

 list.forEach((EmployeeWrapper element)->{
      System.out.println(element.getName());
  });
public class EmployeeWrapper {

    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public EmployeeWrapper(String name) {
        this.name = name;
    }
}

Using where clause

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(EmployeeWrapper.class);
Root root = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.construct(EmployeeWrapper.class,root.get("name")));
criteriaQuery.where(criteriaBuilder.equal(root.get("name"),"Emp 1"));
EmployeeWrapper employeeWrapper = 
           (EmployeeWrapper) session.createQuery(criteriaQuery).getSingleResult();
System.out.println(employeeWrapper.getName());
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(EmployeeWrapper.class);
Root root = criteriaQuery.from(Employee.class);
Predicate restriction1 = criteriaBuilder.greaterThanOrEqualTo(root.get("id"),2);
Predicate restriction2 = criteriaBuilder.lessThanOrEqualTo(root.get("id"),5);
criteriaQuery.select(criteriaBuilder.construct(EmployeeWrapper.class,root.get("name")));
criteriaQuery.where(criteriaBuilder.and(restriction1, restriction2));
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(EmployeeWrapper.class);
Root root = criteriaQuery.from(Employee.class);
ParameterExpression<String> name = criteriaBuilder.parameter( String.class );
Predicate restriction1 = criteriaBuilder.equal(root.get("name"),name);
criteriaQuery.select(criteriaBuilder.construct(EmployeeWrapper.class,root.get("name")));
criteriaQuery.where(restriction1);
TypedQuery<EmployeeWrapper> query = session.createQuery( criteriaQuery );
query.setParameter(name,"Emp 1");
List<EmployeeWrapper> employeeWrapper = query.getResultList();
employeeWrapper.forEach((EmployeeWrapper element)->{
       System.out.println(element.getName());
      });

Using Parameters

Group By

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(Employee.class);
Root root = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.sum(root.get("age")));
System.out.println(
        session.createQuery(criteriaQuery).getSingleResult()
      );

Sum of age 

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(Employee.class);
Root root = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.max(root.get("age")));
System.out.println(
       session.createQuery(criteriaQuery).getSingleResult()
     );

Max Age

Exercise 1

  • Create an Hibernate entity Person with instance variables id, name, age.
  • Do few entries for Person Entity.
  • Use criteria builder to perform following operations:
  • Select all the records from person table.
  • Select name from all the records from person table.
  • Select age and name from all the records of person table.
  • Use PersonWrapper to fetch the records of a person from criteria builder.
  • Select all the Person whose age is between 25 to 34.
  • Use parameter substitution to get the Person object with the specific name.
  • Find the person with Max age.
  • Group the person by age.

Group by age

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery criteriaQuery=criteriaBuilder.createQuery(Employee.class);

Root root = criteriaQuery.from(Employee.class);
criteriaQuery.groupBy(root.get("age"));
criteriaQuery.select(criteriaBuilder.array(root.get("age") ,criteriaBuilder.count(root)));

List<Object []> list = session.createQuery(criteriaQuery).getResultList();
list.forEach((Object [] element)->{
      System.out.println(element[0]);
      System.out.println(element[1]);
  });

Caching

Hibernate can integrate with various caching providers for the purpose of caching data outside the context of a particular Session.

<property name="hibernate.cache.region.factory_class">
    org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
<property name="hibernate.cache.use_second_level_cache">true</property>

build.gradle

apply plugin : 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile('org.hibernate:hibernate-core:5.2.6.Final')
    compile ('mysql:mysql-connector-java:5.1.6')
    compile group: 'org.hibernate', name: 'hibernate-ehcache', version: '5.2.6.Final'
    compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
}

In order to make Hibernate entity cacheable. Two annotations are needed to be placed at the top of them.

@Cacheable

@Cache(usage =  CacheConcurrencyStrategy.READ_ONLY)

Query Caching

<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
Query query = session.createQuery("from Employee where id = 1");
query.setCacheable(true);
query.getResultList();

Evicting Caching

SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory();
        
Session session = sessionFactory.openSession();
session.beginTransaction();

Employee employee = session.get(Employee.class,1);
System.out.println(employee);

session.getTransaction().commit();
session.close();

Session session2 = sessionFactory.openSession();
session2.beginTransaction();
sessionFactory.getCache().evict(Employee.class);

Employee employee2 = session2.get(Employee.class,1);
System.out.println(employee2);

session2.getTransaction().commit();
session2.close();

sessionFactory.close();

We can evict cache with the help of evict method which we get on getCache()

Exercise 2

  • Apply second level caching for Person entity.
  • Apply Query Caching for Person entity.
  • Try cache evict.

Transaction and locking

Locking: In a relational database, locking refers to actions taken to prevent data from changing between the time it is read and the time is used.

Locking strategies: Pessimistic and Optimistic

Pessimistic locking assumes that concurrent transactions will conflict with each other, and requires resources to be locked after they are read and only unlocked after the application has finished using the data.

 

Pessimistic lock can be of two types, shared and exclusive

Shared Lock

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;

import javax.persistence.LockModeType;

public class PessimisticLocking {

    public static void thread1() throws InterruptedException {
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();

        Query<Employee> query = session
                .createQuery("FROM Employee WHERE id = 1")
                .setLockMode(LockModeType.PESSIMISTIC_READ);
        Employee employee = query.getSingleResult();
        System.out.println("thread1------"+employee);
        Thread.sleep(10000);
        session.getTransaction().commit();
        session.close();
        sessionFactory.close();
    }

    public static void thread2(){
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();

        Query<Employee> query = session
                .createQuery("FROM Employee WHERE id = 1")
                .setLockMode(LockModeType.PESSIMISTIC_READ);
        Employee employee = query.getSingleResult();
        System.out.println("thread2-------"+employee);

        session.getTransaction().commit();
        session.close();
        sessionFactory.close();
    }

    public static void main(String[] args) {
        new Thread(()->{
            try {
                thread1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            thread2();
        }).start();
    }
}

Exclusive Lock

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;

import javax.persistence.LockModeType;

public class PessimisticLocking {

    public static void thread1() throws InterruptedException {
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();

        Query<Employee> query = session
                .createQuery("FROM Employee WHERE id = 1")
                .setLockMode(LockModeType.PESSIMISTIC_WRITE);
        Employee employee = query.getSingleResult();
        System.out.println("thread1------"+employee);
        Thread.sleep(10000);
        session.getTransaction().commit();
        session.close();
        sessionFactory.close();
    }

    public static void thread2(){
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();

        Query<Employee> query = session
                .createQuery("FROM Employee WHERE id = 1")
                .setLockMode(LockModeType.PESSIMISTIC_WRITE);
        Employee employee = query.getSingleResult();
        System.out.println("thread2-------"+employee);

        session.getTransaction().commit();
        session.close();
        sessionFactory.close();
    }

    public static void main(String[] args) {
        new Thread(()->{
            try {
                thread1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            thread2();
        }).start();
    }
}

Pessimistic lock type

LockModeType Description
NONE The absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to update()or saveOrUpdate() also start out in this lock mode.
PESSIMISTIC_READ The entity is locked pessimistically using a shared lock, if the database supports such a feature. Otherwise, an explicit lock is used.
PESSIMISTIC_WRITE The entity is locked using an explicit lock.
PESSIMISTIC_FORCE_INCREMENT
The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed.

Exercise 3

Demonstrate shared and exclusive pessimistic locking for Person entity.

Optimistic locking

When the application uses long transactions or conversations that span several database transactions, you can store versioning data so that if the same entity is updated by two conversations, the last to commit changes is informed of the conflict, and does not override the other conversation’s work.

 

This approach guarantees some isolation, but scales well and works particularly well in read-often-write-sometimes situations.

 

Hibernate provides two different mechanisms for storing versioning information, a dedicated version number or a timestamp.

Version

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Employee {

    @Id
    private Integer id;
    private String name;
    private Integer age;
    @Version
    private Long version;

    // getters and setters
}
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class LockingApplication {


    void thread1(){
        try {
            SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory();
            Session session = sessionFactory.openSession();
            session.beginTransaction();

            Employee employee = session.get(Employee.class,1);

            employee.setName("Name56");
            session.update(employee);

            session.getTransaction().commit();
            session.close();
            sessionFactory.close();
            System.out.println("THREAD 1 !!!!");
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    void thread2(){
        try {
            SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
            Session session = sessionFactory.openSession();
            session.beginTransaction();

            Employee employee2 = session.get(Employee.class, 1);


            employee2.setAge(48);
            session.update(employee2);

            session.getTransaction().commit();
            session.close();
            sessionFactory.close();
            System.out.println("THREAD 2 !!!!");
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(()->{
            new LockingApplication().thread1();
        }).start();

        new Thread(()->{
            new LockingApplication().thread2();
        }).start();
    }
}
  • OptimisticLocking: This annotation is used to specify if the currently annotated attribute will trigger an entity version increment upon being modified.
  • OptimisticLockType: The four possible strategies are defined by the OptimisticLockType enumeration.
OptimisticLockType Description
NONE optimistic locking is disabled even if there is a @Version annotation present
VERSION (the default) performs optimistic locking if there is a @Version annotation present
ALL performs optimistic locking based on all fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements
DIRTY performs optimistic locking based on dirty fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements

Versionless optimistic locking: Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". This is also useful for use with modeling legacy schemas.

 

import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.OptimisticLocking;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
public class Employee {

    @Id
    private Integer id;
    private String name;
    private Integer age;
//    @Version
    private Long version;

    // getters and setters  
}

@DynamicUpdate: ​For updating, the entity uses dynamic sql generation where only changed columns ( for OptimisticLockType.DIRTY) or all columns ( for OptimisticLockType.ALL)  get referenced in the where clause

Optimistic v/s Pessimistic

1. Optimistic locking does not cause transactions to wait for each other like Pessimistic locking. if a transaction fails because of optimistic locking, the user is required to start all over again.

 

2. Optimistic locking is generally faster because there is actually no locking from the database point of view. Pessimistic locking occurs on DB level, depends on RDMS we may not have control on what is locked, we need to take care of locking order manually.

Exercise 4

Demonstrate version and version less locking for Person.

Batching

DBC offers support for batching together SQL statements that can be represented as a single PreparedStatement. Implementation wise this generally means that drivers will send the batched operation to the server in one call, which can save on network calls to the database. Hibernate can leverage JDBC batching. The following settings control this behavior.

hibernate.jdbc.batch_size

 Hibernate also specific JDBC batch size configuration on a per Session basis.

entityManager
    .unwrap( Session.class )
    .setJdbcBatchSize( 10 );

Session Batching

SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
    entityManager = entityManagerFactory().createEntityManager();

    txn = entityManager.getTransaction();
    txn.begin();

    for ( int i = 0; i < 100_000; i++ ) {
        Person Person = new Person( String.format( "Person %d", i ) );
        entityManager.persist( Person );
    }

    session.getTransaction().commit();
  session.close();
  sessionFactory.close();

The following example shows an anti-pattern for batch inserts.

Naive way to insert 100 000 entities with Hibernate.

There are several problems associated with this example:

  • Hibernate caches all the newly inserted Customer instances in the session-level c1ache, so, when the transaction ends, 100 000 entities are managed by the persistence context. If the maximum memory allocated to the JVM is rather low, this example could fails with an OutOfMemoryException. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap.

  • long-running transactions can deplete a connection pool so other transactions don’t get a chance to proceed.

  • JDBC batching is not enabled by default, so every insert statement requires a database roundtrip. To enable JDBC batching, set the hibernate.jdbc.batch_size property to an integer between 10 and 50.

Batch Insert

SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
try {
    entityManager = entityManagerFactory().createEntityManager();

    txn = entityManager.getTransaction();
    txn.begin();

    int batchSize = 25;

    for ( int i = 0; i < entityCount; ++i ) {
        Person Person = new Person( String.format( "Person %d", i ) );
        entityManager.persist( Person );

        if ( i > 0 && i % batchSize == 0 ) {
            //flush a batch of inserts and release memory
            entityManager.flush();
            entityManager.clear();
        }
    }

  session.getTransaction().commit();
  session.close();
  sessionFactory.close();

Exercise 5

Implement batch insert for 10000 Person instances.

Enums in Hibernate

Hibernate supports the mapping of Java enums as basic value types in 2 different ways.

@Enumerated

The original JPA-compliant way to map enums was via the @Enumerated.

Enum values are stored according to one of 2 strategies indicated by javax.persistence.EnumType :

ORDINAL

stored according to the enum value’s ordinal position within the enum class, as indicated by java.lang.Enum#ordinal

STRING

stored according to the enum value’s name, as indicated by java.lang.Enum#name

 

 

Exercise 6

Introduce an enum Gender in Person entity and save the Person instance with enum value.

Merge

Employee employee = session.get(Employee.class,7);
employee.setAge(51);

session.getTransaction().commit();
session.close();

Session session2 = sessionFactory.openSession();
session2.beginTransaction();

Employee employee2 = session2.get(Employee.class,7);
employee2.setName("Edited Name");
session2.merge(employee2);

session2.getTransaction().commit();
session2.close();

Intention of the merge method is to update a persistent entity instance with new field values from a detached entity instance.

Advance Hibernate Part-2

By Pulkit Pushkarna

Advance Hibernate Part-2

  • 1,204