JavaEnterprise JavaThe Exciting New Features in JPA 2

The Exciting New Features in JPA 2

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

The Java Persistence API version 2 (JPA 2), released as part of the Java EE 6 specification in November 2009, introduced new and interesting features to the Java community. In this article, I examine some of the major advancements that JPA 2 made in its API, the Java Persistence Query Language (JPQL), and the properties defined in its persistence.xml configuration file. The code snippets provided are all compatible with EclipseLink, which is the reference implementation of JPA 2.

JPA 2 API Enhancements

In this section, I describe the new API methods added to the JPA API in version 2. Specifically, I discuss the enhancements made to the following APIs:

  • EntityManager
  • EntityManagerFactory
  • Query

Enhancements in EntityManagerFactory Interface

EntityManagerFactory acts as a pool of EntityManager instances, which are used to interact with the database. EntityManagerFactory is created for each database and it is analogous to a connection pool. The EntityManagerFactory instance is created using the createEntityManagerFactory static method of the Persistence class with the argument being the persistence unit name, for example:

EntityManagerFactory emf = 
Persistence.createEntityManagerFactory("JPAProject");

JPA 2 also introduced a second layer of cache, which is shared across various persistence contexts and can be accessed using the EntityManagerFactory. (For more details refer to JPA 2.0 Cache Vs. Hibernate Cache: Differences in Approach.) The Cache interface introduced in JPA 2 can be implemented to provide this second-level cache. An instance of Cache can be obtained by invoking thegetCache() method on the EntityManagerFactory instance, which can then be used to interact with the second-level cache.

Here is an example:

EntityManagerFactory emf = 
Persistence.createEntityManagerFactory("JPAProject");
Cache cache = emf.getCache();

Enhancements in EntityManager Interface

Entities are the basic programming artifact used in JPA programming; they are used to provide object relational mapping (ORM) using various metadata annotations. These entity instances are managed by the EntityManager instance, which is obtained from EntityManagerFactory. All the operations on the Entity instances are managed by EntityManager, much like a connection instance.

The EntityManager instance is obtained in the following way from the EntityManagerFactory:

EntityManagerFactory emf = 
Persistence.createEntityManagerFactory("JPAProject");
EntityManager em = emf.createEntityManager();

The additional methods included in the EntityManager interface are:

  • detach
  • getEntityManagerFactory
  • Overloaded find methods

1. detach

The detach method defined in JPA 2 removes the specified entity from the persistence context and removes the changes made to the entity before flushing is all undone. JPA 1.x defined only a single clear method for removing all the managed entities from the persistence context. While this method was very useful for clearing the persistence context, it had its own limitation: The developer was not allowed to remove a specific instance from the persistence context. This is now possible in JPA 2 using the new detach method, which can be used as follows:

Item myItem = new Item(); 
em.detach(myItem);

In the above example a call to persist on the detached myItem instance will not have any effect, because it will be removed from the database when the detach is invoked and hence will not be stored.

2. getEntityManagerFactory

The getEntityManagerFactory method is used to obtain the EntityManagerFactory object, which is responsible for creating the EntityManager instances that are used to manage the entities. Here’s an example:

   EntityManagerFactory entityMgrFactory = em.getEntityManagerFactory();

3. Overloaded find

The find method is used to retrieve a specific entity from the persistence context based on its primary key. JPA 2 introduces three overloaded find methods. The first overloaded method searches for an entity with respect to its primary key and then locks the entity with its respective lock type specified in the method. A lock is generally put on an entity to prevent any another transaction from updating the entity when the present transaction is modifying it.

