Spring Batch执行后EntityManager persist无法持久化Team数据排查
问题描述
我正在基于CSV文件创建球队仪表盘,定义了Match和Team两个Entity。使用Spring Batch将比赛数据加载至PostgreSQL,数据加载正常,可通过查询数据库验证加载完成。批处理任务结束后,我希望执行一些聚合查询并使用EntityManager将结果持久化到Team表,但调用persist后数据库中无对应数据,且未出现任何报错。输出HashMap中的数据显示正常,请问我的持久化方式可能存在什么问题?
相关代码
import com.project.dashboard.model.Team; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class JobCompletionNotificationListener implements JobExecutionListener { private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class); @PersistenceContext private final EntityManager em; @Autowired public JobCompletionNotificationListener(EntityManager em) { this.em = em; } @Override @Transactional(propagation = Propagation.REQUIRED) public void afterJob(JobExecution jobExecution) { if (jobExecution.getStatus() == BatchStatus.COMPLETED) { log.info("!!! JOB FINISHED! Time to verify the results"); Map<String, Team> teamData = new HashMap<>(); em.createQuery("select m.team1, count(*) from Match m group by m.team1", Object[].class) .getResultList() .stream() .map(e -> new Team((String) e[0], (long) e[1])) .forEach(team -> teamData.put(team.getTeamName(), team)); em.createQuery("select m.team2, count(*) from Match m group by m.team2", Object[].class) .getResultList() .forEach(e -> { Team team = teamData.get((String) e[0]); team.setTotalMatches(team.getTotalMatches() + (long) e[1]); }); em.createQuery("select m.winner, count(*) from Match m group by m.winner", Object[].class) .getResultList() .forEach(e -> { Team team = teamData.get((String) e[0]); if(team!=null) { team.setTotalWins((team.getTotalWins() + (long) e[1])); } }); teamData.values().forEach(em::persist); teamData.values().forEach(System.out::println); } } }
问题排查与解决
- 事务配置与传播问题:
afterJob方法上的@Transactional(Propagation.REQUIRED)可能无法正确开启事务,因为Spring Batch的任务结束后,原有的批处理事务已经提交,REQUIRED会尝试加入已不存在的事务,导致持久化操作不在事务范围内。改用Propagation.REQUIRES_NEW强制开启独立事务,确保持久化逻辑在自己的事务中执行。 - EntityManager注入冲突:同时使用
@PersistenceContext和构造注入EntityManager,可能导致获取的实例不是当前事务的上下文实例。移除构造注入,仅保留@PersistenceContext注入即可。 - persist操作的对象状态限制:
persist()仅对新创建的、未被EntityManager管理的实体生效。如果Team表中已有同名球队数据,调用persist()不会有任何操作。需要先查询实体是否存在,不存在用persist(),存在则直接更新(因为find出来的实体处于托管状态,修改后会自动同步);或者直接用merge()统一处理新增和更新场景。 - 未强制刷新持久化上下文:默认情况下,EntityManager会在事务提交时自动刷新,但如果事务没有正确触发提交,数据不会写入数据库。可以手动调用
em.flush()强制将持久化上下文的变更同步到数据库。 - Team实体主键配置问题:检查Team实体的主键注解是否正确,比如是否将
teamName标记为@Id。如果主键策略缺失或错误,JPA无法识别实体的唯一性,会导致持久化失败。
修改后的示例代码
import com.project.dashboard.model.Team; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionListener; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class JobCompletionNotificationListener implements JobExecutionListener { private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class); @PersistenceContext private EntityManager em; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void afterJob(JobExecution jobExecution) { if (jobExecution.getStatus() == BatchStatus.COMPLETED) { log.info("!!! JOB FINISHED! Time to verify the results"); Map<String, Team> teamData = new HashMap<>(); em.createQuery("select m.team1, count(*) from Match m group by m.team1", Object[].class) .getResultList() .stream() .map(e -> new Team((String) e[0], (long) e[1])) .forEach(team -> teamData.put(team.getTeamName(), team)); em.createQuery("select m.team2, count(*) from Match m group by m.team2", Object[].class) .getResultList() .forEach(e -> { Team team = teamData.get((String) e[0]); team.setTotalMatches(team.getTotalMatches() + (long) e[1]); }); em.createQuery("select m.winner, count(*) from Match m group by m.winner", Object[].class) .getResultList() .forEach(e -> { Team team = teamData.get((String) e[0]); if(team!=null) { team.setTotalWins((team.getTotalWins() + (long) e[1])); } }); teamData.values().forEach(team -> { Team existingTeam = em.find(Team.class, team.getTeamName()); if (existingTeam == null) { em.persist(team); } else { existingTeam.setTotalMatches(team.getTotalMatches()); existingTeam.setTotalWins(team.getTotalWins()); } }); em.flush(); teamData.values().forEach(System.out::println); } } }
内容的提问来源于stack exchange,提问作者SaM
相关产品推荐
相关产品推荐

