You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 01:38:11