为什么SimpleJdbcCall igronre @Transactional批注

时间:2019-09-26 12:11:18

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

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

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

Firs方法directoryRepo.save(directory);只是简单的JPA保存操作,第二个rsdhUtilsService.createPhysTable(directory);JDBCTemplate从其自身服务进行的存储过程调用。问题是:如果JPA或SimpleJdbcCall操作中发生任何异常,事务将回滚,并且不会保留与JPA相关的nothig,但是如果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注释被忽略,我可以看到新记录保留在DB中。 我仅通过application.properties配置了一个数据源,这是JDBCTemlate的配置方式

@Component
class MapSQLParamUtils {

    private final DataSource dataSource;

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

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

}

所以我的问题是:为什么@Transactional会忽略SimpleJdbcCall,以及如何配置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();
    }

}

2 个答案:

答案 0 :(得分: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.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);
    }

答案 1 :(得分:-1)

请尝试:

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

@Transactional仅回退未检查异常的事务。对于检查的异常及其子类,它将提交数据。因此,尽管在这里引发了一个异常,但由于它是一个已检查的异常,因此Spring会忽略它并将数据提交到数据库中。

因此,如果抛出Exception或它的子类,请始终将其与@Transactional批注一起使用,以告诉Spring如果发生检查的异常,则回滚事务。

这非常简单,只需在@Transactional中使用以下内容:

@Transactional(rollbackFor = Exception.class)