Chapter 6. Persistence

Table of Contents

The persistence API
Relation to the configuration framework
Convenience methods on JbpmContext
Managed transactions
Injecting the hibernate session
Injecting resources programmatically
Advanced API usage
Configuring the persistence service
The DbPersistenceServiceFactory
The hibernate session factory
Configuring a c3po connection pool
Configuring a ehcache cache provider
Hibernate transactions
JTA transactions
Customizing queries
Database compatibility
Isolation level of the JDBC connection
Changing the jBPM DB
The jBPM DB schema
Combining your hibernate classes
Customizing the jBPM hibernate mapping files
Second level cache

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.

Figure 6.1. The transformations and different forms

The transformations and different forms

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

More information on how to deploy a process archive to the database can be found in the section called “Deploying a process archive”

The persistence API

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>

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.

Figure 6.2. The persistence related classes

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.

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:/JbpmDS

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'

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.

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();
}

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.