[英]Problem of using hibernate interceptor in springboot
I want to use hibernate interceptor in spring boot in order to use the afterTransactionCompletion()
method to do something after the transaction committed.我想在 Spring Boot 中使用 hibernate 拦截器,以便在事务提交后使用afterTransactionCompletion()
方法做一些事情。
I follow the How to use Spring managed Hibernate interceptors in Spring Boot to configure(I just add spring.jpa.properties.hibernate.ejb.interceptor=com.lc.demo.inteceptor.MyInteceptor
in application.properties
)我按照如何在 Spring Boot 中使用 Spring 管理的 Hibernate 拦截器进行配置(我只是在application.properties
添加spring.jpa.properties.hibernate.ejb.interceptor=com.lc.demo.inteceptor.MyInteceptor
)
The interceptor works but there is still a problem when I try to get the transaction status in the method afterTransactionCompletion()
, it is always NOT_ACTIVE
(I wish it could be COMMITTED
):拦截器可以工作,但是当我尝试在方法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);
}
}
I try to debug it and find that before the afterTransactionCompletion()
is called,我尝试调试它afterTransactionCompletion()
现在调用afterTransactionCompletion()
之前,
in org.hibernate.resource.jdbc.internal.LogicalConnectionProvidedImpl
which extends AbstractLogicalConnectionImplementor
, the commit()
method call the afterCompletion()
method which call the resetConnection(boolean initiallyAutoCommit)
to set the transaction status NOT_ACTIVE
:在扩展AbstractLogicalConnectionImplementor
org.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;
}
}
Can somebody help me to solve this problem?有人可以帮我解决这个问题吗? Thanks a lot.非常感谢。 Here are my pom.xml
:这是我的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>
If you're using Spring transactions you can leverage TransactionSynchronization
and use afterCommit()
如果您使用的是Spring事务,则可以利用TransactionSynchronization
并使用afterCommit()
default void afterCommit() 默认void afterCommit()
Invoked after transaction commit. 事务提交后调用。 Can perform further operations right after the main transaction has successfully committed. 成功完成主要交易后,可以立即执行进一步的操作。
Usage: 用法:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
void afterCommit(){
//do your thing
}
})
You can also explore TransactionSynchronizationAdapter
- in a similar way you can implement you own "AfterCommitExecutor" that implements the Executor
interface and extends TransactionSynchronizationAdapter and overrides the afterCommit()
method. 您还可以探索TransactionSynchronizationAdapter
以类似的方式可以实现自己的“ AfterCommitExecutor”,该实现实现Executor
接口并扩展TransactionSynchronizationAdapter并覆盖afterCommit()
方法。
I use the answer by hovanessyan and it works, now let me completely describe what I did here: 我使用了hovanessyan的答案,它很有效,现在让我完整地描述一下我在这里所做的事情:
I was trying to migrate other people's code to springboot, the code uses hibernate with a persistence.xml and the interceptor uses threadlocal to store all the entities in a transaction, when the transaction is committed, choose one "best" entity to email user, else do nothing and clear the threadlocal, the code is : 我正在尝试将其他人的代码迁移到springboot,代码使用带有persistence.xml的hibernate和拦截器使用threadlocal存储事务中的所有实体,在提交事务后,选择一个“最佳”实体给用户发送电子邮件,否则不执行任何操作并清除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);
}
}
But in afterTransactionCompletion() method, the transaction status is always NOT_ACTIVE, now I use the TransactionSynchronization interface just to replace the afterTransactionCompletion() method: 但是在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
}
And the new inteceptor also need to be configured global by AOP: 而且新的接收器还需要由AOP全局配置:
@Component
@Aspect
public class InterceptorInit{
@Autowired
private MyInteceptor mI;
@Before("@annotation(org.springframework.transaction.annotation.Transactional)")
public void registerTransactionSyncrhonization() {
TransactionSynchronizationManager.registerSynchronization(mI);
}
}
Now it seems that all work well, I will continue to test. 现在看来一切正常,我将继续测试。
While I suggest usage of Spring TransactionSynchronization too.虽然我也建议使用 Spring TransactionSynchronization。 In case it can't be used (or not desired to) there are two things to note:如果不能使用(或不想使用),有两件事需要注意:
beforeTransactionCompletion - "Called before a transaction is committed (but not before rollback)." beforeTransactionCompletion - “在提交事务之前(但不在回滚之前)调用。” - which means this method can be actually used to recognize if the transaction is fine right before commit or not and save it in some temporary (ideally ThreadLocal) state. - 这意味着该方法实际上可用于在提交之前识别事务是否正常,并将其保存在某个临时(理想情况下为 ThreadLocal)状态。
afterTransactionCompletion - in case the transaction is rollbacked, the state of the transaction is not "NOT_ACTIVE" but "MARKED_ROLLBACK" - therefore the state "NOT_ACTIVE" in combination with beforeTransactionCompletion being actually called can be used to determine the transaction was a success. afterTransactionCompletion - 如果事务被回滚,事务的状态不是“NOT_ACTIVE”而是“MARKED_ROLLBACK” - 因此状态“NOT_ACTIVE”与实际调用的 beforeTransactionCompletion 相结合可用于确定事务是否成功。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.