简体   繁体   English

Spring 启动事务回滚不会在 PostgreSQL 数据库上触发,因为事务空闲

[英]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:这是来自PgConnectionrollback 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.任何有关为什么将事务标记为空闲并停止要执行的回滚方法的提示将不胜感激。

Edit (1)编辑 (1)

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();
    }
}

Edit (2)编辑 (2)

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(截至目前最新),但尝试回滚时连接仍然处于空闲状态。

Edit (3)编辑 (3)

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 的数据源和事务生命周期。

Test Service测试服务

@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");
    }
}

Custom Transactional自定义事务

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();
}

Custom Transactional Aspect自定义事务方面

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;
    }
}

Connection Handler连接处理程序

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;
    }
}

BaseDao基地道

Change modifier of getConnectionHandler to public .getConnectionHandler的修饰符更改为public

    public ConnectionHandler getConnectionHandler() {
        return getDataSource().getConnection(getSchemaName());
    }

pom.xml pom.xml

You can remove postgresql.version , spring-jdbc.version and HikariCP.version .您可以删除postgresql.versionspring-jdbc.versionHikariCP.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM