Opentaps Hibernate Integration

From Opentaps Wiki

Jump to: navigation, search

Beginning with opentaps version 1.4, Hibernate will be available as a persistence tool alongside with the original ofbiz entity engine. Hibernate is a more object oriented persistence framework and better suited for the new Domain Driven Architecture, while the ofbiz entity engine will continue to be supported for legacy features from both ofbiz and opentaps. In this document, we will review how the hibernate integration in opentaps works.

Contents

Working with Hibernate in opentaps

Generating Hibernate Configuration Files

After changing or defining new entities in your entitymodel.xml files, you need to generate new Java classes for your entities with

$ ant make-base-entities

This command updates the Java classes defined in org.opentaps.domain.base.entities, including their hibernate annotations, and the hibernate.cfg.xml for integrating with hibernate. (If you use a repository management system, you must commit both and you Java classes and the updated hibernate.cfg.xml files.)

Image:Error.png Important
Be sure to reference the JARs from /hot-deploy/opentaps-common/lib/hibernate in your Ant build file, or else during build it will not be able to locate the appropriate Hibernate files.

Accessing Entities with Hibernate

Image:Accept.png Tip
If you are developing in Eclipse, when using Hibernate make sure to run the ant task make-base-entities immediately after you are done defining new entities. Also make sure you refresh the Eclipse project so that it picks up the new Hibernate POJOs for code-assist.


To use hibernate to access your entities, you will need an org.opentaps.foundation.entity.hibernate.Session, which is an extension of the org.hibernate.Session class. You can obtain it from the Infrastructure class like this:

       session = infrastructure.getSession();

The Infrastructure must be created from dispatcher in order to obtain the hibernate session.

In situations where you are passing through the service engine the dispatcher is obtained from the DispatchContext

For example:

       public static Map myNewService(DispatchContext dctx, Map context) {
           LocalDispatcher dispatcher = dctx.getDispatcher();
   
           try {
               Infrastructure infrastructure = new Infrastructure(dispatcher);
               Session session = infrastructure.getSession();
           } catch (InfrastructureException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           }
       }

In situations where you are bypassing the service engine and accessing the Java method directly the dispatcher is obtained from HttpServletRequest

For example:

   public static Map myNewService(HttpServletRequest request, HttpServletResponse response) {
       LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher");
   
       try {
           Infrastructure infrastructure = new Infrastructure(dispatcher);
           Session session = infrastructure.getSession();
       } catch (InfrastructureException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       }
   }

Then, you can work with it as if it were a Hibernate Session:

       Transaction tx = session.beginTransaction();
       TestEntity newTestEntity = new TestEntity();
       newTestEntity.setTestStringField("testInsertTestEntity string field");
       newTestEntity.setCreatedStamp(UtilDateTime.nowTimestamp());
       session.save(newTestEntity);
       tx.commit();
       session.flush();
       // ...
       TestEntity loadEntity = (TestEntity) session.load(TestEntity.class, newTestEntity.getTestId());
       // ...
       String hql = "from TestEntity eo where eo.testId='" + testEntityId2 + "'";
       Query query = session.createQuery(hql);
       List<TestEntity> list = query.list()

You do not need to close your JDBC connection manually with the opentaps Session, however. When you call

           session.close();

It will do it for you automatically.

Traversing Related Entities

opentaps will automatically create the relationship annotations, such as @OneToMany, @ManyToMany, and @Join, for your Java classes.

To get related entities, use the hibernate query language (HQL):

   String hql = "from TestEntityItem eo where eo.testEntity.testId='" + testEntity.getTestId() + "'"
              + " and eo.testEntityItemSeqId in (" + testEntityItemSeqIds + ")";
   Query query = session.createQuery(hql);
   List<TestEntityItem> list = query.list();

Or use the getter methods in the base entities:

   List<TestEntityItem> list = testEntity.getTestEntityItems();

Note that hibernate will automatically load related entities for you, so the getRelated methods from Repository which were designed for the ofbiz entity engine are no longer needed when you work with hibernate.

Working with View Entities

Once a view entity has been defined in entitymodel.xml, you can access it as any other Java object from hibernate, for example:

       Query query = session.createQuery("from TestEntityAndItem eo where eo.testId='" 
                   + testEntityId + "' order by eo.testEntityItemSeqId");
       List<TestEntityAndItem> list = query.list();
       

The opentaps Session, which extends the hibernate Session, will automatically create the SQL for accessing the view entity.

Using Transactions

You should use the transaction manager configured in the ofbiz entity engine through the UserTransaction class. In the entity engine, the transaction manager is configured as:

 <transaction-factory class="org.ofbiz.geronimo.GeronimoTransactionFactory"/>

This is obtained from the ofbiz TransactionFactory by our Session and used the same way as a hibernate transaction:

     UserTransaction tx = session.beginUserTransaction();
     // do something useful
     tx.commit();

Auto Generating ID Values

ofbiz keeps track of auto generated sequence IDs in an entity called SequenceValueItem To make sure that the auto generated sequence IDs from hibernate and the ofbiz entity engine work well together, we have created an OpentapsIdentifierGenerator which also uses the same SequenceValueItem to obtain the next sequential ID. This ID generator is wired to the base entity POJO Java objects with hibernate annotations, like this:

 @org.hibernate.annotations.GenericGenerator(name="Party_GEN",  strategy="org.opentaps.foundation.entity.hibernate.OpentapsIdentifierGenerator")
 @GeneratedValue(generator="Party_GEN")   
 @Id
    
 @Column(name="PARTY_ID")
    
 private String partyId;

So that a partyId field is automatically set for you.

You can also ask the Session to generate a particular sequence ID for you:

String testEntityItemSeqId = session.getNextSeqId("TestEntityItemSeqId");

This can be helpful when you have complex keys with several fields, and you want the secondary key fields to be auto sequenced as well.

Support for OFBIZ EECA's

ofbiz EECA's are supported with custom event listeners which are registered with hibernate when Infrastructure.getSessionFactory(String delegatorName) is called, usually during the initial startup. These event listeners will run ofbiz services defined in eeca.xml's when hibernate is used to update the same entities.

Caching

After opentaps 1.4 preview 2, opentaps added support for hibernate caching using EhCache by default. opentaps supports mutual cache clearing between the ofbiz entity engine and hibernate. This means that opentaps will synchronize hibernate cache when you update your values with ofbiz entity engine, and vice versa. The hibernate cache configuration is locate in HibernateCfg.ftl (it also in hibernate.cfg.xml) as following:

  <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
  <property name="net.sf.ehcache.configurationResourceName">ehcache.xml</property> 
  <property name="hibernate.search.worker.execution">async</property>
  <property name="hibernate.cache.use_query_cache">true</property>
  <property name="hibernate.cache.use_second_level_cache">true</property>
  <property name="hibernate.search.worker.buffer_queue.max">5</property>
  <property name="hibernate.search.worker.thread_pool.size">5</property>

You can disable the caching feature by removing or commenting out these lines (both HibernateCfg.ftl and hibernate.cfg.xml).

Image:Error.png Important
The entities of current open hibernate session will not update when they are changed by ofbiz entity engine after session has already been opened, so you must open a new session to get a refreshed version of the object. In contrast, if you update a value with hibernate, it will be refreshed in the ofbiz entity engine's cache. This is due to the different way the ofbiz entity engine works versus hibernate -- the ofbiz delegator is a static object, whereas each hibernate session is a different object.

Using Hibernate without the OFBIZ Entity Engine

You can also use hibernate without the ofbiz entity engine by adding your Java objects to the template opentaps uses to generate the hibernate configuration files, hot-deploy/opentaps-common/templates/HibernateCfg.ftl. Simply put your Java objects after the

  <#list>
    ...
  </#list>

directives in the file, like in the following example:

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
  <session-factory>
  <property name="hibernate.connection.provider_class">org.opentaps.foundation.entity.hibernate.OpentapsConnectionProvider</property>  
  <property name="hibernate.transaction.factory_class">org.opentaps.foundation.entity.hibernate.OpentapsTransactionFactory</property>
  <property name="hibernate.transaction.manager_lookup_class">org.opentaps.foundation.entity.hibernate.OpentapsTransactionManagerLookup</property>  
  <property name="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</property>
<#list entities as entity>
  <mapping class="org.opentaps.domain.base.entities.${entity}"/>
</#list>
  <mapping class="org.opentaps.domain.base.entities.MyPojo1"/>
  <mapping class="org.opentaps.domain.base.entities.MyPojo2"/>
  </session-factory>
</hibernate-configuration>

opentaps will then create the hibernate configuration file for each database and include your Java objects in it. However, you would not be able to access these Java objects as generic values from the ofbiz entity engine.

Under the Hood: How It Works

Configuring Hibernate

Currently hibernate in opentaps accesses the database through the entity engine, via the OpentapsConnectionProvider to obtain database connections and OpentapsTransactionFactory/OpentapsTransactionManagerLookup for transaction management. The connection pool, transaction manager, and maximum number of connections are all obtained from the entity engine for hibernate.

Base Class Annotations

In opentaps version 1.4, the entity model XML from the ofbiz entity engine is still used as the base definition for all entities. The opentaps POJO generator is used to create base Java objects automatically from these entity definitions. This POJO generator will also create the annotations which hibernate can then use to map those base objects to the database persistence layer. The definitions of the annotations can be found in BaseEntity.ftl file used by the POJO generator.

How Hibernate Configuration Files are Generated

The POJO Generator will use hot-deploy/opentaps-common/templates/HibernateCfg.ftl to generate a base hibernate configuration file in hot-deploy/opentaps-common/config/hibernate.cfg.xml When opentaps is started, the following new container in framework/base/config/ofbiz-containers.xml

    <container name="hibernate-container" class="org.opentaps.common.container.HibernateContainer">
        <property name="delegator-name" value="default"/>
    </container>    

