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,