Chapter 7. Persistence

In most scenarios, jBPM is used to maintain execution of processes that span a long time. In this context, "a long time" means spanning several transactions. The main purpose of persistence is to store process executions during wait states. So think of the process executions as state machines. In one transaction, we want to move the process execution state machine from one state to the next.

A process definition can be represented in 3 different forms : as xml, as java objects and as records in the jBPM database. Executional (=runtime) information and logging information can be represented in 2 forms : as java objects and as records in the jBPM database.

The transformations and different forms

Figure 7.1. The transformations and different forms

For more information about the xml representation of process definitions and process archives, see Chapter 21, jBPM Process Definition Language (JPDL).

More information on how to deploy a process archive to the database can be found in Section 21.1.1, “Deploying a process archive”

7.1. The persistence API

7.1.1. Relation to the configuration framework

The persistence API is an integrated with the configuration framework by exposing some convenience persistence methods on the JbpmContext. Persistence API operations can therefore be called inside a jBPM context block like this:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

  // Invoke persistence operations here

} finally {
  jbpmContext.close();
}

In what follows, we suppose that the configuration includes a persistence service similar to this one (as in the example configuration file src/config.files/jbpm.cfg.xml):

<jbpm-configuration>

  <jbpm-context>
    <service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
    ...
  </jbpm-context>
  ...
</jbpm-configuration>

7.1.2. Convenience methods on JbpmContext

The three most common persistence operations are:

  • Deploying a process
  • Starting a new execution of a process
  • Continuing an execution

First deploying a process definition. Typically, this will be done directly from the graphical process designer or from the deployprocess ant task. But here you can see how this is done programmatically:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
  ProcessDefinition processDefinition = ...;
  jbpmContext.deployProcessDefinition(processDefinition);
} finally {
  jbpmContext.close();
}

For the creation of a new process execution, we need to specify of which process definition this execution will be an instance. The most common way to specify this is to refer to the name of the process and let jBPM find the latest version of that process in the database:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
  String processName = ...;
  ProcessInstance processInstance = 
      jbpmContext.newProcessInstance(processName);
} finally {
  jbpmContext.close();
}

For continuing a process execution, we need to fetch the process instance, the token or the taskInstance from the database, invoke some methods on the POJO jBPM objects and afterwards save the updates made to the processInstance into the database again.

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
  long processInstanceId = ...;
  ProcessInstance processInstance = 
      jbpmContext.loadProcessInstance(processInstanceId);
  processInstance.signal();
  jbpmContext.save(processInstance);
} finally {
  jbpmContext.close();
}

Note that if you use the xxxForUpdate methods in the JbpmContext, an explicit invocation of the jbpmContext.save is not necessary any more because it will then occur automatically during the close of the jbpmContext. E.g. suppose we want to inform jBPM about a taskInstance that has been completed. Note that task instance completion can trigger execution to continue so the processInstance related to the taskInstance must be saved. The most convenient way to do this is to use the loadTaskInstanceForUpdate method:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
  long taskInstanceId = ...;
  TaskInstance taskInstance = 
      jbpmContext.loadTaskInstanceForUpdate(taskInstanceId);
  taskInstance.end();
} finally {
  jbpmContext.close();
}

Just as background information, the next part is an explanation of how jBPM manages the persistence and uses hibernate.

The JbpmConfiguration maintains a set of ServiceFactorys. The service factories are configured in the jbpm.cfg.xml as shown above and instantiated lazy. The DbPersistenceServiceFactory is only instantiated the first time when it is needed. After that, service factories are maintained in the JbpmConfiguration. A DbPersistenceServiceFactory manages a hibernate SessionFactory. But also the hibernate session factory is created lazy when requested the first time.

The persistence related classes

Figure 7.2. The persistence related classes

During the invocation of jbpmConfiguration.createJbpmContext(), only the JbpmContext is created. No further persistence related initializations are done at that time. The JbpmContext manages a DbPersistenceService, which is instantiated upon first request. The DbPersistenceService manages the hibernate session. Also the hibernate session inside the DbPersistenceService is created lazy. As a result, a hibernate session will be only be opened when the first operation is invoked that requires persistence and not earlier.

7.1.3. Managed transactions

The most common scenario for managed transactions is when using jBPM in a JEE application server like JBoss. The most common scenario is the following:

  • Configure a DataSource in your application server
  • Configure hibernate to use that data source for its connections
  • Use container managed transactions
  • Disable transactions in jBPM

A stateless session facade in front of jBPM is a good practice. The easiest way on how to bind the jbpm transaction to the container transaction is to make sure that the hibernate configuration used by jbpm refers to an xa-datasource. So jbpm will have its own hibernate session, there will only be 1 jdbc connection and 1 transaction.

The transaction attribute of the jbpm session facade methods should be 'required'

The the most important configuration property to specify in the hibernate.cfg.xml that is used by jbpm is:

hibernate.connection.datasource=  --datasource JNDI name-- like e.g. java:/DefaultDS

More information on how to configure jdbc connections in hibernate, see the hibernate reference manual, section 'Hibernate provided JDBC connections'

For more information on how to configure xa datasources in jboss, see the jboss application server guide, section 'Configuring JDBC DataSources'

7.1.4. Injecting the hibernate session

In some scenarios, you already have a hibernate session and you want to combine all the persistence work from jBPM into that hibernate session.

Then the first thing to do is make sure that the hibernate configuration is aware of all the jBPM mapping files. You should make sure that all the hibernate mapping files that are referenced in the file src/config.files/hibernate.cfg.xml are provided in the used hibernate configuration.

Then, you can inject a hibernate session into the jBPM context as is shown in the following API snippet:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
  jbpmContext.setSession(SessionFactory.getCurrentSession());

  // your jBPM operations on jbpmContext

} finally {
  jbpmContext.close();
}

That will pass in the current hibernate session used by the container to the jBPM context. No hibernate transaction is initiated when a session is injected in the context. So this can be used with the default configurations.

The hibernate session that is passed in, will not be closed in the jbpmContext.close() method. This is in line with the overall philosophy of programmatic injection which is explained in the next section.

7.1.5. Injecting resources programmatically

The configuration of jBPM provides the necessary information for jBPM to create a hibernate session factory, hibernate session, jdbc connections, jbpm required services,... But all of these resources can also be provided to jBPM programmatically. Just inject them in the jbpmContext. Injected resources always are taken before creating resources from the jbpm configuration information.

The main philosophy is that the API-user remains responsible for all the things that the user injects programmatically in the jbpmContext. On the other hand, all items that are opened by jBPM, will be closed by jBPM. There is one exception. That is when fetching a connection that was created by hibernate. When calling jbpmContext.getConnection(), this transfers responsibility for closing the connection from jBPM to the API user.

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
  // to inject resources in the jbpmContext before they are used, you can use
  jbpmContext.setConnection(connection);
  // or
  jbpmContext.setSession(session);
  // or
  jbpmContext.setSessionFactory(sessionFactory);

} finally {
  jbpmContext.close();
}

7.1.6. Advanced API usage

The DbPersistenceService maintains a lazy initialized hibernate session. All database access is done through this hibernate session. All queries and updates done by jBPM are exposed by the XxxSession classes like e.g. GraphSession, SchedulerSession, LoggingSession,... These session classes refer to the hibernate queries and all use the same hibernate session underneath.

The XxxxSession classes are accessible via the JbpmContext as well.

7.2. Configuring the persistence service

7.2.1. The DbPersistenceServiceFactory

The DbPersistenceServiceFactory itself has 3 more configuration properties: isTransactionEnabled, sessionFactoryJndiName and dataSourceJndiName. To specify any of these properties in the jbpm.cfg.xml, you need to specify the service factory as a bean in the factory element like this:

IMPORTANT: don't mix the short and long notation for configuring the factories. See also Section 6.1, “Customizing factories”. If the factory is just a new instance of a class, you can use the factory attribute to refer to the factory class name. But if properties in a factory must be configured, the long notation must be used and factory and bean must be combined as nested elements. Like this:

  <jbpm-context>
    <service name="persistence">
      <factory>
        <bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
          <field name="isTransactionEnabled"><false /></field>
          <field name="sessionFactoryJndiName">
            <string value="java:/myHibSessFactJndiName" />
          </field>
          <field name="dataSourceJndiName">
            <string value="java:/myDataSourceJndiName" />
          </field>
        </bean>
      </factory>
    </service>
    ...
  </jbpm-context>
  • isTransactionEnabled: by default, jBPM will begin a hibernate transaction when the session is fetched the first time and if the jbpmContext is closed, the hibernate transaction will be ended. The transaction is then committed or rolled back depending on wether jbpmContext.setRollbackOnly was called. The isRollbackOnly property is maintained in the TxService. To disable transactions and prohibit jBPM from managing transactions with hibernate, configure the isTransactionEnabled property to false as in the example above. This property only controls the behaviour of the jbpmContext, you can still call the DbPersistenceService.beginTransaction() directly with the API, which ignores the isTransactionEnabled setting. For more info about transactions, see Section 7.3, “Hibernate transactions”.
  • sessionFactoryJndiName: by default, this is null, meaning that the session factory is not fetched from JNDI. If set and a session factory is needed to create a hibernate session, the session factory will be fetched from jndi using the provided JNDI name.
  • dataSourceJndiName: by default, this is null and creation of JDBC connections will be delegated to hibernate. By specifying a datasource, jBPM will fetch a JDBC connection from the datasource and provide that to hibernate while opening a new session. For user provided JDBC connections, see ???.

7.2.2. The hibernate session factory

By default, the DbPersistenceServiceFactory will use the resource hibernate.cfg.xml in the root of the classpath to create the hibernate session factory. Note that the hibernate configuration file resource is mapped in the property 'jbpm.hibernate.cfg.xml' and can be customized in the jbpm.cfg.xml. This is the default configuration:

<jbpm-configuration>
  ...
  <!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
  <string name='resource.hibernate.cfg.xml' 
          value='hibernate.cfg.xml' />
  <!-- <string name='resource.hibernate.properties' 
                  value='hibernate.properties' /> -->
  ...
</jbpm-configuration>

When the property resource.hibernate.properties is specified, the properties in that resource file will overwrite all the properties in the hibernate.cfg.xml. Instead of updating the hibernate.cfg.xml to point to your DB, the hibernate.properties can be used to handle jbpm upgrades conveniently: The hibernate.cfg.xml can then be copied without having to reapply the changes.

7.2.3. Configuring a c3po connection pool

Please refer to the hibernate documentation: http://www.hibernate.org/214.html

7.2.4. Configuring a ehcache cache provider

If you want to configure jBPM with JBossCache, have a look at the jBPM configuration wiki page

For more information about configuring a cache provider in hibernate, take a look at the hibernate documentation, section 'Second level cache'

The hibernate.cfg.xml that ships with jBPM includes the following line:

<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>

This is done to get people up and running as fast as possible without having to worrie about classpaths. Note that hibernate contains a warning that states not to use the HashtableCacheProvider in production.

To use ehcache instead of the HashtableCacheProvider, simply remove that line and put ehcache.jar on the classpath. Note that you might have to search for the right ehcache library version that is compatible with your environmment. Previous incompatibilities between a jboss version and a perticular ehcache version were the reason to change the default to HashtableCacheProvider.

7.3. Hibernate transactions

By default, jBPM will delegate transaction to hibernate and use the session per transaction pattern. jBPM will begin a hibernate transaction when a hibernate session is opened. This will happen the first time when a persistent operation is invoked on the jbpmContext. The transaction will be committed right before the hibernate session is closed. That will happen inside the jbpmContext.close().

Use jbpmContext.setRollbackOnly() to mark a transaction for rollback. In that case, the transaction will be rolled back right before the session is closed inside of the jbpmContext.close().

To prohibit jBPM from invoking any of the transaction methods on the hibernate API, set the isTransactionEnabled property to false as explained in Section 7.2.1, “The DbPersistenceServiceFactory” above.

7.4. JTA transactions

The most common scenario for managed transactions is when using jBPM in a JEE application server like JBoss. The most common scenario to bind your transactions to JTA is the following:

  <jbpm-context>
    <service name="persistence">
      <factory>
        <bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
          <field name="isTransactionEnabled"><false /></field>
          <field name="isCurrentSessionEnabled"><true /></field>
          <field name="sessionFactoryJndiName">
            <string value="java:/myHibSessFactJndiName" />
          </field>
        </bean>
      </factory>
    </service>
    ...
  </jbpm-context>

Then you should specify in your hibernate session factory to use a datasource and bind hibernate to the transaction manager. Make sure that you bind the datasource to an XA datasource in case you're using more then 1 resource. For more information about binding hibernate to your transaction manager, please, refer to paragraph 'Transaction strategy configuration' in the hibernate documentation.

<hibernate-configuration>
  <session-factory>

    <!-- hibernate dialect -->
    <property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>

    <!-- DataSource properties (begin) -->
    <property name="hibernate.connection.datasource">java:/JbpmDS</property>

    <!-- JTA transaction properties (begin) -->
    <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
    <property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>
    <property name="jta.UserTransaction">java:comp/UserTransaction</property>

    ...
  </session-factory>
</hibernate-configuration>

Then make sure that you have configured hibernate to use an XA datasource.

These configurations allow for the enterprise beans to use CMT and still allow the web console to use BMT. That is why the property 'jta.UserTransaction' is also specified.

7.5. Customizing queries

All the HQL queries that jBPM uses are centralized in one configuration file. That resource file is referenced in the hibernate.cfg.xml configuration file like this:

<hibernate-configuration>
  ...
    <!-- hql queries and type defs -->
    <mapping resource="org/jbpm/db/hibernate.queries.hbm.xml" />
  ...
</hibernate-configuration>

To customize one or more of those queries, take a copy of the original file and put your customized version somewhere on the classpath. Then update the reference 'org/jbpm/db/hibernate.queries.hbm.xml' in the hibernate.cfg.xml to point to your customized version.

7.6. Database compatibility

jBPM runs on any database that is supported by hibernate.

The example configuration files in jBPM (src/config.files) specify the use of the hypersonic in-memory database. That database is ideal during development and for testing. The hypersonic in-memory database keeps all its data in memory and doesn't store it on disk.

7.6.1. Isolation level of the JDBC connection

Make sure that the database isolation level that you configure for your JDBC connection is at least READ_COMMITTED.

Almost all features run OK even with READ_UNCOMMITTED (isolation level 0 and the only isolation level supported by HSQLDB). But race conditions might occur in the job executor and with synchronizing multiple tokens.

7.6.2. Changing the jBPM DB

Following is an indicative list of things to do when changing jBPM to use a different database:

  • put the jdbc-driver library archive in the classpath
  • update the hibernate configuration used by jBPM
  • create the schema in the new database

7.6.3. The jBPM DB schema

The jbpm.db subproject, contains a number of drivers, instructions and scripts to help you getting started on your database of choice. Please, refer to the readme.html in the root of the jbpm.db project for more information.

While jBPM is capable of generating DDL scripts for all database, these schemas are not always optimized. So you might want to have your DBA review the DDL that is generated to optimize the column types and use of indexes.

In development you might be interested in the following hibernate configuration: If you set hibernate configuration property 'hibernate.hbm2ddl.auto' to 'create-drop' (e.g. in the hibernate.cfg.xml), the schema will be automatically created in the database the first time it is used in an application. When the application closes down, the schema will be dropped.

The schema generation can also be invoked programmatically with jbpmConfiguration.createSchema() and jbpmConfiguration.dropSchema().

7.6.4. Known Issues

This section highlights the known-issues in databases that have been tested with jBPM.

7.6.4.1. Sybase Issues

Some Sybase distributions have a known issue with truncating binary files resulting in misbehavior of the system. This limitation is resulting from the storage mechanism of binaries into the Sybase database.

7.7. Combining your hibernate classes

In your project, you might use hibernate for your persistence. Combining your persistent classes with the jBPM persistent classes is optional. There are two major benefits when combining your hibernate persistence with jBPM's hibernate persistence:

First, session, connection and transaction management become easier. By combining jBPM and your persistence into one hibernate session factory, there is one hibernate session, one jdbc connection that handles both yours and jBPM's persistence. So automatically the jBPM updates are in the same transaction as the updates to your own domain model. This can eliminates the need for using a transaction manager.

Secondly, this enable you to drop your hibernatable persistent object in to the process variables without any further hassle.

The easiest way to integrate your persistent classes with the jBPM persistent classes is by creating one central hibernate.cfg.xml. You can take the jBPM src/config.files/hibernate.cfg.xml as a starting point and add references to your own hibernate mapping files in there.

7.8. Customizing the jBPM hibernate mapping files

To customize any of the jBPM hibernate mapping files, you can proceed as follows:

  • copy the jBPM hibernate mapping file(s) you want to copy from the sources (src/java.jbpm/...) or from inside of the jbpm jar.
  • put the copy anywhere you want on the classpath
  • update the references to the customized mapping files in the hibernate.cfg.xml configuration file

7.9. Second level cache

jBPM uses hibernate's second level cache for keeping the process definitions in memory after loading them once. The process definition classes and collections are configured in the jBPM hibernate mapping files with the cache element like this:

<cache usage="nonstrict-read-write"/>

Since process definitions (should) never change, it is ok to keep them in the second level cache. See also Section 21.1.3, “Changing deployed process definitions”.

The second level cache is an important aspect of the JBoss jBPM implementation. If it weren't for this cache, JBoss jBPM could have a serious drawback in comparison to the other techniques to implement a BPM engine.

The caching strategy is set to nonstrict-read-write. At runtime, the caching strategy could be set to read-only. But in that case, you would need a separate set of hibernate mapping files for deploying a process. That is why we opted for the nonstrict-read-write.