Step 4.4 - Modify ProductController to Enable Collection Cache
The SimpleJpaHibernateApp is extended in "
Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 1 - Co-ordinating Query Views Based on Parameter Passing from JSF View to Managed Bean" and the above steps, to demonstrate easy modification to achieve JSF query views co-ordination and then JMX monitoring. The ProductCode-to-Product entity 'navigation' and retrieval are done by JPA Query with SQL joint and parameter binding as shown in Code Listing 4.4.1, instead of one-to-many entity mapping and entity collection retrieval. Hence, collection cache are not used as observed previously from the Hibernate JMX cache statistics on collection cache.
Coding Listing 4.4.1 - simpleJpaHibernateApp.controllers.ProductController#getProducts() - using JPA Query with SQL joint and parameter binding
/**
* This version of ProductController uses JPA query with SQL joint
* to retrieve all Products associated with a given ProductCode
* indicated by its ID 'selectedProdCode'.
* This shows simple code modification to get additional behaviour.
*
* @author Max Poon (maxpoon@dev.java.net)
*/
public class ProductController {
...
public DataModel getProducts() {
EntityManager em = getEntityManager();
try{
Query q;
if (selectedProdCode != null && selectedProdCode.length() != 0) {
q = em.createQuery("select object(o) from Product as o " +
"where o.productCode.prodCode = :selectedProdCode")
.setParameter("selectedProdCode", selectedProdCode);
} else {
q = em.createQuery("select object(o) from Product as o");
}
q.setMaxResults(batchSize);
q.setFirstResult(firstItem);
model = new ListDataModel(q.getResultList());
return model;
} finally {
em.close();
}
}
...
public int getItemCount() {
EntityManager em = getEntityManager();
try{
Query q;
if (selectedProdCode != null && selectedProdCode.length() != 0) {
q = em.createQuery("select count(o) from Product as o " +
"where o.productCode.prodCode = :selectedProdCode")
.setParameter("selectedProdCode", selectedProdCode);
} else {
q = em.createQuery("select count(o) from Product as o");
}
int count = ((Long) q.getSingleResult()).intValue();
return count;
} finally {
em.close();
}
}
...
An alternative to the above is to get the
ProductCode entity class using the
selectedProdCode string :
ProductCode productCode = em.find(ProductCode.class, selectedProdCode);
Then for
ProductController.getProducts(), retrieve all the associated
Product's from
ProductCode.getProductonCollection() using one-to-many association :
productList = (List<Product>) productCode.getProductCollection();
For
ProductController.getItemCount(),
count = productCode.getProductCollection().size();
as shown in Code Listing 4.4.2 below :
Coding Listing 4.4.2 - simpleJpaHibernateApp.controllers.ProductController#getProducts() and getItemCount() - using JPA Entity Collection, e.g. to show use of Hibernate Collection Cache in additional to Class Caching
/**
* This version of ProductController uses entity collection for
* retrieving all Products associated with a given ProductCode
* indicated by its ID 'selectedProdCode' to show JMX monitoring
* on collection cache in additional to entity class cache
*
* @author Max Poon (maxpoon@dev.java.net)
*/
public class ProductController {
...
public DataModel getProducts() {
EntityManager em = getEntityManager();
try{
List<Product> productList;
if (selectedProdCode != null && selectedProdCode.length() != 0) {
ProductCode productCode = em.find(ProductCode.class, selectedProdCode);
productList = (List<Product>) productCode.getProductCollection();
} else {
Query q = em.createQuery("select object(o) from Product as o");
q.setMaxResults(batchSize);
q.setFirstResult(firstItem);
productList = (List<Product>) q.getResultList();
}
model = new ListDataModel(productList);
return model;
} finally {
em.close();
}
}
...
public int getItemCount() {
EntityManager em = getEntityManager();
try{
int count;
if (selectedProdCode != null && selectedProdCode.length() != 0) {
ProductCode productCode = em.find(ProductCode.class, selectedProdCode);
count = productCode.getProductCollection().size();
} else {
Query q = em.createQuery("select count(o) from Product as o");
count = ((Long) q.getSingleResult()).intValue();
}
return count;
} finally {
em.close();
}
...
The entity class
ProductCode.java is also required to further enable collection cache on
ProductCode.productCollection (similarly for
Manufacturer.productCollection in
Manufacturer.java) :
Code Listing 4.4.3 - ProductCode.java with Hibernate @Cache Annotation to enable Entity Class Cache on ProductCode and Collection Cache on ProductCode.productionCollection
/*
* ProductCode.java
*
*/
package simpleJpaHibernateApp.entities;
import java.io.Serializable;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
* Entity class ProductCode, adding Hibernate Annotations to enable
* Entity Class Cache on ProductCode, and
* Collection Cache on ProductCode.productCollection
*
* @author Max Poon (maxpoon@dev.java.net)
*/
@Entity
@Table(name = "PRODUCT_CODE")
@NamedQueries( {
......
} )
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ProductCode implements Serializable {
@Id
@Column(name = "PROD_CODE", nullable = false)
private String prodCode;
@Column(name = "DISCOUNT_CODE", nullable = false)
private char discountCode;
@Column(name = "DESCRIPTION")
private String description;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "productCode")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Collection<Product> productCollection;
......
After recompiling SimpleJpaHibernateApp and invoking its "Listing Products" page for
ProdCode="SW" (
Figure 4.3.7), the JMX statistics collected by JConsole are shown :
* Hibernate JMX statistics in
Figure 4.4.1
* Ehcache JMX statistics
Class Cache for simpleJpaHibernateApp.entities.ProductCode, in Figure 4.4.2
Class Cache for simpleJpaHibernateApp.entities.Product, in Figure 4.4.3
Collection Cache simpleJpaHibernateApp.entities.ProductCode.productionCollection, in Figure 4.4.4
Figure 4.4.1 - JConsole showing Hibernate JMX statistics, on SimpleJpaHibernateApp using @Cache Annotation (to enable Collection Cache), as compared to Figure 4.3.8 for SimpleJpaHibernateApp using JPA Query
Figure 4.4.2 - JConsole showing Ehcache JMX Class Cache Statistics for ProductCode, on SimpleJpaHibernateApp using @Cache Annotation to enable Collection Cache, as compared to Figure 4.3.9 for SimpleJpaHibernateApp using JPA Query
Figure 4.4.3 - JConsole showing Ehcache JMX Class Cache Statistics for Product, on SimpleJpaHibernateApp using @Cache Annotation to enable Collection Cache, as compared to Figure 4.3.10 for SimpleJpaHibernateApp using JPA Query
Figure 4.4.4 - JConsole showing Ehcache JMX Collection Cache Statistics for ProductCode.productCollection, on SimpleJpaHibernateApp using @Cache Annotation to enable Collection Cache (Collection Cache not used in SimpleJpaHibernateApp using JPA Query)
Resulting from the above JMX statistics collected on Hibernate and Ehcache, an indicative comparison of cache and query statistics for the same use case can be tabulated, e.g. for the mentioned case of querying all the 6
Product's associated with
ProductCode where
ProdCode="SW", showing the following major observations :
Querying using entity collection causes slightly more entity loading (28 vs 29) but, at the same time, more utilisation of entity class cache and Hibernate 2nd level cache
(2ndLevelCacheHit of 2 vs 109, for the respective cases)
Querying using entity collection improves query by :
requiring more simple query (compare the different QueryExecutionMaxTimeQueryString's)
reducing time of the longest query execution (QueryExecutionMaxTime from 181 ms to 60 ms)
reducing number of queries required (QueryExecutionCount from 27 to 20)
Using JPA Query
with SQL Joint
Using Entity Collection
& Collection Cache
Hibernate JMX Statistics
Entity Load Count
28
29
2nd Level Cache Hit
2
109
2nd Level Cache Miss
1
2
2nd Level Cache Put
28
30
Query Execution Count
27
20
Query Execution Max Time (ms)
181
60
Query Execution Max Time Query String
select count(o)
from Product as o where o.productCode.prodCode
= :selectProdCode;
select object(o)
from Product as o;
Ehcache JMX Statistics
ProductCode Cache Hit / Miss 3 / 13
8 / 16
Product Cache Hit / Miss
0 / 8
48 / 8
ProductCode.productCollection Cache Hit / Miss
Not Available
6 / 2
Notes:
Relative values of QueryExecutionMaxTime may differ slightly in different testing environments and value above are just the results collected during my testings. The new structure of the SimpleJpaHibernateApp application shown as NetBeans Projects is included for reference :
Figure 4.4.5 - New Structure of SimpleJpaHibernateApp NetBeans Project
Summary
To summarise, the above shows :
ways of extending JSF-Hibernate applications to enable JMX monitoring on its Java object/relational persistence (Hibernate v3) and cache (Ehcache 1.3.0) implementations, for both Hibernate v3 and JPA-with-Hibernate applications
an example (using SimpleJpaHibernateApp) of how JMX statistics enable comparison of query execution and cache utilisation for application providing same functionalities but using different implementation strategies
Note: While only JMX statistics for Java object/relational persistence (Hibernate v3) and cache (Ehcache 1.3.0) implementations are discussed above, application-specific JMX statistics can also be gathered by building custom JMX MBeans, e.g. as shown in the NetBeans tutorial "
Getting Started with JMX Monitoring in NetBeans IDE 5.0".
Acknowledgements
Special thanks to
Greg Luck for his collaboration in the configuration of HibernateTravelPOJO and SimpleJpaHibernateApp to enable JMX monitoring on
Ehcache 1.3.0 released earlier in June 11, 2007.
Resources
Note: All the 3 application source packages can either be opened and compiled in
NetBeans 5.5 or above, or compiled by
Ant 1.6.5 or above.