Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 3 - Enabling JMX Monitoring on Hibernate v3 & Ehcache 1.3.0
Background
This is the continuation from the previous article "Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 2 - Enabling JMX Monitoring on Hibernate v3 and Ehcache 1.3, on HibernateTutorialApp" where we continue with (3) and (4) of the following :
- Configuring HibernateTutorialApp/HibernateTravelPOJO to use Ehcache 1.3.0
- Configuring HibernateTutorialApp/HibernateTravelPOJO to enable JMX monitoring on Hibernate and Ehcache
- Configuring SimpleJpaHibernateApp to use Ehcache 1.3.0
- Configuring SimpleJpaHibernateApp to enable JMX monitoring on Hibernate and Ehcache
(3) Configuring SimpleJpaHibernateApp to use Ehcache 1.3.0
The configurations for SimpleJpaHibernateApp, created with the NetBeans tutorial "Using Hibernate With the Java Persistence API", to use Ehcache 1.3.0 are similar to those of "Configuring HibernateTutorialApp/HibernateTravelPOJO to use Ehcache 1.3.0" , except :
- SimpleJpaHibernateApp is directly configured to use Hibernate (Hibernate-3.2.2, or above, e.g. Hibernate-3.2.4sp1) and Ehcache-1.3.0
- persistence.xml and hibernate.cfg.xml (cache related configurations highlighted in bold), and ehcache.cfg.xml as shown :
| Code Listing 3.1 - persistence.xml for SimpleJpaHibernateApp |
| <?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="SimpleJpaHibernateAppPU" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>jdbc/sample</jta-data-source> <class>simpleJpaHibernateApp.entities.ProductCode</class> <class>simpleJpaHibernateApp.entities.Product</class> <class>simpleJpaHibernateApp.entities.Manufacturer</class> <properties> <property name= "hibernate.ejb.classcache.simpleJpaHibernateApp.entities.ProductCode" value="read-write"/> <property name= "hibernate.ejb.classcache.simpleJpaHibernateApp.entities.Product" value="read-write"/> <property name= "hibernate.ejb.classcache.simpleJpaHibernateApp.entities.Manufacturer" value="read-write"/> <property name="hibernate.ejb.cfgfile" value="/hibernate.cfg.xml"/> </properties> </persistence-unit> </persistence> |
| Code Listing 3.2 - hibernate.cfg.xml for SimpleJpaHibernateApp |
| <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- SQL dialect --> <property name="hibernate.dialect"> org.hibernate.dialect.DerbyDialect</property> <!-- Debug logging of SQL statements --> <property name="hibernate.show_sql">true</property> <!-- Cache Configurations --> <!-- Using net.sf.ehcache.hibernate.SingletonEhCacheProvider instead of net.sf.ehcache.hibernate.EhCacheProvider ensures the same instance of CacheManager is referred to by both Hibernate and our JMX Agent simpleJpaHibernateApp.agents.jmxAgent. (Thanks to Greg Luck!) --> <property name="hibernate.cache.provider_class"> net.sf.ehcache.hibernate.SingletonEhCacheProvider</property> <property name="hibernate.cache.provider_configuration"> /ehcache.cfg.xml</property> <property name="hibernate.cache.use_minimal_puts">false</property> <property name="hibernate.cache.use_query_cache">true</property> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.use_structured_entries">true</property> </session-factory> </hibernate-configuration> |
| Coding Listing 3.3 - ehcache.cfg.xml for SimpleJpaHibernateApp |
| <?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <cache name="simpleJpaHibernateApp.entities.ProductCode" maxElementsInMemory="300" eternal="true" overflowToDisk="false" /> <cache name="simpleJpaHibernateApp.entities.Product" maxElementsInMemory="300" eternal="true" overflowToDisk="false" /> <cache name="simpleJpaHibernateApp.entities.Manufacturer" maxElementsInMemory="300" eternal="true" overflowToDisk="false" /> <cache name="simpleJpaHibernateApp.entities.ProductCode.productCollection" maxElementsInMemory="300" eternal="true" overflowToDisk="false" /> <cache name="simpleJpaHibernateApp.entities.Manufacturer.productCollection" maxElementsInMemory="300" eternal="true" overflowToDisk="false" /> </ehcache> |
(4) Configuring SimpleJpaHibernateApp to enable JMX monitoring on Hibernate and Ehcache
After being configured to use Ehcache 1.3.0 as shown in "Configuring SimpleJpaHibernateApp to use Ehcache 1.3.0" above, the SimpleJpaHibernateApp can also be extended to enable JMX monitoring, e.g. on Hibernate and Ehcache used, similar to "Configuring HibernateTutorialApp/HibernateTravelPOJO to enable JMX monitoring on Hibernate and Ehcache" in previous article.
Step 4.1 - Create JMX Agent with Hibernate and Ehcache MBeans Registration Codes
Similar to Step 2.1 in "Configuring HibernateTutorialApp/HibernateTravelPOJO to enable JMX monitoring on Hibernate and Ehcache", we need to create the JMX Agent which registers the Hibernate and Ehcache MBeans to enable JMX monitoring on them. However, there are a few small but important differences here...
(A) To register Hibernate's MBean, we still need the following codes :
// Enable Hibernate JMX Statistics
StatisticsService statsMBean = new StatisticsService();
statsMBean.setSessionFactory(sessionFactory);
statsMBean.setStatisticsEnabled(true);
mbs.registerMBean(statsMBean, on);
i.e. we need to get Hibernate SessionFactory from our JPA execution environment (which uses the implementation-independent JPA PersistentUnit and the associated EntityMangerFactory instead) to enable JMX monitoring on Hibernate, by (also used by the NetBeans Tutorial "NetBeans Wiki - UsingHibernateWithJPA") :
- Session session = (Session) em.getDelegate();
And, in our case for SimpleJpaHibernateApp, it is followed by :
- SessionFactory sessionFactory = session.getSessionFactory();
to get SessionFactory as in Code Listing 4.1 (additional MBeans registration codes in bold) below.
(B) Also, we would like to modify the init() method to take EntityMangerFactory from the calling context, to get the Hibernate SessionFactory for registering Hibernate statsMBean.
| Code Listing 4.1 - SimpleJpaHibernateApp's JmxAgent.java |
| /* * JmxAgent.java * */ package simpleJpaHibernateApp.agents; import java.lang.management.ManagementFactory; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManager; import net.sf.ehcache.CacheManager; import net.sf.ehcache.management.ManagementService; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.jmx.StatisticsService; /** * JMX agent (singleton) for monitoring Hibernate and Ehcache * in SimpleJpaHibernateApp, which uses: * <ul> * <li>JavaServer Faces (JSF) web-tier</li> * <li>Java Persistence API (JPA) persistence</li> * <li>Hibernate Core (3.2.4 sp1) and Hibernate EntityManager (3.3.1)</li> * <li>Ehcache 1.3.0</li> * </ul> * * @author Max Poon (maxpoon@dev.java.net) */ public class JmxAgent { private EntityManager em; private Session session; private SessionFactory sf; /** * Instantiate, register MBeans, enable Hibernate & Ehcache JMX Statistics * @param emf javax.persistence.EntityManagerFactory to be passed in * from the invoking context (instead of creating it here * which is expensive operation) */ public void init(EntityManagerFactory emf) throws Exception { try { // Create EntityManager from EntityManagerFactory passed in // from the invoking context em = emf.createEntityManager(); // Get Hibernate Session and SessionFactory from EntityManager // *Important* for registering the Hibernate SessionFactory // with org.hibernate.jmx.StatisticsService later on // to enable JMX monitoring of Hibernate statistics session = (Session) em.getDelegate(); sf = session.getSessionFactory(); } catch (Exception ex) { ex.printStackTrace(); } finally { em.close(); } ObjectName on = new ObjectName ("Hibernate:type=statistics,application=SimpleJpaHibernateApp"); // Enable Hibernate JMX Statistics StatisticsService statsMBean = new StatisticsService(); statsMBean.setSessionFactory(sf); statsMBean.setStatisticsEnabled(true); mbs.registerMBean(statsMBean, on); /* * Enable Ehcache JMX Statistics * Use CacheManager.getInstance() instead of new CacheManager() * as net.sf.ehcache.hibernate.SingletonEhCacheProvider is used * to ensure reference to the same CacheManager instance as used * by Hibernate */ CacheManager cacheMgr = CacheManager.getInstance(); ManagementService.registerMBeans (cacheMgr, mbs, true, true, true, true); } /** * Returns an agent singleton. */ public synchronized static JmxAgent getDefault(EntityManagerFactory emf) throws Exception { if(singleton == null) { singleton = new JmxAgent(); singleton.init(emf); } return singleton; } public MBeanServer getMBeanServer() { return mbs; } // Platform MBeanServer used to register your MBeans private final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // Singleton instance private static JmxAgent singleton; } |
Step 4.2 Modify JSF Managed Beans to initiate JMX Agent
Unlike the HibernateTravelPOJO application, there is no HibernateUtil.java-like object for getting Hibernate Session in SimpleJpaHibernateApp. One solution is to initiate JmxAgent during the 1st retrieval of EntityManager in following JSF managed beans / controllers :
- simpleJpaHibernate.controllers.ProductController
- simpleJpaHibernate.controllers.ProductCodeController
- simpleJpaHibernate.controllers.ManufacturerController
e.g. as shown in Code Listing 4.2 for simpleJpaHibernate.controllers.ProductController :
| Code Listing 4.2 - ProductController.java modified to initiate JmxAgent if needed |
| /* * ProductController.java * */ package simpleJpaHibernateApp.controller; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Resource; .... import simpleJpaHibernateApp.agents.JmxAgent; import simpleJpaHibernateApp.entities.Manufacturer; import simpleJpaHibernateApp.entities.Product; import simpleJpaHibernateApp.entities.ProductCode; /** * 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 { /** Creates a new instance of ProductController */ public ProductController() { } private Product product; private ProductCode selectedProductCode; private String selectedProdCode; private DataModel model; @Resource private UserTransaction utx; @PersistenceUnit(unitName = "SimpleJpaHibernateAppPU") private EntityManagerFactory emf; private EntityManager getEntityManager() { initJMX(); return emf.createEntityManager(); } // Initiate JMX if needed void initJMX() { try { JmxAgent.getDefault(emf); } catch (Exception ex) { ex.printStackTrace(); } } ... ... |
Similar modifications should also be done for :
- simpleJpaHibernate.controllers.ProductCodeController
- simpleJpaHibernate.controllers.ManufacturerController
Then recompile SimpleJpaHibernateApp with the above configurations and checked that it is working fine getting also the web interface and functionalities as those 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".
Step 4.3 - Use JConsole to Observe JMX Statistics
Similar to Step 2.3 in "Configuring HibernateTutorialApp/HibernateTravelPOJO to enable JMX monitoring on Hibernate and Ehcache", start JConsole and connect to JVM running GlassFish (indicated by "com.sun.enterprise.server.PELaunch" in JConsole) and observe the following real-time JMX information collected.
Starting with the "Listing ProductCodes" page :
| Figure 4.3.1 - "Listing ProductCodes" page of SimpleJpaHibernateApp |

| Figure 4.3.2 - JConsole showing Hibernate Entity Names, Queries, 2nd Level Cache Regions, and related statistics |
| Figure 4.3.3 - JConsole showing Ehcache Statistics on ProductCode, with 6 cache miss for the 6 ProductCode instances retrieved |
Click on "SW" page on "Listing ProductCodes" page to get the following, then click on "List of Product with this Product Code" to get the next screen shown in Figure 4.3.7.
| Figure 4.3.4 - "Detail of ProductCode" page of SimpleJpaHibernateApp for ProdCode="SW" |

| Figure 4.3.5 - JConsole showing Hibernate Statistics being updated as compared to Figure 4.3.2 |
| Figure 4.3.6 - JConsole showing Ehcache Statistics for ProductCode updated (with 2 more cache misses and 7 more cache hits) as compared to Figure 4.3.3 |
| Figure 4.3.7 - "Listing Products" page of SimpleJpaHibernateApp for ProdCode="SW" |

| Figure 4.3.8 - JConsole showing Hibernate Statistics being updated as compared to Figure 4.3.5 |
| Figure 4.3.9 - JConsole showing Ehcache Statistics for ProductCode updated, with 1 more (ProductCode where ProdCode="SW") cache hit as compared to Figure 4.3.6 |
| Figure 4.3.10 - JConsole showing Ehcache Statistics for Product, with 8 new retrieval, i.e. cache misses, for the 8 Product instances where ProdCode="SW" as shown in Figure 4.3.7 |
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 |
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
- Hibernate v3 Reference Chapter 19 : Improving Performance
- Speed Up Your Hibernate Applications with Second-Level Caching
- Hibernate : Publishing statistics through JMX
- Gathering Performance Metrics for Hibernate
- Ehcache User Guide : JMX Management and Monitoring
- Zip archive of the resulting extended "HibernateTravelPOJO" and "HibernateTutorialApp" NetBeans projects / source packages
- Zip archive of the resulting extended "SimpleJpaHibernateApp" NetBeans project / source package
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.
- Printer-friendly version
- maxpoon's blog
- 4175 reads





