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