【问题标题】:JPA many-to-one relationship CascadeType behaviorJPA 多对一关系 CascadeType 行为
【发布时间】:2025-11-22 09:55:02
【问题描述】:

我有 2 张桌子:UserLoan。用户有 3 个字段:id (PK)、first_namelast_nameLoan 表有字段 user_idUser 表的外键:

我的应用程序的逻辑:当我创建新的Loan 对象并在那里设置相应的user 时,它应该为唯一用户创建一个新对象,或者将userid 设置为user_id现有用户。

为此,我的数据库在first_namelast_name 上有一个唯一索引。

Loan 类使用User@ManyToOne 关系:

public class Loan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name = "user_id")
    private User user;
... methods ...

当我添加新用户时,一切都很好,它通过新的 PK 持续存在于数据库中。但是当我尝试添加一个现有的时,我得到了一个例外:

javax.servlet.ServletException: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'FIRST_LAST_NAME' defined on 'USER'.
Error Code: 20000

当我再次输入@ManyToOne(cascade = CascadeType.MERGE) 时,我只能输入现有用户,因为只有我传递了一个未保存在数据库中的新用户,所以我遇到了一个异常:

javax.servlet.ServletException: org.springframework.dao.InvalidDataAccessApiUsageException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: User{id=null, firstName='a', lastName='a'}.;

@ManyToOne(cascade = CascadeType.ALL) 效果不佳。

更新

插入新Loan的代码是:

user = userService.findByName(firstName, lastName);
        if (user == null) {
            user = new User(firstName, lastName);
        }
        loan.setUser(user);
        loanService.save(loan);

findByName()save() 方法是:

private EntityManager em;    
public User findByName(String firstName, String lastName) {
            TypedQuery<User> query = em.createQuery(
                    "SELECT u FROM User u WHERE u.firstName = :firstName " +
                            "AND u.lastName = :lastName", User.class)
                    .setParameter("firstName", firstName).setParameter("lastName", lastName);
            List<User> users = query.getResultList();
            if (!users.isEmpty()) {
                return users.iterator().next();
            } else {
                return null;
            }
        }

     public void save(User user) {
            if (user.getId() == null) {
                em.persist(user);
            } else {
                em.merge(user);
            }
        }

【问题讨论】:

  • 向我们展示您的插入代码。
  • 请同时发布用户类的代码。
  • 我更新了问题,谢谢
  • 我不太确定问题或您在做什么,因为您的“储蓄”是为用户而不是贷款。
  • 使用 Cascade.All 时出现什么错误,什么时候出现异常?

标签: java jpa persistence eclipselink


【解决方案1】:

如果您调用 Persist on Loan a)在用户关系上设置 cascade.Persist 并且引用的用户存在,如果未从同一 EntityManager 实例/上下文中读取用户实例,则设置您将获得异常 - 您正在通过 userService 读取它,然后保存贷款服务中的贷款,因此上下文必须由容器管理并在同一事务中工作。

b) 如果未设置 cascade.PERSIST(或 cascade.ALL)且引用用户为新用户,则会出现“找到新对象”异常。

如果您无法在要保存贷款的同一个 EntityManager 中执行用户读取,则切换到使用合并可能会有所帮助,因为 JPA 应检查引用的实体并酌情合并。然后,您需要在关系上设置 cascade.MERGE 选项,以便将合并应用于用户实例。

请注意,使用 merge 与 Persist 的不同之处在于您传入的内容不会由上下文管理。这意味着在事务提交后,传入的实体仍然不会设置任何主键值。如果您要继续将实体用于任何其他操作,您可能需要传回从 Merge 返回的实体。

【讨论】:

  • 感谢克里斯的详细回答。我认为你是对的 - 问题是使用了 2 个不同的 EntityManager 实例。我会尝试在同一个实例中执行操作。
最近更新 更多