All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.zhengframework.jdbc.JdbcLocalTxnInterceptor Maven / Gradle / Ivy

There is a newer version: 1.8.0
Show newest version
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 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 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