背景
开发人员自述:
"这个修改(逻辑), 我修改草稿状态的就正常, 修改异常状态的就报错, 奇了怪了, 就多了一段查询逻辑, 我也加事务了啊"
代码逻辑复现见下文, 其中340为测试用主键,前端传递
报错记不清了,类似于jpa经典的报entity with id xxx冲突还带些transactional提示
定位
打开日志DEBUG模式,本地调试
系统中存在JpaTransactionManager 和 DataSourceTransactionManager
使用Jpa的entityManager进行数据库操作时必须使用JpaTransactionManager, 否则涉及DML就会报事务问题, 当然这个是以前排查的了
1.清空测试用表, 如图片所示代码
-
测试, 未报错, 但可以看到新增的数据为自增主键, 指定的主键未生效, 日志如下
2.仿照开发反馈的场景, 数据库原始造一条主键为340的数据后, 进行同样的测试
-
delete处的日志一致,
-
saveAll处日志不同如图
-
最后可以看到commit时为更新记录的, 检查数据库符合, 仍为一条340的数据
3.尝试使用Jpa事务管理器进行测试
@Transactional(transactionManager = "jpaTransactionManager", rollbackFor = Exception.class)
-
当表清空后测试: 发现在delete后创建了新事务, 且commit顺序可能是玄学, 那个开发的报错大概率为其电脑上或测试环境的commit顺序不同导致的
-
这肯定是不对的, 检查saveAll方法, 发现Jpa默认实现为默认事务管理器名称. 故而spring判定不存在saveAll所REQUIRED的事务, create new
@Transactional
@Override
//org.springframework.data.jpa.repository.support.SimpleJpaRepository#saveAll
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
- 将saveAll改写为使用jpaTransactionManager后再尝试, 事务正常了, 只有一个事务, 结果为不论数据库存不存在340, 都会实际上是新增一条自增主键的记录.
- 特殊的是, 如果saveAll的是通过findById(340L)查出来的实体, 则jpa进行的是update操作, 仅此处逻辑特殊
解决
得到结论
-
JpaRepository默认使用的是transactionManager
-
EntityManager要求使用jpaTransactionManager, 且对于操作严格, 基本为操作全部严格的使用实体, 随意new对象都会被看作全新的, 自增逻辑
-
对于@Id且@GeneratedValue(strategy = GenerationType.IDENTITY)的自增主键列, 只支持
- 新增, 主键由自增生成, 指定无效
- 更新, 主键可以指定, 只要数据库存在就行, jpa执行update
规范
- 宣贯Jpa此2种事务管理器对应的场景, 一般增删改查业务操作使用默认事务管理器即可.
- 宣贯尽量不要使用entityManager, 一旦使用要保证所用的实体均来自jpa管理获得.