[JAVA][JPA][Transaction]事务管理器与自增主键的坑/涉及EntityManager

143 阅读2分钟

背景

开发人员自述:

"这个修改(逻辑), 我修改草稿状态的就正常, 修改异常状态的就报错, 奇了怪了, 就多了一段查询逻辑, 我也加事务了啊"

代码逻辑复现见下文, 其中340为测试用主键,前端传递

报错记不清了,类似于jpa经典的报entity with id xxx冲突还带些transactional提示

定位

打开日志DEBUG模式,本地调试

系统中存在JpaTransactionManager 和 DataSourceTransactionManager

使用Jpa的entityManager进行数据库操作时必须使用JpaTransactionManager, 否则涉及DML就会报事务问题, 当然这个是以前排查的了

1.清空测试用表, 如图片所示代码 image.png

  • 测试, 未报错, 但可以看到新增的数据为自增主键, 指定的主键未生效, 日志如下 image.png

    image.png

2.仿照开发反馈的场景, 数据库原始造一条主键为340的数据后, 进行同样的测试

  • delete处的日志一致,

  • saveAll处日志不同如图

  • 最后可以看到commit时为更新记录的, 检查数据库符合, 仍为一条340的数据

    image.png

image.png

3.尝试使用Jpa事务管理器进行测试

@Transactional(transactionManager = "jpaTransactionManager", rollbackFor = Exception.class)
  • 当表清空后测试: 发现在delete后创建了新事务, 且commit顺序可能是玄学, 那个开发的报错大概率为其电脑上或测试环境的commit顺序不同导致的

    image.png

  • 这肯定是不对的, 检查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操作, 仅此处逻辑特殊

解决

得到结论

  1. JpaRepository默认使用的是transactionManager

  2. EntityManager要求使用jpaTransactionManager, 且对于操作严格, 基本为操作全部严格的使用实体, 随意new对象都会被看作全新的, 自增逻辑

  3. 对于@Id且@GeneratedValue(strategy = GenerationType.IDENTITY)的自增主键列, 只支持

    • 新增, 主键由自增生成, 指定无效
    • 更新, 主键可以指定, 只要数据库存在就行, jpa执行update

规范

  • 宣贯Jpa此2种事务管理器对应的场景, 一般增删改查业务操作使用默认事务管理器即可.
  • 宣贯尽量不要使用entityManager, 一旦使用要保证所用的实体均来自jpa管理获得.