多租户,Hibernate 4.3,Wildfly 9,Hikaricp自动提交模式假与ejb3 JTA容器管理事务和dao

时间:2016-06-15 10:34:35

标签: hibernate jpa jboss wildfly jta

我们正在使用hibernate 4.3 multiTenancy,hikaricp连接提供程序:

  <property name="hibernate.multiTenancy" value="DATABASE"/>
  <property name="hibernate.multi_tenant_connection_provider" value="multitenancy.HikariTenantConnectionProvider"/>
  <property name="hibernate.tenant_identifier_resolver" value="multitenancy.ThreadLocalIdentifierResolver"/> 

在所有情况下,我们都希望容器使用java ee ejb标准来管理我们的事务。

问题:

自动提交

在自动提交模式下,发生以下过​​程可以说我们在dao模式voucherService中有以下代码:

@TransactionAttribute(TransactionAttributeType.MANDATORY)
private Voucher save(Voucher entity, Boolean isCreate) throws Exception {

        voucherPoolService.save(entity);

        List<VoucherTag> tags = _detachTags(entity);
        List<VoucherCaption> captions = _detachCaptions(entity);

        if (isCreate) {
          voucherDAO.save(entity);
        } else {
          voucherDAO.merge(entity);
        }

        retailerService.updateTime(entity);

        _clearCollections(entity);
        _attachTags(entity, tags);
        _attachCaptions(entity, captions);

        voucherDAO.merge(entity);

        return entity;
}

休息电话:

  @POST
  @Transactional(value = Transactional.TxType.REQUIRES_NEW, rollbackOn = {Exception.class})
  public Voucher create(Voucher entity) throws Exception {
    if (getValidator() != null) {
      getValidator().validateCreate(entity);
    }
    return getService().save(entity, true); // create
  }

如何在自动提交模式下处理:

CONTAINER - &gt; JTA交易 - &gt; JDBC

START TRANSACTION;
INSERT INTO voucher_pool ...
COMMIT;

START TRANSACTION;
INSERT INTO voucher ...
COMMIT;

START TRANSACTION;
INSERT INTO retailer ...
COMMIT;

START TRANSACTION;
INSERT INTO voucher_tags ...
COMMIT;


START TRANSACTION;
INSERT INTO voucher_tags ...
COMMIT;

问题?:

如果其中一个实体服务抛出sql异常,例如在线:

  retailerService.updateTime(entity);

JTA事务api将回滚,此时无法回滚:

START TRANSACTION;
INSERT INTO voucher_pool ...
COMMIT;

START TRANSACTION;
INSERT INTO voucher ...
COMMIT;

因为你已经在数据库中作为单独的jdbc tranasactions提交了。 如何以一种很好的方式处理这个问题?

1 个答案:

答案 0 :(得分:1)

解决方案? Autocomit false

 <property name="hibernate.connection.autocommit" value="false"/>

在提交示例dao手动提交实现时,必须明确说明每个实现有一个大问题:

public void commit() throws Exception {
    Session session = getEntityManager().unwrap(Session.class);
    Transaction tx = session.getTransaction();
    if (tx.isActive()) {
        session.doWork(new Work() {
            @Override
            public void execute(Connection connection) throws SQLException {
                connection.commit();
            }
        });
    }
}

用法:

@TransactionAttribute(TransactionAttributeType.MANDATORY)
private Voucher save(Voucher entity, Boolean isCreate) throws Exception {

        voucherPoolService.save(entity);

        List<VoucherTag> tags = _detachTags(entity);
        List<VoucherCaption> captions = _detachCaptions(entity);

        if (isCreate) {
          voucherDAO.save(entity);
        } else {
          voucherDAO.merge(entity);
        }

        retailerService.updateTime(entity);

        _clearCollections(entity);
        _attachTags(entity, tags);
        _attachCaptions(entity, captions);

        voucherDAO.merge(entity);
        // you have to explicitly say when to commit
        getDao().commit();

        return entity;
}

你会问自己为什么我不把它放在通用dao实现中? 但是你最终会遇到与自动提交模式相同的情况,你不会那样。

幸运的是,我有一个很好的解决方案,使用hibernate拦截器来处理它而不明确定义:

getDao().commit();

以下是persistance.xml的持久单元示例:

<persistence-unit name="webservices" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>Your entitys here</class>
<class>Your entitys here</class>
<properties>
    <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
    <property name="hibernate.use_outer_join" value="true"/>
    <property name="hibernate.connection.provider_class"
              value="com.zaxxer.hikari.hibernate.HikariConnectionProvider"/>
    <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
    <property name="hibernate.connection.autoReconnect" value="true"/>
    <property name="hibernate.connection.autocommit" value="false"/>
    <property name="hibernate.connection.release_mode" value="after_transaction"/>

    <property name="hibernate.ejb.use_class_enhancer" value="true"/>
    <property name="hibernate.ejb.interceptor"
              value="webservices.dao.transaction.TransactionInterceptor"/>

    <property name="hibernate.multiTenancy" value="DATABASE"/>
    <property name="hibernate.multi_tenant_connection_provider"
              value="webservices.multitenancy.HikariTenantConnectionProvider"/>
    <property name="hibernate.tenant_identifier_resolver"
              value="webservices.multitenancy.ThreadLocalIdentifierResolver"/>

    <property name="hibernate.current_session_context_class" value="thread"/>

    <property name="hibernate.transaction.jta.platform"
              value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"/>
    <property name="hibernate.transaction.auto_close_session" value="true"/>

    <property name="hibernate.hikari.maximumPoolSize" value="30"/>
    <property name="hibernate.hikari.connectionTestQuery" value="SELECT 1"/>
    <property name="hibernate.hikari.leakDetectionThreshold" value="10000"/>
    <property name="jboss.entity.manager.factory.jndi.name" value="webservicesEMF"/>
    <property name="jboss.entity.manager.jndi.name" value="webservicesEM"/>
    <property name="hibernate.hikari.idleTimeout" value="300000"/>
    <property name="hibernate.hikari.transactionIsolation" value="TRANSACTION_SERIALIZABLE"/>

</properties>

Generic dao部分:

@Stateless
public class GenericDAO<E, ID extends Serializable> extends GenericDAOImpl<E, ID> {

  private Class<E> entityClass;
  private Class<ID> idClass;

  protected static Logger log = LogManager.getLogger(GenericDAO.class);

  @PersistenceContext(name = "webservices")
  protected EntityManager em;

  private static final JPASearchProcessor searchProcessor = new JPASearchProcessor(new JPAAnnotationMetadataUtil());

  public GenericDAO() {
    super();
    entityClass = (Class<E>) DAOUtil.getTypeArguments(GenericDAOImpl.class, this.getClass()).get(0);
    init();
  }

  @PostConstruct
  protected void initialize() {
    setEntityManager(em);
    setSearchProcessor(searchProcessor);
  }    

DAORegistry部分:

@Stateless
public class DAORegistry {

   public static EntityManager getEntityManager() {
     EntityManager em = null;
     try {
        InitialContext ic = new InitialContext();
        em = (EntityManager) ic.lookup("webservicesEM");
     } catch (NamingException e) {
        e.printStackTrace();
     }
     return em;
   }

和魔法诀窍:

应用拦截器:

<property name="hibernate.ejb.interceptor" value="webservices.dao.transaction.TransactionInterceptor"/>

你的拦截器实现:

public class TransactionInterceptor extends EmptyInterceptor {

@Override
public void beforeTransactionCompletion(Transaction noTx) {
    DAORegistry.getEntityManager().unwrap(Session.class);
    Transaction tx = session.getTransaction();
    if (tx.isActive() && !tx.wasRolledBack() && !tx.wasCommitted()){
          session.doWork(Connection::commit);
    }

}

现在您不再需要手动提交实现:

 getDao().commit();

您的dao代码可以在预期的任何地方使用,具有良好的JTA CMT实现。下面的代码将像魅力一样:

@TransactionAttribute(TransactionAttributeType.MANDATORY)
private Voucher save(Voucher entity, Boolean isCreate) throws Exception {

    voucherPoolService.save(entity);

    List<VoucherTag> tags = _detachTags(entity);
    List<VoucherCaption> captions = _detachCaptions(entity);

    if (isCreate) {
        voucherDAO.save(entity);
    } else {
        voucherDAO.merge(entity);
    }

    retailerService.updateTime(entity);

    _clearCollections(entity);
    _attachTags(entity, tags);
    _attachCaptions(entity, captions);

    voucherDAO.merge(entity);

    return entity;
}

一个多星期以来,我一直在寻找一些例子,它会为我提供如此好的实现,我找不到它,所以我想分享我的学习。

现在您可以像预期的那样使用EJB3标准,

请在下面留下您的意见和建议。 :)