【问题标题】:Why do SimpleJdbcCall igronre @Transactional annotation为什么 SimpleJdbcCall igronre @Transactional 注释
【发布时间】:2020-01-26 17:13:33
【问题描述】:

我想在服务方法中做一些与数据库相关的操作。最初它看起来像这样:

@Override
@Transactional
public void addDirectory(Directory directory) {
    //some cheks here
    directoryRepo.save(directory);
    rsdhUtilsService.createPhysTable(directory);
}

第一个方法directoryRepo.save(directory); 只是简单的JPA 保存操作,第二个方法rsdhUtilsService.createPhysTable(directory); 是来自它自己的服务的JDBCTemplate 存储过程调用。问题是:如果在 JPA 或 SimpleJdbcCall 操作中发生任何异常,事务将回滚并且不会保留与 JPA 相关的任何内容,但如果在 JPA 操作中发生异常,则 SimpleJdbcCall 的结果不会受事务回滚的影响。 为了说明这种行为,我删除了 JAP 操作,将 @Transactional 标记为 (readOnly = true) 并将所有与 JDBCTemplate 相关的逻辑从另一个服务移动到当前服务。

@Service
public class DirectoriesServiceImpl implements DirectoriesService {

    private final DirectoryRepo directoryRepo;

    private final MapSQLParamUtils sqlParamUtils;

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public DirectoriesServiceImpl(DirectoryRepo directoryRepo, MapSQLParamUtils sqlParamUtils, JdbcTemplate jdbcTemplate) {
        this.directoryRepo = directoryRepo;
        this.sqlParamUtils = sqlParamUtils;
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    @Transactional(readOnly = true)
    public void addDirectory(Directory directory) {
        directoryRepo.save(directory);

        new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
                .withFunctionName("create_dict")
                .executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
    }

}

因此@Transactional 注释被忽略,我可以看到新记录保存在数据库中。 我只有一个通过application.properties 配置的DataSource,下面是JDBCTemlate 的配置方式

@Component
class MapSQLParamUtils {

    private final DataSource dataSource;

    @Autowired
    MapSQLParamUtils(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource);
    }

}

所以我的问题是:为什么SimpleJdbcCall 会忽略@Transactional 以及如何配置JPAJDBCTemlate 以使用相同的事务管理器。

更新: 这就是我在控制器中使用此服务的方式

@RestController
@RequestMapping(value = "/api/v1/directories")
public class DirectoriesRESTControllerV1 {

    private final DirectoriesService directoriesService;

    @Autowired
    public DirectoriesRESTControllerV1(DirectoriesService directoriesService) {
        this.directoriesService = directoriesService;
    }

    @PostMapping
    @PreAuthorize("hasPermission('DIRECTORIES_USER', 'W')")
    public ResponseEntity createDirectory(@NotNull @RequestBody DirectoryRequestDTO createDirectoryRequestDTO) {
        Directory directoryFromRequest = ServiceUtils.convertDtoToEntity(createDirectoryRequestDTO);
        directoriesService.addDirectory(directoryFromRequest);
        return ResponseEntity.noContent().build();
    }

}

【问题讨论】:

  • 请出示您拨打addDirectory的代码。
  • @chrylis 我已经更新了我的问题
  • 没有时间阅读和参与,但铃声响起:JPA 可能并不总是与 JDBC 结合,这取决于 JPA 缓存。
  • @chrylis,@vipul tnx,但我想我找到了原因。我猜是因为withFunctionName("create_dict") 到底做了什么。我们使用 Oracle 作为我们的数据库,因此create_dict 函数创建了一个数据库表,并且此操作在完成后无法回滚。如果我使用@Transactional ,其中的所有操作似乎都表现得异步,因此当directoryRepo.save(directory) 启动时,程序会继续执行rsdhUtilsService.createPhysTable(directory) 而无需等待先前方法调用的结果,如果是刹车,createPhysTable(directory) 的结果不能被回滚

标签: java spring-boot spring-data-jpa spring-jdbc spring-transactions


【解决方案1】:
  1. 如前所述,这里的问题是 JPA 不会在调用存储库方法时立即执行 sql 查询。要强制执行它,您可以使用显式 entityManager.flush():
@Autowired
private javax.persistence.EntityManager entityManager;
...

@Override
@Transactional(readOnly = true)
public void addDirectory(Directory directory) {
    directoryRepo.save(directory);
    entityManager.flush();

    new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
            .withFunctionName("create_dict")
            .executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
  1. 要通过休眠查看真正的 SQL 查询,您可以启用选项 show_sql,如果您的应用程序是 spring-boot,则此配置将启用它:
spring.jpa:
  show-sql: true
  properties:
    hibernate:
      format_sql: true

logging.level:
  org.hibernate.SQL: DEBUG
  1. 关于事务管理器。如果 entityManager 刷新还不够,您可能需要处理 JPA 和 DataSource 的复合事务管理器。 Spring data commons 有ChainedTransactionManager。注意:你应该小心它。我在我的项目中这样使用它:
    @Bean(BEAN_CONTROLLER_TX)
    public PlatformTransactionManager controllerTransactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    @Bean(BEAN_ANALYTICS_TX)
    public PlatformTransactionManager analyticsTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * Chained both 2 transaction managers.
     *
     * @return chained transaction manager for controller datasource and analytics datasource
     */
    @Primary
    @Bean
    public PlatformTransactionManager transactionManager(
            @Qualifier(BEAN_CONTROLLER_TX) PlatformTransactionManager controllerTransactionManager,
            @Qualifier(BEAN_ANALYTICS_TX) PlatformTransactionManager analyticsTransactionManager) {
        return new ChainedTransactionManager(controllerTransactionManager, analyticsTransactionManager);
    }

【讨论】:

    【解决方案2】:

    请试试这个:

    @Transactional(rollbackFor = Exception.class)
    public void addDirectory(Directory directory){
    

    @Transactional 仅回滚未检查异常的事务。对于已检查的异常及其子类,它会提交数据。所以虽然这里抛出了一个异常,但是因为它是一个受检异常,所以 Spring 会忽略它并将数据提交到数据库中。

    因此,如果您抛出异常或其子类,请始终将上述内容与 @Transactional 注释一起使用,以告诉 Spring 在发生检查的异常时回滚事务。

    这很简单,只需将以下内容与@Transactional一起使用:

    @Transactional(rollbackFor = Exception.class)
    

    【讨论】:

    • 问题中的代码不会抛出已检查的异常。
    猜你喜欢
    • 2017-01-12
    • 2013-07-20
    • 1970-01-01
    • 2018-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多