JPA 多对一关系 CascadeType 行为
JPA many-to-one relationship CascadeType behavior
我有 2 个 table:User
和 Loan
。用户有 3 个字段:id
(PK)、first_name
和 last_name
。 Loan
table 有字段 user_id
是 User
table 的外键:
我的应用程序的逻辑:当我创建新的 Loan
对象并在那里设置相应的 user
时,它应该为唯一用户创建一个新对象或设置一个 id
user
现有用户 user_id
。
为此,我的数据库在 first_name
、last_name
上有一个唯一索引。
Loan
class 使用 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);
}
}
如果你打电话坚持贷款
a) 在用户关系上设置了 cascade.Persist 并且引用的用户存在,如果用户实例不是从同一个 EntityManager instance/context 中读入的,那么设置你将得到一个异常 - 你正在通过 userService 读取它然后将贷款保存在 loanService 中,因此上下文必须是容器管理的并且在同一事务中才能工作。
b) 如果未设置 cascade.PERSIST(或 cascade.ALL)且引用用户是新用户,您将得到 "new object was found" 异常。
如果您无法在您要保存贷款的同一个 EntityManager 中执行用户读取,切换到使用合并可能会有所帮助,因为 JPA 然后应该检查引用的实体并适当地合并。然后,您需要在关系上设置 cascade.MERGE 选项,以便将合并应用到 User 实例。
请注意,使用 merge 与 Persist 不同,因为您传入的内容不会由上下文管理。这意味着在事务提交之后,传入的实体仍然没有设置任何主键值。如果您要继续使用实体进行任何其他操作,您可能希望传回从 Merge 返回的实体。
我有 2 个 table:User
和 Loan
。用户有 3 个字段:id
(PK)、first_name
和 last_name
。 Loan
table 有字段 user_id
是 User
table 的外键:
我的应用程序的逻辑:当我创建新的 Loan
对象并在那里设置相应的 user
时,它应该为唯一用户创建一个新对象或设置一个 id
user
现有用户 user_id
。
为此,我的数据库在 first_name
、last_name
上有一个唯一索引。
Loan
class 使用 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);
}
}
如果你打电话坚持贷款 a) 在用户关系上设置了 cascade.Persist 并且引用的用户存在,如果用户实例不是从同一个 EntityManager instance/context 中读入的,那么设置你将得到一个异常 - 你正在通过 userService 读取它然后将贷款保存在 loanService 中,因此上下文必须是容器管理的并且在同一事务中才能工作。
b) 如果未设置 cascade.PERSIST(或 cascade.ALL)且引用用户是新用户,您将得到 "new object was found" 异常。
如果您无法在您要保存贷款的同一个 EntityManager 中执行用户读取,切换到使用合并可能会有所帮助,因为 JPA 然后应该检查引用的实体并适当地合并。然后,您需要在关系上设置 cascade.MERGE 选项,以便将合并应用到 User 实例。
请注意,使用 merge 与 Persist 不同,因为您传入的内容不会由上下文管理。这意味着在事务提交之后,传入的实体仍然没有设置任何主键值。如果您要继续使用实体进行任何其他操作,您可能希望传回从 Merge 返回的实体。