Java Batch(JSR352)中Batchlet执行JPA删除查询的事务异常问题
问题场景
现有一个Java Batch作业,可完成数据读取、处理及数据库存储操作,需新增一个删除数据的步骤。由于Chunk的Reader/Processor/Writer模式不适用该场景,采用Batchlet方案,但执行JPA删除查询时始终抛出javax.persistence.TransactionRequiredException: Executing an update/delete query异常。尝试过手动管理事务、切换JTA/非JTA持久化单元、自行创建EntityManager等方式,均未解决问题。相关代码如下:
package ...; import javax.batch.api.BatchProperty; import javax.inject.Inject; import javax.inject.Named; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @Named("CleanerBatchlet") public class CleanerBatchlet extends AbstractBatchlet { public static final Logger log = LogManager.getLogger(CleanerBatchlet.class); @PersistenceContext(unitName = "...") private EntityManager entityManager; @Inject @BatchProperty(name = "technologyIds") private String technologyIds; private void clearQueue(long technologyId) { //EntityManager entityManager = ...getEntityManager(); //entityManager.getTransaction().begin(); Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId"); q.setParameter("technologyId", technologyId); int count = q.executeUpdate(); //entityManager.getTransaction().commit(); log.debug("Deleted {} entries from queue {}", count, technologyId); //entityManager.close(); } @Override public String doProcess() throws Exception { log.debug("doProcess()"); out.println("technologyIds=" + technologyIds); log.info("technologyIds=" + technologyIds); try { String[] parts = technologyIds.split(","); for (String part: parts) { long technologyId = Long.parseLong(part); clearQueue(technologyId); } } catch (NullPointerException | NumberFormatException e) { throw new IllegalStateException("technologyIds must be set to a string of comma-separated numbers.", e); } return "COMPLETED"; } }
解决方案
1. 为Batchlet添加事务上下文
Java Batch默认不会为Batchlet自动开启事务,需手动启用容器管理事务或手动控制事务:
方式一:容器管理事务(推荐)
给doProcess方法添加@Transactional注解(Jakarta EE环境用jakarta.transaction.Transactional,旧版Java EE用javax.transaction.Transactional),由容器自动管理事务的开启、提交与回滚:
import jakarta.transaction.Transactional; // ... @Override @Transactional public String doProcess() throws Exception { // 原有逻辑保持不变 }
方式二:手动管理JTA事务
注入UserTransaction手动控制事务生命周期:
import jakarta.transaction.UserTransaction; import javax.persistence.Query; // ... @Inject private UserTransaction userTransaction; private void clearQueue(long technologyId) throws Exception { userTransaction.begin(); try { Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId"); q.setParameter("technologyId", technologyId); int count = q.executeUpdate(); log.debug("Deleted {} entries from queue {}", count, technologyId); userTransaction.commit(); } catch (Exception e) { userTransaction.rollback(); throw e; } }
2. 检查持久化单元配置
确保persistence.xml中持久化单元使用JTA事务类型,容器环境下优先使用JTA:
<persistence-unit name="..." transaction-type="JTA"> <!-- 数据源、实体映射等其他配置 --> </persistence-unit>
若使用RESOURCE_LOCAL类型,需手动创建EntityManagerFactory并管理事务,但这种方式在容器环境下易出错,不推荐。
3. 优先使用容器注入的EntityManager
容器注入的EntityManager会自动关联JTA事务,自行创建的EntityManager(如通过EntityManagerFactory.createEntityManager())需手动管理事务,容易引发异常,建议优先依赖容器注入实例。
内容的提问来源于stack exchange,提问作者queeg

