Notes on JPA 2.0 Created 10/05/12 Updated 10/26/12, Updated 04/04/14, Updated 06/20/14, Updated 09/03/14, Updated 09/24/14, Updated 10/20/14 Updated 11/12/14 Introduction This document updates our “Notes on Hibernate” to include how JPA provides a standard interface to persistence. Concepts The JPA EntityManager is the conceptual equivalent of the Hibernate Session. The most useful method on the EntityManager is find(class, id). Other commonly used methods are save(), merge(), update(), delete(). Create a query, and then call getResultList. Annotations Use @Entity and @Table. These can be found from the javax.persistance package and are the same as those in Hibernate Annotations. EntityManager Lifecycle Changes are queued up and then written when the EntityManager is closed. Lazy Loading To optimize eager loading by using an outer join you have to add @Fetch(FetchMode.JOIN) to your field. This is a Hibernate-specific annotation. Transaction Management To be filled in Referential Integrity Mappings @ManyToOne List @OneToMany List @ManyToMany Set Using mappedby to avoid having to specify the join column on both sides. Query Management JPA has a query builder. Here is an example which returns a list: Page 1 of 3 public List<Page> findEntityListByVendor(Vendor vendor) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Page> criteria = cb.createQuery(Page.class); Root<Page> root = criteria.from(Page.class); Path<Vendor> rootVendor = root.get("vendor"); criteria.where(cb.equal(rootVendor, vendor)); return em.createQuery(criteria).getResultList(); } Here is another example which returns a single object (or null): public Page findEntityByName(String name) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<Page> criteria = builder.createQuery(Page.class); Root<Page> root = criteria.from(Page.class); Path<String> rootName = root.get("name"); criteria.where(builder.equal(rootName, name)); try { return em.createQuery(criteria).getSingleResult(); } catch (NoResultException e) { return null; } } More Complex Query Management This includes joins, and and/or conditions in the predicateTree used for the where criteria. public List<Order> findOrdersByEmail(String email) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<Order> criteria = builder.createQuery(Order.class); Root<Order> root = criteria.from(Order.class); Join<Order, BillingInfo> orderBillingInfo = root.join("billingInfos"); Join<Order, Donor> orderDonor = root.join("donor"); Predicate predicateTree = builder.or( builder.equal(orderBillingInfo.get("billingEmail"), email), builder.equal(orderDonor.get("login"), email)); criteria.where(predicateTree); criteria.orderBy(builder.asc(root.get("dateCreated"))); return orders = em.createQuery(criteria).getResultList(); } Query Language "node to traverse cannot be null!" is a generic Hibernate error message indicating a syntax problem in your query. As another example, forgetting to start a select clause with the word "SELECT" would yield the same error. In this instance the syntax error is due to the on clause - HQL does not support them. Instead do a cross join like so: FROM track_history_items thi, artists art WHERE thi.type = "TrackBroadcast" AND thi.artist_id = art.id GROUP BY art.name ORDER thi.createdAt DESC Page 2 of 3 Native Queries It's interesting to note that you're not limited to JPQL when defining queries to be then executed with Query API. You may be surprised to learn that the EntityManager API offers methods for creating instances of Query for executing native SQL statements. The most important thing to understand about native SQL queries created with EntityManager methods is that they, like JPQL queries, return entity instances, rather than database table records. Here is a simple example of a dynamic native SQL query: ... List<Customer> customers = (List<Customer>)em.createNativeQuery ("SELECT * FROM customers", jpqlexample.entities.Customer.class) .getResultList(); Iterator i = customers.iterator(); Customer cust; out.println("Customers: " + "<br/>"); while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_name() +"<br/>"); } ... JPQL is still evolving, and doesn't have many of those important features available in SQL. In the Defining JPQL Joins earlier section, you saw an example of JPQL's incompleteness: you had to do a lot of work on your own because the JPQL's SUM aggregate function cannot take an arithmetic expression as the parameter. In contrast, the SQL's SUM function doesn't have such a limitation. So, this is a good example of where replacing JPQL with native SQL could be efficient. The following code illustrates how you might simplify things in this particular example by choosing native SQL over JPQL: ... String sup_name ="Tortuga Trading"; BigDecimal sum = (List)em.createNativeQuery("SELECT SUM(p.price*l.quantity) FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name =?1") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ... Among other things, the above example illustrates that you can bind arguments to native query parameters. In particular, you can bind arguments to positional parameters in the same way as if you were dealing with a JPQL query. The main disadvantage of native queries is complexity of result binding. In the example, the query produces a single result of a simple type, thus avoiding this problem. In practice, however, you often have to deal with a result set of a complex type. In this case, you will have to declare an entity to which you can map your native query, or define a complex result set mapped to multiple entities or to a blend of entities and scalar results. Page 3 of 3