多JTA EntityManager同时使用事务异常问题求助
代码实现
class A { @PersistenceContext(unitName = "EPU") EntityManager schemaEntityManager; @PersistenceContext(unitName = "ELegacyPU") EntityManager oldSchemaEntityManager; void init() { queryFromDatabase(); // 检查是否为空,若是则从遗留库查询 queryFromOldDatabase(); recreateLegacyInCurrentDatabase(); } List<E> queryFromDatabase() { // 从schemaEntityManager查询 } List<ELegacy> queryFromOldDatabase() { // 从oldSchemaEntityManager查询 } void recreateLegacyInCurrentDatabase() { // records -> schemaEntityManager.merge() } }
场景一异常
仅在recreateLegacyInCurrentDatabase方法上添加@Transactional时,抛出以下异常:
javax.persistence.TransactionRequiredException: WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context) at org.jboss.as.jpa@24.0.0.Final//org.jboss.as.jpa.container.AbstractEntityManager.transactionIsRequired(AbstractEntityManager.java:880) at org.jboss.as.jpa@24.0.0.Final//org.jboss.as.jpa.container.AbstractEntityManager.merge(AbstractEntityManager.java:567)
场景二异常
仅在类上添加@Transactional、仅在queryFromDatabase/queryFromOldDatabase方法上添加,或上述两种情况搭配recreateLegacyInCurrentDatabase方法添加时,queryFromOldDatabase方法抛出以下异常:
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not prepare statement at org.hibernate@5.3.20.Final//org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154) at org.hibernate@5.3.20.Final//org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1575) at org.hibernate@5.3.20.Final//org.hibernate.query.Query.getResultList(Query.java:132) at org.hibernate@5.3.20.Final//org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:74) ... Caused by: org.hibernate.exception.GenericJDBCException: could not prepare statement ... Caused by: java.sql.SQLException: IJ031070: Transaction cannot proceed: STATUS_MARKED_ROLLBACK
疑问
当前使用persistence.xml配置JTA,是否必须改用RESOURCE_LOCAL配置才能解决问题?
回答
场景一异常原因
TransactionRequiredException是因为类内部调用@Transactional方法导致注解未生效。JBoss容器对@Transactional的代理仅在外部调用时生效,init()作为非事务方法直接调用recreateLegacyInCurrentDatabase(),此时该方法的事务注解不会被容器处理,merge()操作因缺少事务上下文报错。
场景二异常原因
当你在类或查询方法上添加@Transactional时,整个init()流程会被纳入同一个JTA事务,但同时操作两个不同的JTA数据源(EPU和ELegacyPU)。若这两个数据源未配置XA分布式事务,容器无法在单个事务中协调两个独立资源,会导致事务被标记为回滚状态,后续对旧库的查询操作因事务失效抛出STATUS_MARKED_ROLLBACK异常。
解决方案(无需改用RESOURCE_LOCAL)
完全可以在JTA配置下解决问题,具体步骤如下:
修复事务生效问题
- 将
@Transactional添加到init()方法上(而非仅加在recreateLegacyInCurrentDatabase()),确保整个流程的事务上下文由容器管理; - 或者将
A类实例注入到其他组件中,通过外部调用触发init(),确保内部@Transactional方法通过代理执行。
- 将
解决跨数据源事务冲突
- 方案一:配置XA分布式事务
在persistence.xml中将两个数据源配置为XA类型(需数据库支持XA驱动),并在JBoss中启用分布式事务管理器(如Narayana),让容器协调跨数据源的事务。 - 方案二:拆分事务边界
将查询旧库与写入新库的操作拆分为独立事务:- 查询旧库的方法(
queryFromOldDatabase())无需事务(只读操作可在无事务上下文执行); - 将
recreateLegacyInCurrentDatabase()设为独立事务方法,并通过外部代理调用; - 若需保证数据一致性,可通过编程式事务手动控制两个事务的提交/回滚逻辑。
- 查询旧库的方法(
- 方案一:配置XA分布式事务
结论
不需要改用RESOURCE_LOCAL配置。JTA本身支持跨数据源事务,只要调整事务注解的生效范围,并根据业务需求配置XA分布式事务或拆分事务边界,即可解决当前异常问题。
内容的提问来源于stack exchange,提问作者y07k2