will generate all the hibernate configuration XML files in the hot-deploy/opentaps/config/ directory for each data source in your entity engine XML file. For example, it will generate a localmysql.cfg.xml, a localpostgres.cfg.xml

How We Get the Hibernate Session

The opentaps Infrastructure Class maintains a Map of delegatorName and hibernate SessionFactory objects. Each SessionFactory is created for its corresponding delegatorName the first time it is requested from the Infrastructure.getSessionFactory(delegatorName) method. This SessionFactory is created from ofbiz entity engine configurations:

  • The HibernateContainer in ofbiz-containers.xml has a property called delegator-name
  • From this delegator, we get the data source defined in entityengine.xml for the default group helper name. This is set to org.ofbiz by default in the Infrastructure class and is the group attribute of the entitygroup.xml definitions in ofbiz:
 <entity-group group="org.ofbiz" entity="AcctgTagEnumType"/>

In entityengine.xml, you map a data source to each group:

   <delegator name="default" entity-model-reader="main" entity-group-reader="main" entity-eca-reader="main" distributed-cache-clear-enabled="false">
       <group-map group-name="org.ofbiz" datasource-name="localmysql"/>
   </delegator>

So, we are basically following the entity engine from the delegator to the data source via the group.

  • Once we have the data source, we can create the SessionFactory from the hibernate.cfg.xml for that data source. For example, if your data source is "localmysql", we will create the SessionFactory from the localmysql.cfg.xml created by the HibernateContainer

The HibernateContainer, which loads on startup, will cause a SessionFactory to be loaded for the delegator in the delegator-name attribute. Once this SessionFactory is loaded, it will be available for future use. Additional session factories can be obtained later by calling the getSessionFactory directly.

When the Infrastructure.getSession() method is called, it will use the delegator already in the Infrastructure object to open a JDBC connection first, and then use that JDBC connection and the SessionFactory for the delegator to return a Session.

The Infrastructure.getSession() will return an org.opentaps.foundation.entity.hibernate.Session, which extends the hibernate Session with the following differences:

  • when the session is closed, the JDBC connection is also automatically closed
  • when a Query is created, this Session will check if the query is on an entity engine view entity and construct the Query from native SQL first

View Entities

View entities are supported with @NamedNativeQuery annotations in the base entity Java classes, which are automatically generated by the opentaps POJO Generator.

Encryption

The ofbiz delegator allows you to set a field to be encrypted in the database with the encrypt="true" attribute in the field tag of an entity definition. The opentaps hibernate will support the same encryption/decryption algorithm, so that encrypted values can be stored with the delegator and decrypted when it's retrieved with hibernate or vice versa. This is done by:

  1. When an object is being stored, the EcaCommEvent.beforeSave Method will come for the object to an ofbiz GenericValue and then use the ofbiz delegator to encrypt it.
  2. When an object is retrieved from the database, the Session.load will call the HibernateUtil.decryptField to decrypt it.

Transaction Management

To maintain compatibility with the ofbiz entity engine's transaction manager and connection pool providers, we have defined transaction manager look up, transaction factory, and connection provider classes which use the ofbiz transaction manager and transaction factory to begin transactions. These are defined in the hibernate.cfg.xml configuration file. They allow you to mix delegator and hibernate transaction codes as in this following example:

        UserTransaction tx = session.beginUserTransaction();
        TestEntity useIdentifierTestEntity = new TestEntity();
        useIdentifierTestEntity.setTestStringField("Use IdentifierGenerator string field");
        session.save(useIdentifierTestEntity);
        String getNextSeqIdTestEntityId = delegator.getNextSeqId("TestEntity");
        GenericValue useGetNextSeqIdTestEntity = delegator.create("TestEntity",
                UtilMisc.toMap("testId", getNextSeqIdTestEntityId,  "testStringField", "Use getNextSeqId string field"));
        session.flush();
        tx.commit();

Support for BLOBs

To make the "BLOB" type work with postgresql, we made the following changes:

  1. change blob field mapping to Java byte[]
  2. avoid using the @Lob annotation
  3. add this element in hibernate.cfg.xml
<property name="hibernate.jdbc.use_streams_for_binary">true</property>

Unit Tests

Unit tests for hibernate are found in org.opentaps.tests.entity.HibernateTests in the opentaps-tests component.

Performance

To make hibernate perform more effectively, please remember to

  1. Parametrize your queries
  2. When doing queries, do not SELECT * JOIN. Only use the columns you need.
  3. Pass values instead of entities.
  4. Parametrize timestamp values

Remember sessions are held open by views. Cache should be serializable.

Learning More

The best way to learn more about how to use hibernate with opentaps is to look through the unit tests in org.opentaps.tests.entity.HibernateTests as an example.



© Open Source Strategies, Inc. Development of this documentation site is sponsored by Open Source Strategies, Inc.
Help support opentaps with a subscription to this documentation site.