多次调用JdbcTemplate.update没有包装在一个单独的数据库事务中。

4 浏览
0 Comments

多次调用JdbcTemplate.update没有包装在一个单独的数据库事务中。

我查看了几个示例,不明白我在做什么不同;然而,这对我没有用。下面是我代码的简化描述,包括代码片段,以说明出现了什么问题。

我有一个主要的事务表,每个应用事务都有一条记录。我有一个链接表,其中包含从事务中获取的图像,外键指向主事务记录。这里没有什么奇怪或花哨的东西。(完整披露:还有其他两个链接表,但我确信一个表的修复方法适用于它们所有。)

我的代码创建了一个RESTful端点,像这样:

@Controller
@RequestMapping("/feed/v1")
public class ReportingDataFeedControllerV1 {
    @Resource(name = "dataFeedService")
    private DataFeedService dataFeedService;
    @PostMapping(value = "/myend", consumes = "application/json", produces = "application/json")
    public @ResponseBody DataFeedResponse DataFeed(@RequestBody DataFeedRequest request) {
        DataFeedResponse response = new DataFeedResponse();
        try {
        dataFeedService.saveData(request);

DataFeedService是一个定义了saveData方法的接口,由DataFeedServiceImpl实现:

public class DataFeedServiceImpl implements DataFeedService {
    @Resource(name = "dataFeedRepository")
    private DataFeedRepository dataFeedRepository;
    @Override
    //@Transactional        // 外部@Transactional已注释
    /**
     * 调用存储库实现类的save方法。
     * 
     * @param request - 包含要保存的数据的DataFeedRequest对象
     * @throws Exception
     * @author SmithDE
     */
    public void saveData(DataFeedRequest request) throws Exception {
        dataFeedRepository.saveData(request);
    }

DataFeedRepository是另一个由DataFeedRepositoryImpl实现的接口:

public class DataFeedRepositoryImpl implements DataFeedRepository {
    private static final int REF_SS_MFA = 4;
    @Resource(name="rdpJdbcTemplate")
    private JdbcTemplate jdbcTemplate;

所以,至少有两个级别的类在启动此操作的方法和真正调用saveData()的方法之间。

在DataFeedRepositoryImpl类中的方法会将事务数据保存到数据库表中,并且使用@Transactional进行注释,像这样:(此外,它还包括@Override,因为它是从基类实现的接口的实现。)

@Override
@Transactional           // 嵌套的@Transactional
/**
 * 执行将数据保存到数据库表的方法。
 * 
 * @param request - 包含要保存的数据的请求对象
 * @exception Exception
 * @author SmithDE
 */
public void saveData(DataFeedRequest request) throws Exception {

该方法会为主事务表构建一个INSERT语句。它需要检索新的主键,因此它调用JdbcTemplate的update方法,像这样:

        KeyHolder keyHolder = new GeneratedKeyHolder();
        int newRowCount = jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection
                    .prepareStatement(thisquery, Statement.RETURN_GENERATED_KEYS);
                    return ps;
                  }, keyHolder);
        id = (long) keyHolder.getKey();

接下来,它会为与此事务相关联的图像构建一系列的INSERT语句,并将它们分别发送到数据库中的JdbcTemplate.update()调用中,像这样:

    for (DataFeedImage image : images) {
        ...
            newRowCount = jdbcTemplate.update(thisquery);
    }

我期望在插入图像到链接图像表的任何调用中出现错误时,整个数据库事务都会回滚。然而,我的观察结果不同:

  • 主事务数据记录仍然存在于主事务表中。
  • 在错误发生之前插入的任何图像仍然存在于图像表中。

我希望所有这些update()调用都是同一个数据库事务的一部分,但显然它们并不是。

请帮助我理解我做错了什么。

刚写这篇修改时,有一个想法。前一个实现类中的方法被注释为@Transactional,实际进行JdbcTemplate.update()调用的方法也被注释为@Transactional,这个嵌套的@Transactional声明会引起问题吗?

0
0 Comments

在这个问题中,问题的原因是在多次调用JdbcTemplate.update时没有将其包裹在一个单一的数据库事务中。这可能导致数据不一致或丢失。

解决这个问题的方法是将多次调用JdbcTemplate.update的代码包裹在一个事务中。这可以通过使用Spring的事务管理器来实现。下面是一个示例代码:

@Autowired
private PlatformTransactionManager transactionManager;
public void updateDataInTransaction() {
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    TransactionStatus status = transactionManager.getTransaction(def);
    try {
        // 在事务中执行多次更新操作
        jdbcTemplate.update("UPDATE table1 SET column1 = value1");
        jdbcTemplate.update("UPDATE table2 SET column2 = value2");
        jdbcTemplate.update("UPDATE table3 SET column3 = value3");
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
        throw e;
    }
}

通过将多次调用JdbcTemplate.update包裹在事务中,可以确保这些操作要么全部成功提交,要么全部回滚。这样可以保证数据的一致性和完整性。

然而,在这个问题中,决策者决定对应用程序进行重新架构和重写,使用Java Persistence API(JPA)而不是JdbcTemplates。这可能是因为JPA提供了更高级别的抽象和更方便的数据访问方式。

虽然JPA也提供了事务管理的支持,但是否会出现相同的数据库事务问题还有待观察。

总之,将多次调用JdbcTemplate.update包裹在一个单一的数据库事务中可以解决这个问题,确保数据的一致性和完整性。另外,考虑使用更高级别的数据访问框架(如JPA)可能提供更方便和可靠的解决方案。

0