Versioned Objects in Hibernate/JPA
Suppose two
users access same record of a table simultaneously. After some time
one of the user updates the record but still the other user uses the
old data that was loaded when it was first accessed. Now if the
second user in this case tries to update the record without
refreshing it (to get its updated version) then he might overwrite
the changes made by the first user.
In order to
handle the above scenario the versioned entities are used. This
versioning strategy is also known as optimistic locking. Following is
the code that shows how to enable optimistic locking in
Hibernate/JPA.
@Entity public class MyEntity implements Serializable { //... @Version private Long version; //... }
In optimistic
locking whenever a record is accessed its version is noticed and this
version is compared before the same record is updated. If the version
before updating the record is found to be same then it means no
change has been made after the record was first accessed. However the
difference in the version found before updating the record shows that
it has already been updated by some other transaction after it was
first accessed.
In the
background the version field is added to the filter criteria while
fetching the data along with other necessary filters in the query. In
the above scenario the actual record version was updated but the
WHERE clause still used the old version to look up the same record
that was first accessed which in return matches nothing and no row is
returned by the SELECT query. In such cases Hibernate throws
org.hibernate.StaleObjectStateException (wrapped in a
javax.persistence.OptimisticLockException). The possible exception
handling could be to prompt user to refresh the data from UI or
internally refresh the stale object whenever
StaleObjectStateException is thrown.
Note: The
version field is not necessarily a number. It might be a timestamp or
any other unique value.
Different types of queries available in Hibernate and their Usage
Hibernate
supports three types of query languages which are as follows;
1. HQL
(Hibernate Query Language)
2. JPQL (JPA
Query Language)
3. Native
SQL
All of above
types of queries can optionally be used as Named Queries. The named
query is added in the entity class and therefore we do not have to
scatter the HQL, JPQL or Native SQL code over different places within
the Java code. I personally believe that as long as the architecture
of the application is well designed and all the queries are placed
only within the designated layer (lets say DAO layer) the Named
Queries should not be used.
However on
the other hand a badly designed application may have queries
scattered over multiple layers in that case it seems to be preferable
to use Named Queries to avoid maintainability issues.
Note: With
the exception of names of Java classes and properties, HQL/JPQL
queries are case-insensitive.
HQL Queries
HQL queries
are compatible with the hibernate native ORM implementation.
Following is the code to create HQL queries.
List<Customer> customers = (List<Customer>)session.createQuery("FROM Customer").list();
Following is
the code to create HQL Named queries.
@NamedQueries( { @NamedQuery(name = "getAllCustomers", query = "FROM Customer") } ) @Entity @Table(name = "customer") public class Customer implements java.io.Serializable { ...
List<Customer> customers = (List<Customer>)session.getNamedQuery("getAllCustomers").list();
JPQL Queries
JPQL queries
are compatible with the hibernate JPA implementation. Following is
the code to create JPQL queries.
List<Customer> customers =(List<Customer>)entityManager.createQuery("SELECT c FROM Customer c").getResultList();
Following is
the code to create JPQL Named queries.
@NamedQueries( { @NamedQuery(name = "getAllCustomers", query = "SELECT c FROM Customer c") } ) @Entity @Table(name = "customer") public class Customer implements java.io.Serializable { ...
List<Customer> customers = (List<Customer>)entityManager.createNamedQuery("getAllCustomers").getResultList();
Native Queries
Native
queries are compatible with both hibernate native ORM implementation
as well as its JPA implementation. Native queries provide great
flexibility in that they let us write simple SQL queries that can
easily be optimized. However while using native queries we should be
very sure that using HQL/JQPL queries is less convenient in that
particular situation.
a. Hibernate native
ORM implementation
We can bind
native SQL to an entity by adding invoking the addEntity method on
the SQL Query object. There doesn't exist any table for the entity
class bound to a native SQL query, it is just a Java class mapped to
an SQL query (isn't it powerful?). Following is the code to create
native queries with hibernate native ORM implementation.
query = session.createSQLQuery("SELECT ename, job, dept.deptno, dname " + "FROM emp, dept " + "WHERE emp.deptno (+) = dept.deptno AND job (+) = 'MANAGER'").addEntity(Employee.class); List<Employee> list = query.list();
Following is
the code to create named native queries with hibernate native ORM
implementation.
@NamedNativeQueries( { @NamedNativeQuery(name = "findEmployees",query = "SELECT ename, job, dept.deptno, dname " + "FROM emp, dept " + "WHERE emp.deptno (+) = dept.deptno AND job (+) = 'MANAGER'", resultClass = Employee.class) } ) @Entity @Table(name = "employee") public class Employee implements java.io.Serializable { ...
List<Employee> employees = (List<Employee>)session.getNamedQuery("findEmployees").list();
b.
Hibernate JPA implementation
Following is
the code to create native queries with hibernate JPA implementation.
Query query = entityManager.createNativeQuery("SELECT ename, job, dept.deptno, dname FROM emp, dept WHERE emp.deptno (+) = dept.deptno AND job (+) = 'MANAGER'",Employee.class); List<Employee> list = query.getResultList();
Following is
the code to create named native queries with hibernate JPA
implementation.
@NamedNativeQueries( { @NamedNativeQuery(name = "findEmployees",query = "SELECT ename, job, dept.deptno, dname " + "FROM emp, dept " + "WHERE emp.deptno (+) = dept.deptno AND job (+) = 'MANAGER'", resultClass = Employee.class) } ) @Entity @Table(name = "employee") public class Employee implements java.io.Serializable { ...
List<Employee> employees = (List<Employee>)entityManager.createNamedQuery("findEmployees").getResultList();
Preventing SQL injection in Hibernate
Always use
indexed parameters or named parameters in all (HQL,JPQL and native
SQL) queries which ensures that the parameter values will always be
validated or escaped appropriately by the JDBC driver. Thus with the
use of these parameters SQL injections can easily be prevented. Using
named parameters keeps the code more readable as compared to the
indexed parameters. Following are some simple examples.
/* Positional parameter in HQL */ Query query = session.createQuery("from Orders as orders where orders.id = ?"); List results = query.setString(0, "12").list(); /* Named parameter in HQL */ Query query = session.createQuery("from Employees as emp where emp.bonus >:bonus"); List results = query.setLong("bonus", new Long(12000)).list();
Flushing and Refreshing
Flushing and
Refreshing are the processes that synchronize the data between
application and database layers. The entityManager.flush()
synchronizes all the changes that are made to the persistent entities
back to the underlying database, whereas the entityManager.refresh()
method does the reverse. It updates the entity object with values
taken from the database. Any new values that are set to the entity
objects are lost after calling refresh() method.