Opentaps Hibernate Integration
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.
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.)
![]() |
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
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
Our caching system now support ofbiz <-> hibernate mutual cache clearing. It mean it will sync hibernate cache on you update GenericValue 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.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 remove these lines (both HibernateCfg.ftl and hibernate.cfg.xml).
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
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:
- 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.
- 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:
- change blob field mapping to Java byte[]
- avoid using the @Lob annotation
- 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.
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.