簡體   English   中英

Spring 啟動事務回滾不會在 PostgreSQL 數據庫上觸發,因為事務空閑

[英]Spring boot transaction rollback doesn't fire on PostgreSQL database because of idle transaction

我嘗試執行事務操作並故意拋出異常以驗證是否已完成回滾但未執行回滾。

PostgreSQL 數據庫版本為 12.1-1,基於 Docker。

這是包含@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");
    }
}

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

並且從另一個服務調用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);
        }
    }
}

調試時我發現如果沒有執行回滾,那是因為事務是IDLE

這是來自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);
    }
  }

任何有關為什么將事務標記為空閑並停止要執行的回滾方法的提示將不勝感激。

編輯 (1)

作為@M。 Deinum 提到,不能保證在使用@PostConstruct時已經創建了事務代理。 這就是我使用ApplicationRunner進行測試的原因:

@Component
public class AppStartupRunner implements ApplicationRunner {
    @Autowired
    private MyTestService testService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
           testService.test();
    }
}

但這也不起作用。

在應用程序啟動后不久,我還嘗試通過使用RestController並向其發送 HTTP 請求來運行test方法,但仍然是同樣的問題。

@RestController
public class AppController {

    @Autowired
    private MyTestService testService;

    @GetMapping("/test")
    public ResponseEntity<Object> test() {
        testService.test();
        return ResponseEntity.ok().build();
    }
}

編輯 (2)

我將 PostgreSQL JDBC 版本從 42.2.2 升級到 42.2.18(截至目前最新),但嘗試回滾時連接仍然處於空閑狀態。

編輯 (3)

我在 git 存儲庫中重現了該問題: https://github.com/Martin-Hogge/spring-boot-postgresql-transactional-example/tree/master

我檢查了您希望在單個應用程序中使用多個模式(數據源、jdbc 模板)的架構。 @Transactional僅管理名為HikariPool-1的應用程序的默認數據源。 當您調用 rest 方法時,將創建名為HikariPool-2的新 hikari 池。 您的操作在HikariPool-2上,但@Transactional僅管理HikariPool-1

@Transactional的事務管理器參數不能動態更改。 因此,您可以定義一個新的自定義注釋來管理您的事務。 或者您可以使用TransactionTemplate代替注釋。

我創建了一個簡單的自定義事務管理方面。 它使用已定義的 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;
    }
}

連接處理程序

我為事務操作添加了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;
    }
}

基地道

getConnectionHandler的修飾符更改為public

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

pom.xml

您可以刪除postgresql.versionspring-jdbc.versionHikariCP.version 問題與版本無關。 為方面操作添加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