多次调用JdbcTemplate.update没有包装在一个单独的数据库事务中。
多次调用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声明会引起问题吗?
在这个问题中,问题的原因是在多次调用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)可能提供更方便和可靠的解决方案。