[英]Spring boot transaction rollback doesn't fire on PostgreSQL database because of idle transaction
I try to execute a transactional operation and intentionally throw an exception in order to verify if a rollback is done but the rollback isn't being executed.我尝试执行事务操作并故意抛出异常以验证是否已完成回滚但未执行回滚。
The PostgreSQL database version is 12.1-1 and is Docker-based. PostgreSQL 数据库版本为 12.1-1,基于 Docker。
Here is the service that contains the @Transactional
annotation:这是包含@Transactional
注释的服务:
@Service
public class MyTestService {
@Autowired
private DocumentDataDao documentDataDao;
@Transactional
public void test() {
DocumentData data = new DocumentData();
data.setData(UUID.randomUUID().toString());
documentDataDao.create(data);
throw new IllegalArgumentException("Test rollback");
}
}
The create
function is using a NamedParameterJdbcTemplate
to insert the data: create
function 使用NamedParameterJdbcTemplate
插入数据:
String statement = String.format("INSERT INTO %s (%s) VALUES (%s) RETURNING %s", tableName,
String.join(",", insertingColumns), String.join(",", values),
String.join(",", returningColumns));
return getNamedJdbcTemplate().queryForObject(statement, parameters, getRowMapper());
And the test
function is called from another service:并且从另一个服务调用test
function :
@Service
public class ApplicationStartupListener {
private Logger log = LoggerFactory.getLogger(ApplicationStartupListener.class);
@Autowired
private MyTestService testService;
@PostConstruct
public void init() {
try {
testService.test();
} catch (Exception e) {
log.error("fail to start", e);
}
}
}
When debugging I found out that if the rollback isn't executed it's because of the transaction being IDLE
.调试时我发现如果没有执行回滚,那是因为事务是IDLE
。
Here is the rollback
function from PgConnection
and executeTransactionCommand
isn't being executed:这是来自PgConnection
的rollback
function 并且未执行executeTransactionCommand
:
public void rollback() throws SQLException {
checkClosed();
if (autoCommit) {
throw new PSQLException(GT.tr("Cannot rollback when autoCommit is enabled."),
PSQLState.NO_ACTIVE_SQL_TRANSACTION);
}
if (queryExecutor.getTransactionState() != TransactionState.IDLE) {
executeTransactionCommand(rollbackQuery);
}
}
Any hint on why the transaction is being marked as idle and stops the rollback method to be executed would be appreciated.任何有关为什么将事务标记为空闲并停止要执行的回滚方法的提示将不胜感激。
As @M.作为@M。 Deinum mentioned, there is no guarantee that a transactional proxy has been created when using @PostConstruct
. Deinum 提到,不能保证在使用@PostConstruct
时已经创建了事务代理。 That's why I tested with an ApplicationRunner
:这就是我使用ApplicationRunner
进行测试的原因:
@Component
public class AppStartupRunner implements ApplicationRunner {
@Autowired
private MyTestService testService;
@Override
public void run(ApplicationArguments args) throws Exception {
testService.test();
}
}
But this didn't work either.但这也不起作用。
I also tried to run the test
method a few moments after the application has been started by using a RestController
and sending an HTTP request to it but still, the same issue.在应用程序启动后不久,我还尝试通过使用RestController
并向其发送 HTTP 请求来运行test
方法,但仍然是同样的问题。
@RestController
public class AppController {
@Autowired
private MyTestService testService;
@GetMapping("/test")
public ResponseEntity<Object> test() {
testService.test();
return ResponseEntity.ok().build();
}
}
I upgraded the PostgreSQL JDBC version from 42.2.2 to 42.2.18 (latest as of now) but the connection is still IDLE when trying to rollback.我将 PostgreSQL JDBC 版本从 42.2.2 升级到 42.2.18(截至目前最新),但尝试回滚时连接仍然处于空闲状态。
I reproduced the issue in a git repository: https://github.com/Martin-Hogge/spring-boot-postgresql-transactional-example/tree/master .我在 git 存储库中重现了该问题: https://github.com/Martin-Hogge/spring-boot-postgresql-transactional-example/tree/master 。
I examined the architecture that you want to use multiple schemas (data sources, jdbc templates) in a single application.我检查了您希望在单个应用程序中使用多个模式(数据源、jdbc 模板)的架构。 @Transactional
only manages application's default data source that is named HikariPool-1
. @Transactional
仅管理名为HikariPool-1
的应用程序的默认数据源。 When you call the rest method, new hikari pool will be created that is named HikariPool-2
.当您调用 rest 方法时,将创建名为HikariPool-2
的新 hikari 池。 Your operations are on HikariPool-2
, but @Transactional
manages only HikariPool-1
.您的操作在HikariPool-2
上,但@Transactional
仅管理HikariPool-1
。
@Transactional
's transaction manager argument can not be changed dynamically. @Transactional
的事务管理器参数不能动态更改。 So, you can define a new custom annotation that manages your transactions.因此,您可以定义一个新的自定义注释来管理您的事务。 Or you can use TransactionTemplate
instead of annotations.或者您可以使用TransactionTemplate
代替注释。
I created a simple custom transaction management aspect.我创建了一个简单的自定义事务管理方面。 It is working with defined dao's data source and transaction lifecycle.它使用已定义的 dao 的数据源和事务生命周期。
@Service
public class MyTestService {
@Autowired
private DocumentDataDao documentDataDao;
@CustomTransactional(DocumentDataDao.class)
public void test() {
DocumentData data = new DocumentData();
data.setData(UUID.randomUUID().toString());
documentDataDao.create(data);
throw new IllegalArgumentException("Test rollback");
}
}
package com.example.transactional;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CustomTransactional {
Class<? extends BaseDao<?>> value();
}
package com.example.transactional;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class CustomTransactionalAspect implements ApplicationContextAware {
private ApplicationContext applicationContext;
private Map<Class<? extends BaseDao<?>>, BaseDao<?>> classMap = new HashMap<>();
@Around("@annotation(customTransactional)")
public Object customTransaction(ProceedingJoinPoint joinPoint, CustomTransactional customTransactional) throws Throwable {
BaseDao<?> baseDao = getBaseDao(customTransactional.value());
// custom transaction management
return baseDao.getConnectionHandler().getTransactionTemplate().execute(status -> {
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
});
}
/**
* Search {@link BaseDao} class on spring beans
*
* @param clazz Target dao class type
* @return Spring bean object
*/
private BaseDao<?> getBaseDao(Class<? extends BaseDao<?>> clazz) {
return classMap.computeIfAbsent(clazz, c -> applicationContext.getBean(c));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
I added transactionTemplate
for transaction operations我为事务操作添加了transactionTemplate
public class ConnectionHandler {
private NamedParameterJdbcTemplate namedJdbcTemplate;
private JdbcTemplate jdbcTemplate;
private TransactionTemplate transactionTemplate;
private String schema;
public ConnectionHandler(DataSource dataSource, String schema) {
this.namedJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.schema = schema;
this.transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
}
public NamedParameterJdbcTemplate getNamedJdbcTemplate() {
return namedJdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public String getSchema() {
return schema;
}
public TransactionTemplate getTransactionTemplate() {
return transactionTemplate;
}
}
Change modifier of getConnectionHandler
to public
.将getConnectionHandler
的修饰符更改为public
。
public ConnectionHandler getConnectionHandler() {
return getDataSource().getConnection(getSchemaName());
}
You can remove postgresql.version
, spring-jdbc.version
and HikariCP.version
.您可以删除postgresql.version
、 spring-jdbc.version
和HikariCP.version
。 Problem is not related with versions.问题与版本无关。 Add spring-boot-starter-aop
dependency for aspect operations.为方面操作添加spring-boot-starter-aop
依赖项。
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.