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

JPA多对多关联Subject重复插入问题排查与解决

JPA多对多关联中Subject重复创建的问题解决

问题描述

我正在创建一个简单的示例应用,以理解JPA多对多关联的行为。根据我的理解,执行Main.java后,Subject表应仅创建3条记录,分别对应“Software Engineering”、“Distributed System”和“Business Analysis and Optimization”,每条记录拥有独立ID。但实际情况是Subject表中创建了4条记录,其中“Software Engineering”因同时在CSE和IT分支开设而出现重复条目。请问这是正确的行为吗?如果不是,如何修改Main.java使Subject表仅保留3条记录?

原因分析

这不是正确的预期行为,问题根源在于:

  • 每次调用getSubject1()都会生成全新的Subject对象实例,JPA会将这些内存中不同的实例判定为独立实体,哪怕它们的名称、描述完全一致。
  • Branch类的@ManyToMany注解设置了cascade = {CascadeType.ALL},当持久化两个Branch时,会分别级联保存各自关联的Subject实例,导致同一课程被重复插入数据库。

解决方案

核心思路是复用同一个Subject实例,避免为不同Branch创建重复的实体对象,具体可通过两种方式实现:

方式1:提前创建并复用Subject实例

在Main方法中先初始化所有需要的Subject对象,将同一个实例关联到不同Branch上,确保同一课程只被持久化一次。

方式2:先查询数据库再决定是否创建

在已有数据的场景下,优先通过Subject名称查询数据库,存在则复用现有记录,不存在再创建新实例(更适合生产环境)。

修改后的Main.java代码

以下是采用方式1的修改代码:

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = null;
        EntityManager entityManager = null;
        EntityTransaction transaction = null;

        try {
            emf = Persistence.createEntityManagerFactory("my-persistence-unit");
            entityManager = emf.createEntityManager();
            transaction = entityManager.getTransaction();
            transaction.begin();

            // 提前创建所有Subject实例,确保同一课程仅初始化一次
            Subject subject1 = getSubject1();
            Subject subject2 = getSubject2();
            Subject subject3 = getSubject3();

            Branch branch1 = getBranch1();//CSE Branch in Engineering
            Branch branch2 = getBranch2();//IT Branch in Engineering
            
            branch1.setSubjects(new HashSet<>());
            branch1.addSubject(subject1);
            branch1.addSubject(subject2);
            
            branch2.setSubjects(new HashSet<>());
            branch2.addSubject(subject1); // 复用同一个subject1实例
            branch2.addSubject(subject3);

            entityManager.persist(branch1);
            entityManager.persist(branch2);

            transaction.commit();

        } catch (Exception e) {
            if(transaction != null && transaction.isActive()) transaction.rollback();
            e.printStackTrace();
        } finally {
            if(entityManager != null) entityManager.close();
            if(emf != null) emf.close();
        }
    }

    // 原有工具方法保持不变
    private static Subject getSubject1() {
        Subject subject = new Subject();
        subject.setSubjectName("Software Engineering");
        subject.setSubjectDesc("Apply key aspects of software engineering processes for the development of a complex software system");

        return subject;
    }

    private static Subject getSubject2() {
        Subject subject = new Subject();
        subject.setSubjectName("Distributed System");
        subject.setSubjectDesc("Explore recent advances in distributed computing systems");

        return subject;
    }

    private static Subject getSubject3() {
        Subject subject = new Subject();
        subject.setSubjectName("Business Analysis and Optimization");
        subject.setSubjectDesc("understand the Internal and external factors that impact the business strategy");

        return subject;
    }

    private static Branch getBranch1() {
        Branch branch = new Branch();
        branch.setBranchName("Computer Science and Engineering");
        branch.setBranchShortName("CSE");
        branch.setDescription("CSE department offers courses under ambitious curricula in computer science and computer engineering..");

        return branch;
    }

    private static Branch getBranch2() {
        Branch branch = new Branch();
        branch.setBranchName("Information Technology");
        branch.setBranchShortName("IT");
        branch.setDescription("IT is the business side of computers - usually dealing with databases, business, and accounting");

        return branch;
    }
}

生产环境优化方案(方式2)

如果需要避免重复执行代码导致的数据冗余,可先查询数据库再创建实例,核心逻辑示例:

// 查询是否已存在同名Subject,不存在则创建
Subject existingSubject = entityManager.createQuery(
    "SELECT s FROM Subject s WHERE s.subjectName = :name", Subject.class)
    .setParameter("name", "Software Engineering")
    .getResultStream()
    .findFirst()
    .orElseGet(() -> getSubject1());

内容的提问来源于stack exchange,提问作者kverma28

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 01:15:50