SSJ整合jBPM4.3

原文地址

There have been a few discussion threads over using jBPM4 with JPA – with or without Spring. And a few “managed to work” kind of hacks came up. For example, prior to version 4.3, there was SpringConfiguration.setSessionFactory() where you inject the SessionFactory object obtained from the JPA entity manager factory. Another solution may be, you can have two sets of configurations – one for your regular business model and another for jBPM4. And you must admit none of these are really clean solutions.


  Ideally this should same as working with Hibernate-jBPM4. You create a session factory configuration and pass the jBPM-xxx.hbm.xml files to the session factory. A sample session factory may look like this:


<hibernate-configuration>
  <session-factory>
     <property name="hibernate.dialect">...</property>
     <property name="hibernate.format_sql">true</property>
     .

     .

     .

     <mapping />

     <mapping resource="myResource.hbm.xml" />

     <mapping resource="jbpm.repository.hbm.xml" />
     <mapping resource="jbpm.execution.hbm.xml" />
     <mapping resource="jbpm.history.hbm.xml" />
     <mapping resource="jbpm.task.hbm.xml" />
     <mapping resource="jbpm.identity.hbm.xml" />
  </session-factory>
</hibernate-configuration>


    Things should be similar with JPA as well:

<persistence 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" version="1.0">
    <persistence-unit name="sample">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <mapping-file>jbpm.execution.hbm.xml</mapping-file>
        <mapping-file>jbpm.repository.hbm.xml</mapping-file>
        <mapping-file>jbpm.task.hbm.xml</mapping-file>
        <mapping-file>jbpm.history.hbm.xml</mapping-file>
        <class>org.santanu.MyDomainPojo</class>
        <class>org.santanu.AnotherMyDomainPojo</class>
        <properties>
        ...
        </properties>
    </persistence-unit>
</persistence>
  
  The assumption here is that your JPA provider is hibernate.


   So let me list the steps we need to do to achieve this:
  1. The class I loved the most when it comes to handle db access in jBPM4 is org.jbpm.pvm.internal.session.DbSession. By default we have an implementation of that interface, org.jbpm.pvm.internal.hibernate.DbSessionImpl. This implementation uses current hibernate session to do all database operations. But we want to use JPA APIs. So we need to have another implementation of DbSession that will use JPA APIs instead of Hibernate classes.
  2. Once we have the org.jbpm.pvm.internal.session.DbSession implementation we need configure jBPM to use this. So we need to replace <db-session/> with our own tag in jBPM configuration file.
  3. To support the tag in configuration we need to create binding and descriptor classes.
  4. We also need to configure entity manager instead of hibernate session. This involves creating the binding and descriptor classes.
  5. One more configuration is required for creating/accessing entity manager factory. But if the entity manager factory is created by some other component (if framework like Spring is there in our application then by all likelihood entity manager factory wil be created and managed by that) - then we may not need that. So if we assume Spring to be our container then we can give this configuration a skip.
    Ideally with these changes we should be able to use JPA instead of using Hibernate (directly). But when was the last time life has been ideal?? So there has to be some twist in the tail. org.jbpm.pvm.internal.session.DbSession appeared to be a nice way to implement "DAO" pattern (http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html).  And all other classes will go through the DbSession implementation to execute the queries. But that is not the case. There are classes which gets the Hibernate Session directly from environment and uses that. In fact there are quite a few such cases (a search in Eclipse has shown 117 direct access to hibernate session in jBPM4 code). It looks like the idea of DbSession was lost in translation. Or may be I did not get the idea correct, but whatever it is, it helps to channelize all the db operation through one such class.


    Changing 100+ classes not to use hibernate session is quite a task. And that will be a decently wide spread change on existing jBPM code. Lets minimize the impact on the code. We will have the regular org.jbpm.pvm.internal.wire.descriptor.HibernateSessionDescriptor with the only modification that it gets the session from the entity manager if we are using JPA.


    JPA Implementation of DbSession - We can have a DbSession implementation org.jbpm.pvm.internal.jpa.JpaDbSessionImpl which somewhat follows org.jbpm.pvm.internal.hibernate.DbSessionImpl and have the same set of methods implemented using Entity manager instead of Hibernate Session.

    Binding classes for JpaDbSession - We need a binding and a descriptor for JpaDbSession. This is just a part of our regular exercise to register a new configuration tag.  Both the binding and descriptor for this can be fairly simple.


public class JpaDbSessionBinding extends WireDescriptorBinding {
    public static final String TAG = "jpa-session";
    public JpaDbSessionBinding() {
          super(TAG);
    }
    public Object parse(Element element, Parse parse, Parser parser) {
         JpaDbSessionDescriptor descriptor = new JpaDbSessionDescriptor();
         return descriptor;
     }
}


public class JpaDbSessionDescriptor extends AbstractDescriptor {
    public Object construct(WireContext wireContext) {
         return new JpaDbSessionImpl();
    }

    public void initialize(Object object, WireContext wireContext) {
            EntityManager entityManager = wireContext.get(EntityManager.class);
            if (entityManager == null) {
                throw new WireException("couldn't find entity manager "
                   + (entityManagerName != null ? "'" + entityManagerName +                                                   "'" + "by type ")  + "to create db-session");
            }
            // inject the session
            ((JpaDbSessionImpl) object).setEntityManager(entityManager);
    }

    public Class<?> getType(WireDefinition wireDefinition) {
        return JpaDbSessionImpl.class;
    }
}

     The configuration we achieve here looks like this:

<jpa-session/>
    Binding classes for EntityManager -  We need to use JPA EntityManager instead of Hibernate Session. So we need to introduce another tag in the configuration file -
          <entity-manager/>
  
    This is supposed to replace hibernate session.
    But this one will be little more spicy than this. The primary reason is that there is no standard way to obtain the relevant EntityManager from the EntityManagerFactory. We miss methods like getCurrentSession() of Hibernate in JPA. We can use @PersistenceContext to get the EntityManager injected to a server managed component like Servlets or EJBs. The same technique works for Spring beans as well. So we need a Spring bean for Spring, some session bean or servlet for a JEE server managed application - in short for different environments we need different classes that can provide us the EntityManager.


    In such a scenario we can allow a class to be plugged in that can do the required lookup. Then we can evolve different possible lookup classes for different target environments and then automatically use the correct lookup class for the environment. We can focus on a Spring version of such EntityManager accessor class here.



    The interface for entity manager accessor looks like this:
package org.jbpm.pvm.internal.jpa;

import javax.persistence.EntityManager;
import org.jbpm.api.cmd.Environment;

/**
* @author Santanu
*/
public interface EntityManagerAccessor {
  /**
   * Implementing class should return the relevant entity manager.
   * @param environment
   * @return
   */
   public EntityManager getEntityManager(Environment environment);
}
    And a spring implementation of the same may be this:
package org.jbpm.pvm.internal.jpa;

import java.util.HashMap;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.jbpm.api.cmd.Environment;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;

/**
* @author Santanu
*/
public class SpringEntityManagerAccessor implements EntityManagerAccessor {
    @Override
    @SuppressWarnings("unchecked")
    public EntityManager getEntityManager(Environment environment) {
        EntityManagerFactory entityManagerFactory
                          = environment.get(EntityManagerFactory.class);
        return EntityManagerFactoryUtils
                .getTransactionalEntityManager(entityManagerFactory, new HashMap());
    }
}

   Rest of the binding and descriptor code are simple...
package org.jbpm.pvm.internal.wire.binding;

import org.jbpm.pvm.internal.wire.descriptor.JpaEntityManagerDescriptor;
import org.jbpm.pvm.internal.xml.Parse;
import org.jbpm.pvm.internal.xml.Parser;
import org.w3c.dom.Element;

/**
* @author Santanu
*/
public class JpaEntityManagerBinding extends WireDescriptorBinding {
    public static final String TAG = "entity-manager";
     
    public JpaEntityManagerBinding() {
        super(TAG);
    }

    /* (non-Javadoc)
     * @see org.jbpm.pvm.internal.xml.Binding
     *       #parse(org.w3c.dom.Element, org.jbpm.pvm.internal.xml.Parse,
     *                                   org.jbpm.pvm.internal.xml.Parser)
     */
    @Override
    public Object parse(Element element, Parse parse, Parser parser) {
        JpaEntityManagerDescriptor descriptor
                               = new JpaEntityManagerDescriptor();
        ...
        ...
        if (element.hasAttribute("accessor-class")) {
            descriptor.setProviderClassName(
                    element.getAttribute("accessor-class"));
        }
        if (element.hasAttribute("create-new")) {
            descriptor.setCreateNew(
                   Boolean.valueOf(element.getAttribute("create-new")));
        }
        ...
        return descriptor;
    }
}

package org.jbpm.pvm.internal.wire.descriptor;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.apache.commons.lang.StringUtils;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.jpa.EntityManagerAccessor;
import org.jbpm.pvm.internal.wire.WireContext;
import org.jbpm.pvm.internal.wire.WireDefinition;
import org.jbpm.pvm.internal.wire.WireException;

/**
* @author Santanu
*/
public class JpaEntityManagerDescriptor extends AbstractDescriptor {

    ...

   /* (non-Javadoc)
    * @see org.jbpm.pvm.internal.wire.Descriptor
    *         #construct(org.jbpm.pvm.internal.wire.WireContext)
    */
    @SuppressWarnings("unchecked")
    @Override
    public Object construct(WireContext wireContext) {
        EnvironmentImpl environment = EnvironmentImpl.getCurrent();
       if (environment==null) {
         throw new WireException("no environment");
       }
  
       EntityManagerFactory entityManagerFactory = null;
       if (StringUtils.isNotBlank(factoryName)) {
       entityManagerFactory
               = (EntityManagerFactory)wireContext.get(factoryName);
       } else {
        entityManagerFactory = environment.get(EntityManagerFactory.class);
       }
  
       if (entityManagerFactory == null) {
           throw new WireException("No entity manager factory found");
       }
  
       EntityManager entityManager = null;
       // here we fight to get the entity manager
       // entity manager can be obtained in multiple ways
       // it can be injected by the container
       // it can be a "resource local" entity manager, where onus is
       // on the application to manage entity manager
  
      // lets allow the user to plug in some codeto get the EntityManager
      if ((entityManager == null) && StringUtils.isNotBlank(providerClassName)) {
        try {
            Class providerClass = Class.forName(providerClassName);
            EntityManagerAccessor entityManagerProvider
                  = (EntityManagerAccessor)providerClass.newInstance();
            entityManager = entityManagerProvider.getEntityManager(environment);
        } catch (ClassNotFoundException e) {
            throw new WireException("Problem loading class " + providerClassName, e);
        } catch (InstantiationException e) {
            throw new WireException("Problem while creating object of type "
                                                   + providerClassName, e);
        } catch (IllegalAccessException e) {
            throw new WireException("Problem while creating object of type "
                                                  + providerClassName, e);
        }

        // else if we are allowed to create an entity manager and have a factory
        // somewhere in the wire context then we can easily create one
        if ((entityManager == null) && create) {
           entityManager = entityManagerFactory.createEntityManager();
        }
        ...
        ...
        return entityManager;
    }

    ...
    ...
    public Class<?> getType(WireDefinition wireDefinition) {
        return EntityManager.class;
    }
}


    The last thing that is pending is the patch-up code we need to make the code dependent directly on hibernate session to work. We need to make sure that if the session is looked up we return the delegating session from the entity manager. For this we need a little change in the existing HibernateSessionDescriptor ans HibernateSessionBinding.We introduce a boolean to specify whether this is running in a JPA environment.

public class HibernateSessionBinding extends WireDescriptorBinding {

          public HibernateSessionBinding() {
             super("hibernate-session");
          }

          public Object parse(Element element, Parse parse, Parser parser) {
              HibernateSessionDescriptor descriptor = new HibernateSessionDescriptor();

              if (element.hasAttribute("jpa")) {
                  Boolean isJpa = XmlUtil.attributeBoolean(element, "jpa", false, parse);
                  descriptor.setJpa(isJpa);
                  //if its jpa then we need this much
                  if (isJpa) {
                       return descriptor;
                  }
              }
              ...
              ...
           }
          ...
          ...
      }
        
      public class HibernateSessionDescriptor extends AbstractDescriptor {
          ...
          ...
          //now we need some more if this class works to satisfy hibernate
          //session seekers
          //in a JPA environment - the strategy works only if the JPA
          //provider is hibernate
          protected boolean jpa = false;
                   
          public Object construct(WireContext wireContext) {
               ...
               if (jpa) {
                   return getSessionFromEntityManager(environment);
               }
               ....
               ....
          }

          private Session getSessionFromEntityManager(EnvironmentImpl environment) {
               EntityManager entityManager = environment.get(EntityManager.class);
               Session session = (Session)entityManager.getDelegate();
               return session;
          }
          ...
          ...
       }

    Putting these all together, the jBPM configuration with these changes look like this (only the modified parts are depicted here):

<transaction-context>
     ...
     <!--db-session/-->
     <jpa-session/>
     ...
     ...
     <transaction type="spring" />
     <entity-manager provider-class="org.jbpm.pvm.internal.jpa.SpringEntityManagerProvider"/>
     <hibernate-session jpa="true"/>
     ...
</transaction-context>

       
    I believe JPA wil be introduced in jBPM5. I just hope the point of smooth gel with hibernate in the process of moving to JPA is not ignored. Hibernate is stil and probably will by far remain the most popular and most mature ORM implementation in Java.

源代码提供

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值