com.github.zhengframework.jdbc.JdbcLocalTxnInterceptor Maven / Gradle / Ivy
package com.github.zhengframework.jdbc;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.persist.Transactional;
import com.google.inject.persist.UnitOfWork;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jooq.impl.DefaultConnectionProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JdbcLocalTxnInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(JdbcLocalTxnInterceptor.class);
private static final ConcurrentMap methodsTransactionals = new ConcurrentHashMap();
private final Provider jooqPersistServiceProvider;
private final Provider unitOfWorkProvider;
// Tracks if the unit of work was begun implicitly by this transaction.
private final ThreadLocal didWeStartWork = new ThreadLocal();
@Inject
public JdbcLocalTxnInterceptor(Provider jooqPersistServiceProvider,
Provider unitOfWorkProvider) {
this.jooqPersistServiceProvider = jooqPersistServiceProvider;
this.unitOfWorkProvider = unitOfWorkProvider;
}
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
UnitOfWork unitOfWork = unitOfWorkProvider.get();
JooqPersistService jooqProvider = jooqPersistServiceProvider.get();
// Should we start a unit of work?
if (!jooqProvider.isWorking()) {
unitOfWork.begin();
didWeStartWork.set(true);
}
Transactional transactional = readTransactionMetadata(methodInvocation);
DefaultConnectionProvider conn = jooqProvider.getConnectionWrapper();
// Allow 'joining' of transactions if there is an enclosing @Transactional method.
if (!conn.getAutoCommit()) {
return methodInvocation.proceed();
}
logger.debug("Disabling JDBC auto commit for this thread");
conn.setAutoCommit(false);
Object result;
try {
result = methodInvocation.proceed();
} catch (Exception e) {
//commit transaction only if rollback didn't occur
if (rollbackIfNecessary(transactional, e, conn)) {
logger.debug("Committing JDBC transaction");
conn.commit();
}
logger.debug("Enabling auto commit for this thread");
conn.setAutoCommit(true);
//propagate whatever exception is thrown anyway
throw e;
} finally {
// Close the em if necessary (guarded so this code doesn't run unless catch fired).
if (null != didWeStartWork.get() && conn.getAutoCommit()) {
didWeStartWork.remove();
unitOfWork.end();
}
}
// everything was normal so commit the txn (do not move into try block above as it
// interferes with the advised method's throwing semantics)
try {
logger.debug("Committing JDBC transaction");
conn.commit();
logger.debug("Enabling auto commit for this thread");
conn.setAutoCommit(true);
} finally {
//close the em if necessary
if (null != didWeStartWork.get()) {
didWeStartWork.remove();
unitOfWork.end();
}
}
//or return result
return result;
}
private Transactional readTransactionMetadata(final MethodInvocation methodInvocation) {
Method method = methodInvocation.getMethod();
Transactional cachedTransactional = methodsTransactionals.get(method);
if (cachedTransactional != null) {
return cachedTransactional;
}
Transactional transactional = method.getAnnotation(Transactional.class);
if (null == transactional) {
// If none on method, try the class.
Class> targetClass = methodInvocation.getThis().getClass();
transactional = targetClass.getAnnotation(Transactional.class);
}
if (null != transactional) {
methodsTransactionals.put(method, transactional);
} else {
// If there is no transactional annotation present, use the default
transactional = Internal.class.getAnnotation(Transactional.class);
}
return transactional;
}
/**
* Returns True if rollback DID NOT HAPPEN (i.e. if commit should continue).
*
* @param transactional The metadata annotation of the method
* @param e The exception to test for rollback
* @param conn A JPA Transaction to issue rollbacks on
*/
private boolean rollbackIfNecessary(final Transactional transactional,
final Exception e,
final DefaultConnectionProvider conn) {
boolean commit = true;
//check rollback clauses
for (Class extends Exception> rollBackOn : transactional.rollbackOn()) {
//if one matched, try to perform a rollback
if (rollBackOn.isInstance(e)) {
commit = false;
//check ignore clauses (supercedes rollback clause)
for (Class extends Exception> exceptOn : transactional.ignore()) {
//An exception to the rollback clause was found, DON'T rollback
// (i.e. commit and throw anyway)
if (exceptOn.isInstance(e)) {
commit = true;
break;
}
}
//rollback only if nothing matched the ignore check
if (!commit) {
logger.debug("Rolling back JDBC transaction for this thread");
conn.rollback();
}
//otherwise continue to commit
break;
}
}
return commit;
}
@Transactional
private static class Internal {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy