【问题标题】:Problem of using hibernate interceptor in springbootspring boot中使用hibernate拦截器的问题
【发布时间】:2019-07-07 02:34:12
【问题描述】:

我想在spring boot中使用hibernate拦截器,以便在事务提交后使用afterTransactionCompletion()方法做一些事情。

我按照How to use Spring managed Hibernate interceptors in Spring Boot进行配置(我只是在application.properties中添加spring.jpa.properties.hibernate.ejb.interceptor=com.lc.demo.inteceptor.MyInteceptor

拦截器可以工作,但是当我尝试在方法afterTransactionCompletion()中获取事务状态时仍然存在问题,它始终是NOT_ACTIVE(我希望它可以是COMMITTED):

import static org.hibernate.resource.transaction.spi.TransactionStatus.COMMITTED;

import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.springframework.stereotype.Component;


@Component
public class MyInteceptor extends EmptyInterceptor{

    private static final long serialVersionUID = -7992825362361127331L;

    @Override
    public void afterTransactionCompletion(Transaction tx) {
        //The status is always NOT_ACTIVE
        TransactionStatus status = tx.getStatus(); //
        if (tx.getStatus() == COMMITTED) {
            System.out.println("This is what I want to do");
        } else {
            System.out.println("This is what I do not want");
        }
    }

    @Override
    public void beforeTransactionCompletion(Transaction tx) {
        // The status is ACTIVE
        TransactionStatus status = tx.getStatus();
        System.out.println(status);
    }
}

我尝试调试了一下,发现在调用afterTransactionCompletion()之前,

在扩展AbstractLogicalConnectionImplementororg.hibernate.resource.jdbc.internal.LogicalConnectionProvidedImpl中,commit()方法调用afterCompletion()方法,该方法调用resetConnection(boolean initiallyAutoCommit)设置事务状态NOT_ACTIVE

    /*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.resource.jdbc.internal;

import java.sql.Connection;
import java.sql.SQLException;

import org.hibernate.TransactionException;
import org.hibernate.resource.jdbc.ResourceRegistry;
import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor;
import org.hibernate.resource.jdbc.spi.PhysicalJdbcTransaction;
import org.hibernate.resource.transaction.spi.TransactionStatus;

import org.jboss.logging.Logger;

/**
 * @author Steve Ebersole
 */
public abstract class AbstractLogicalConnectionImplementor implements LogicalConnectionImplementor, PhysicalJdbcTransaction {
    private static final Logger log = Logger.getLogger( AbstractLogicalConnectionImplementor.class );

    private TransactionStatus status = TransactionStatus.NOT_ACTIVE;
    protected ResourceRegistry resourceRegistry;

    @Override
    public PhysicalJdbcTransaction getPhysicalJdbcTransaction() {
        errorIfClosed();
        return this;
    }

    protected void errorIfClosed() {
        if ( !isOpen() ) {
            throw new IllegalStateException( this.toString() + " is closed" );
        }
    }

    @Override
    public ResourceRegistry getResourceRegistry() {
        return resourceRegistry;
    }

    @Override
    public void afterStatement() {
        log.trace( "LogicalConnection#afterStatement" );
    }

    @Override
    public void afterTransaction() {
        log.trace( "LogicalConnection#afterTransaction" );

        resourceRegistry.releaseResources();
    }

    // PhysicalJdbcTransaction impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    protected abstract Connection getConnectionForTransactionManagement();

    @Override
    public void begin() {
        try {
            if ( !doConnectionsFromProviderHaveAutoCommitDisabled() ) {
                log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" );
                getConnectionForTransactionManagement().setAutoCommit( false );
                log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" );
            }
            status = TransactionStatus.ACTIVE;
        }
        catch( SQLException e ) {
            throw new TransactionException( "JDBC begin transaction failed: ", e );
        }
    }

    @Override
    public void commit() {
        try {
            log.trace( "Preparing to commit transaction via JDBC Connection.commit()" );
            getConnectionForTransactionManagement().commit();
            status = TransactionStatus.COMMITTED;
            log.trace( "Transaction committed via JDBC Connection.commit()" );
        }
        catch( SQLException e ) {
            status = TransactionStatus.FAILED_COMMIT;
            throw new TransactionException( "Unable to commit against JDBC Connection", e );
        }

        afterCompletion();
    }

    protected void afterCompletion() {
        // by default, nothing to do
    }

    protected void resetConnection(boolean initiallyAutoCommit) {
        try {
            if ( initiallyAutoCommit ) {
                log.trace( "re-enabling auto-commit on JDBC Connection after completion of JDBC-based transaction" );
                getConnectionForTransactionManagement().setAutoCommit( true );
                status = TransactionStatus.NOT_ACTIVE;
            }
        }
        catch ( Exception e ) {
            log.debug(
                    "Could not re-enable auto-commit on JDBC Connection after completion of JDBC-based transaction : " + e
            );
        }
    }

    @Override
    public void rollback() {
        try {
            log.trace( "Preparing to rollback transaction via JDBC Connection.rollback()" );
            getConnectionForTransactionManagement().rollback();
            status = TransactionStatus.ROLLED_BACK;
            log.trace( "Transaction rolled-back via JDBC Connection.rollback()" );
        }
        catch( SQLException e ) {
            status = TransactionStatus.FAILED_ROLLBACK;
            throw new TransactionException( "Unable to rollback against JDBC Connection", e );
        }

        afterCompletion();
    }

    protected static boolean determineInitialAutoCommitMode(Connection providedConnection) {
        try {
            return providedConnection.getAutoCommit();
        }
        catch (SQLException e) {
            log.debug( "Unable to ascertain initial auto-commit state of provided connection; assuming auto-commit" );
            return true;
        }
    }

    @Override
    public TransactionStatus getStatus(){
        return status;
    }

    protected boolean doConnectionsFromProviderHaveAutoCommitDisabled() {
        return false;
    }
}

有人可以帮我解决这个问题吗?非常感谢。 这是我的pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lc</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

【问题讨论】:

    标签: java hibernate spring-boot interceptor


    【解决方案1】:

    虽然我也建议使用 Spring TransactionSynchronization。如果它不能使用(或不希望使用),有两点需要注意:

    beforeTransactionCompletion - “在事务提交之前调用(但不是在回滚之前)。” - 这意味着此方法实际上可用于在提交之前识别事务是否正常,并将其保存在某个临时(理想情况下为 ThreadLocal)状态。

    afterTransactionCompletion - 如果事务被回滚,事务的状态不是“NOT_ACTIVE”而是“MARKED_ROLLBACK” - 因此状态“NOT_ACTIVE”与实际调用的 beforeTransactionCompletion 相结合可用于确定事务是否成功.

    【讨论】:

      【解决方案2】:

      我使用了 hovanessyan 的答案并且它有效,现在让我完全描述我在这里所做的事情:

      我试图将其他人的代码迁移到springboot,代码使用带有persistence.xml的hibernate,拦截器使用threadlocal来存储事务中的所有实体,当事务提交时,选择一个“最佳”实体电子邮件用户,否则什么都不做并清除线程本地,代码是:

          public class MyInterceptor extends EmptyInterceptor {
      
          private static final long serialVersionUID = -7992825362361127331L;
      
          //The MyThreadLocal used to store all the entities in a transaction, when the transaction
          //committed, the interceptor will choose the "best" entity to email user
          private static MyThreadLocal myThreadLocal;
      
          public static void setMyThreadLocal(MyThreadLocal mTL) {
              MyInterceptor.myThreadLocal = mTL;
          }
      
          @Override
          public void afterTransactionCompletion(Transaction tx) {
              TransactionStatus status = tx.getStatus();
              if (tx.getStatus() == COMMITTED) {
                  MyThreadLocal.selectTheBestEntityToEmailUser();
              } else {
                  MyThreadLocal.clear();
              }
          }
      
          @Override
          public void beforeTransactionCompletion(Transaction tx) {
              TransactionStatus status = tx.getStatus();
              MyThreadLocal.beforeTransactionCompletion();
          }
      
          @Override
          public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
              MyThreadLocal.resourceAdded((Entity) entity);
              return false;
          }
      
          @Override
          public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
              Diff diff = new Diff(previousState, currentState, propertyNames);
              MyThreadLocal.resourceUpdated((Entity) entity, diff);
              return false;
          }
      
          @Override
          public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
              MyThreadLocal.resourceRemoved((Entity) entity);
          }
      
          @Override
          public void onCollectionUpdate(Object collection, Serializable key) throws CallbackException {
              if (!(collection instanceof PersistentCollection)) {
                  LOGGER.e("Unsupported collection type: {}", collection.getClass());
                  return;
              }
              Entity owner = (Entity) ((PersistentCollection) collection).getOwner();
              String role = ((PersistentCollection) collection).getRole();
              MyThreadLocal.collectionResourceUpdated(owner, role);
          }
      }
      

      但是在afterTransactionCompletion()方法中,事务状态总是NOT_ACTIVE,现在我使用TransactionSynchronization接口只是为了替换afterTransactionCompletion()方法:

          public class MyInterceptor extends EmptyInterceptor implements TransactionSynchronization {
      
          //the mothod of TransactionSynchronization interface
          @Override
          public void afterCompletion(int status) {
              if (status == STATUS_COMMITTED) {
                  MyThreadLocal.selectTheBestEntityToEmailUser();
              } else {
                  MyThreadLocal.clear();
              }
          }
      
          //the old code which works not well
          @Override
          public void afterTransactionCompletion(Transaction tx) {
              TransactionStatus status = tx.getStatus();
              if (tx.getStatus() == COMMITTED) {
                  MyThreadLocal.selectTheBestEntityToEmailUser();
              } else {
                  MyThreadLocal.clear();
              }
          }
      
         ...... other codes
      }
      

      而且新的 inteceptor 也需要通过 AOP 全局配置:

      @Component
      @Aspect
      public class InterceptorInit{
          @Autowired
          private MyInteceptor mI;
      
          @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
          public void registerTransactionSyncrhonization() {
              TransactionSynchronizationManager.registerSynchronization(mI);
          }
      }
      

      现在看来一切正常,我会继续测试。

      【讨论】:

        【解决方案3】:

        如果您使用 Spring 事务,您可以利用 TransactionSynchronization 并使用 afterCommit()

        默认 void afterCommit()

        事务提交后调用。可以正确执行进一步的操作 在主事务成功提交之后。

        用法:

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
                   void afterCommit(){
                        //do your thing
                   }
        })
        

        您还可以探索TransactionSynchronizationAdapter - 以类似的方式,您可以实现自己的“AfterCommitExecutor”,它实现了Executor 接口并扩展了TransactionSynchronizationAdapter 并覆盖了afterCommit() 方法。

        【讨论】:

        • 非常感谢,我使用TransactionSynchronization接口,得到了正确的交易状态
        猜你喜欢
        • 2014-10-06
        • 2018-08-09
        • 2017-02-01
        • 1970-01-01
        • 2017-11-28
        • 1970-01-01
        • 1970-01-01
        • 2011-12-24
        • 2020-11-30
        相关资源
        最近更新 更多