Pessimistic and optimistic locking are the two main locking modes. (Refer to Pessimistic Locking in JPA 2 and Hibernate for more details on the locking modes in JPA. Here’s an example:

   Item myItem = em.find(Item.class, 1, LockModeType.PESSIMISTIC_READ);

In general, the effect of the find method with a lock type is equivalent to searching for the entity type and then applying a lock mode to it. But if the lock mode specified is pessimistic with a version attribute set to the entity, an optimistic version check is performed to obtain the lock. If these checks fail, then an OptimisticLockException is thrown. If a failure in the database-locking mechanism causes the transaction to be rolled back, then a PessimisticLockException is thrown. Alternatively, if a statement-level rollback happens, then a LockTimeoutException is thrown by the persistence provider.

The second overloaded find method searches for an entity based on the primary key, using a vendor-specific property that is supplied to it as a Map object. If this property is not recognized, then it is not considered for finding the entity. Here’s an example:

Item myItem = em.find(Item.class, 1, vendorSpecificMap);

The third overloaded find method uses the primary key and vendor-specific properties and applies a lock on the entity. Here’s an example:

Item myItem = em.find(Item.class, 1, vendorSpecificMap, LockModeType.PESSIMISTIC_READ);

Enhancements in Query Interface

The Query interface is used to execute JPQL queries in JPA. It is obtained in the following way:

EntityManagerFactory emf = 
Persistence.createEntityManagerFactory("JPAProject");
EntityManager em = emf.createEntityManager();
Query myQuery = em.createNamedQuery("Item.findAll");

The additional methods included in the Query interface as part of JPA 2 are:

  • setLockMode and getLockMode
  • getHints
  • getMaxResults
  • getFirstResult
  • getFlushMode

1. setLockMode and getLockMode

The setLockMode method is used to obtain a lock on the object returned as a result of the query. In general, the lock is obtained on individual entities affected by that query. Here’s an example:

   Query myQuery = em.createNamedQuery("Item.findAll") ; 
myQuery.setLockMode(LockModeType.PESSIMISTIC_WRITE);

The getLockMode method is used to obtain the actual lock applied to the Query instance. Here’s an example:

Query myQuery = em.createNamedQuery("Item.findAll") ;   
LockModeType lockMode = myQuery.getLockMode();

2. getHints

The setHint method (introduced as part of JPA 1) adds a vendor-specific hint to the query; these hints are not portable across different vendors. The getHints method defined as part of JPA 2 allows the developer to obtain the previously set hints to the query instance. Here’s an example:

   Query myQuery = em.createNamedQuery("Item.findAll"); 
myQuery.setHint("eclipselink.cache-usage", "DoNotCheckCache");
Map<String, Object> myMap = myQuery.getHints();

These hints can also be provided when the named query is created in the following manner:

@NamedQuery(name = "Item.findAll", query = "SELECT i FROM Item i", hints={@QueryHint(name="eclipselink.cache-usage",

 


value="DoNotCheckCache")})

3. getMaxResults

This method returns the maximum number of results that a query instance is supposed to return. If this value is not set using setMaxResults (part of JPA 1) to the query object, then the result of this method would be equivalent to Integer.MAX_VALUE. Here’s an example:

   Query myQuery = em.createNamedQuery("Item.findAll"); 
myQuery.setMaxResults(12);
System.out.println("GET MAX RESULT " + myQuery.getMaxResults());

4. getFirstResult

The setFirstResult (part of JPA1) method of the Query interface sets the position of the first record that needs to be obtained from the Query object. This value can now be retrieved using the getFirstResult method. If the position value is not set, it returns 0. Here’s an example:

   Query myQuery = em.createNamedQuery("Item.findAll") ; 
myQuery.setFirstResult(4);
System.out.println("FIRST RESULT " + myQuery.getFirstResult());

5. getFlushMode

Flush modes are important for ensuring the integrity of the data obtained through queries. AUTO and COMMIT are the two FlushModeTypes available. If the flush mode is set to AUTO (the default mode), then the persistence provider has to ensure that the result of the query is consistent with the operations performed on the entities. This is ensured by updating the state of the entity after each transaction so that the other transactions do not get any inconsistent data.

On the other hand, if the flush mode is COMMIT, then the persistence provider does not take the responsibility of committing the transaction. Instead, it synchronizes the data once the database COMMIT is performed. The getFlushMode method returns the FlushModeTypes set to the query instance and returns the flush mode set at the EntityManager level if it is not set. Here’s an example:

   Query myQuery = em.createNamedQuery("Item.findAll") ; 
FlushModeType flushMode = myQuery.getFlushMode();

Standard Persistence Properties

The persistence.xml file, the configuration file for JPA applications has definitions for a set of persistence units. Each persistence unit defines one or more managed entities that the EntityManager instance of an application can manage. Each persistence unit provides a set of properties for configuring the data source. Along with all the necessary connection details, the persistence unit also specifies the JPA provider. It is possible to have different persistence providers for different persistence units.

With JPA 1, each provider defined its own set of JDBC properties and it was very difficult to manage them with different providers across different persistence units. JPA 2 eliminates this problem by standardizing these properties. The following code snippet shows a sample persistence.xml file that defines the JDBC properties:

<persistence-unit name="JPAProject" 
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!--Entity Classes -->
<class>com.infosys.demo.Item</class>
<class>com.infosys.demo.Employee</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/JPA 2" />
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="infosys"/>
<property name="javax.persistence.jdbc.show_sql" value="true" />
</properties>
</persistence-unit>

The <persistence-unit> element holds the configuration information about the data source. The name attribute specifies the name with which this persistence unit is identified, which will be provided while creating the EntityManagerFactory instance. The persistence unit specifies the provider (EclipseLink in this case) and the various Entity classes that need to be managed within the persistence context. It also specifies the various JDBC properties such as the driver name, database URL, username, password, and so on.

Additional JPQL Features

JPQL was one of the important factors in the success of JPA1.x. One of the most significant developments that JPA 2 brought in was in its querying model. More object-oriented querying was made possible in JPA 2 by criteria queries (explained in detail in Querying in JPA 2: Typesafe and Object Oriented). With the Query interface, JPA 2 introduced the TypedQuery interface, which extends the Query interface and makes JPQL queries more type safe. With this interface, the query can be made to return a specific type. For example, in the following code snippet the query object is of TypedQuery type and its return type is Employee:

EntityManagerFactory emf = 
Persistence.createEntityManagerFactory("JPAProject");
EntityManager em = emf.createEntityManager();

TypedQuery<Employee> query =
em.createQuery("SELECT e FROM Employee e", Employee.class);
List<Employee> r = query.getResultList();

In the above code snippet, a TypedQuery instance query is created for a SELECT query. At the time of creating the query instance we are sure that the query would return only Employee instances, which is specified in the second argument of the createQuery method.

The following additional capabilities were added to JPQL in JPA 2:

  • A collection object can become an argument to IN expression
  • Support for non-polymorphic queries
  • Provision for timestamp literals
  • CASE statement in JPQL
  • Querying ordered lists

1. A Collection Object Can Become an Argument to IN Expression

The IN expression used in the WHERE clause of the JPQL query now can be supplied with a Collection object as the parameter. In the following example, the JPQL query selects the Bankdetails of customers whose names are listed in the names object.

List<String> names = new ArrayList<String>(); 
names.add("Nitin");
names.add("Mahalakshmi");
names.add("Nirmal");
List<Bankdetails> result =
em.createQuery("SELECT x FROM Bankdetails x WHERE x.name IN :namesList").setParameter("namesList", names).getResultList();

2. Support for Non-polymorphic Queries

JPA 1.x supported polymorphic queries, where the query fired in terms of the parent class would yield all its sub classes also. But there are scenarios where the developer is interested only in one sub class and not in obtaining the result of the other sub class. In this scenario, non-polymorphic queries come in very handy.

In the example below, CreditCard and BankAccount are the two sub classes of the Bankdetails class and the implementation uses the one-table-per-class hierarchy strategy in JPA. If the developer is interested in obtaining only CreditCard objects, the TYPE function can be used to specify the type that needs to be obtained.

List<Bankdetails> result = (List<Bankdetails>) em.createQuery(

 


"SELECT x FROM Bankdetails x WHERE TYPE(x) = CreditCard").getResultList();

3. Provision for Timestamp Literals

JPA 2 supports timestamp literals in JPQL queries. The Employee entity class defines a java.util.Date attribute, which is decorated with @Temporal. The Temporal types are used for properties of an Entity class that are basically time based. The supported Temporal types are javax.sql.Data, javax.sql.Time, javax.sql.Timestamp, java.util.Date and java.util.Calendar.

@Entity 
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Temporal(TemporalType.TIMESTAMP)
private Date time;
// Some other code
}

The following query searches for all the Employee instances whose time attribute is greater than a specified value.

List<Employee> result = (List< Employee >) 
em.createQuery("SELECT e from Employee e WHERE e.time > '2008-06-01 10:00:01.0-09:00'").getResultList();

4. CASE Statement in JPQL

JPA 2 supports CASE expressions in JPQL queries. CASE statements are very helpful in a situation where bulk updates on the entities need to be performed based on a specific condition. CASE statements can also be used in a SELECT clause, which will help in reporting the queries in a much better manner. The general syntax for defining a CASE expression is as follows:

CASE { 
WHEN <conditional_expression> THEN <Scalar_Expression>
WHEN <conditional_expression> THEN <Scalar_Expression>
….
}
ELSE <Scalar_Expression> END

CASE expressions for UPDATE are used in case of bulk updates, and updates to entities can be performed by calling the executeUpdate method on the query instance.

em.createQuery("UPDATE Employee e SET e.age = CASE e.id WHEN 1 THEN 23 WHEN 2 THEN 24 ELSE 25 END").executeUpdate();

In the above code snippet the Employee instances are updated with a new age property based on the id property (i.e. the age is set to 23 if the id is 1 or set to 24 if the id is 2, and the default age of 25 is set for those Employee instances whose id is not specified).

CASE expressions in SELECT are used mainly for reporting purpose and returning better query results. In this case, a CASE expression will be used with a SELECT clause of the JPQL query.

List<Object[]> result3 = em.createQuery( 
"SELECT x.name, CASE WHEN TYPE(x) = CreditCard THEN 'CREDITCARD' "
+ " WHEN TYPE(x) = BankAccount THEN 'BANKACCOUNT' "
+ " ELSE 'BA' END"
+ " FROM Bankdetails x where x.balance > 20000", Object[].class)
.getResultList();

In this example, the CASE expression is used in the SELECT clause to obtain the Bankdetails of those customers whose balance is greater than 20000.

5. Querying the Ordered List

JPA 2 specifies a new annotation @OrderColumn for ManyToMany and OneToMany relationships by adding a new index column, which need not be mapped in the Entity Class. In this example the Employee has a OneToMany relationship with the Dealer class and the indexno column of the Dealer table maintains the order of insertion (index).

@Entity 
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;

// Other code
@OneToMany(cascade = CascadeType.ALL, mappedBy="empId")
@OrderColumn(name="indexno")
private List<Dealer> dealerCollection;
}

The following query queries for the first three Dealers associated with an Employee whose name is specified. The INDEX function can be used to index the Dealer instances in the query.

List<Dealer> res = 
em.createQuery("SELECT d from Employee e JOIN e.dealerCollection d"
+ " WHERE e.empName='Maha' AND INDEX(d) < 3").getResultList();

Conclusion

In this article, I explained with code snippets the new enhancements made to the EntityManager, EntityManagerFactory and Query API, the standardized properties defined in the persistence.xml file and certain advancements made to JPQL. The code snippets provided are all compatible with the JPA 2 reference implementation EclipseLink.

Acknowledgements

I would like to sincerely thank Mr. Subrahmanya (SV, VP, ECOM Research Group, E&R) for his constant encouragement and Ms. Sangeetha S for providing the ideas, guidance, and valuable comments as well as for kindly reviewing this article.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories