Skip to content

Instantly share code, notes, and snippets.

@igorzg
Last active January 16, 2017 13:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save igorzg/5f98bdec71876c6b0692c64f0eced6a7 to your computer and use it in GitHub Desktop.
Save igorzg/5f98bdec71876c6b0692c64f0eced6a7 to your computer and use it in GitHub Desktop.
Multitenancy, Hibernate 4.3, Wildfly 9, Hikaricp autocommit mode false with ejb3 JTA container managed transactions and dao

We are using hibernate 4.3 multiTenancy, hikaricp connection provider:

  <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"/> 

In all cases we want that container is managing our transactions using java ee ejb standard.

PROBLEMS:

Autocommit

In auto commit mode following process happen lets say that we have following code in dao pattern 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;
}

Rest call:

  @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
  }

How this will be processed in autocommit mode:

CONTAINER -> JTA transaction -> 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;

Problems ?:

If one of entity service throws sql exception for example on line:

  retailerService.updateTime(entity);

JTA transaction api will rollback, at that point there is no way to rollback :

START TRANSACTION;
INSERT INTO voucher_pool ...
COMMIT;

START TRANSACTION;
INSERT INTO voucher ...
COMMIT;

Because thy are already committed in database as separate jdbc tranasactions. How to handle this in a nice way ?

Solutions ? Autocomit false

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

There is one big problem with it you have to explicitly say on each implementation when to commit example dao manual commit implementation:

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

Usage of it:

@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;
}

You would ask your self why i don't put it in generic dao implementation ? But you will end up in same situation as autocommit mode and you don't wan't that.

Luckily i have nice solution using hibernate interceptors which will handle that without explicitly defining a:

getDao().commit();

Here is persistance-unit example of your 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="hibernate.hikari.idleTimeout" value="300000"/>
    
    <property name="jboss.entity.manager.factory.jndi.name" value="webservicesEMF"/>
    <property name="jboss.entity.manager.jndi.name" value="webservicesEM"/>
</properties>

Generic dao parts:

@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 parts:

@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;
      }

AND THE MAGIC TRICK:

Apply interceptor:

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

And your interceptor implementation:

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

Now you don't need any more manual commit implementation:

 getDao().commit();

Your dao code will work anywhere as expected with nice JTA CMT implementation. Code below will work like a charm:

@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;
}

For more then a week I was searching for some example which will provide me such a nice implementation and i could not find it so i want to share my learnings.

Now you can use your EJB3 standard as you expected it should work,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment