我有一个包含多对一关系的jpa持久化对象模型:一个Account有多个transaction。一个事务有一个帐户。
下面是一段代码:
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
我能够创建Account对象,向其添加事务,并正确地持久化Account对象。但是,当我创建一个事务,使用现有的已经持久化的帐户,并持久化的事务,我得到一个异常:
导致:org.hibernate.PersistentObjectException:传递给persist: com.paulsanwald.Account的分离实体
org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java: 141)
因此,我能够持久化一个包含事务的Account,但不能持久化一个具有Account的Transaction。我认为这是因为帐户可能没有附加,但这段代码仍然给了我相同的异常:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
如何正确地保存与已经持久化的帐户对象相关联的事务?
即使正确地声明了注释以正确地管理一对多关系,您仍然可能遇到这种异常。当向附加的数据模型添加新的子对象Transaction时,您需要管理主键值—除非您不需要这样做。如果在调用persist(T)之前为如下声明的子实体提供主键值,则会遇到此异常。
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
在本例中,注释声明数据库将在插入时管理实体主键值的生成。自己提供一个(比如通过Id的setter)会导致此异常。
或者,但实际上是一样的,这个注释声明会导致相同的异常:
@Entity
public class Transaction {
@Id
@org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
@GeneratedValue(generator="system-uuid")
private Long id;
....
因此,当应用程序代码已经被管理时,不要在应用程序代码中设置id值。
从子实体Transaction中移除级联,它应该是:
@Entity class Transaction {
@ManyToOne // no cascading here!
private Account account;
}
(FetchType。EAGER可以被删除,它是默认的@ManyToOne)
这是所有!
为什么?通过在子实体Transaction上说“cascade ALL”,你要求每个DB操作都被传播到父实体Account。如果执行持久化(事务),也会调用持久化(帐户)。
但是只有暂时的(新的)实体可以被传递给持久化实体(在本例中是事务)。分离的(或其他非瞬态)可能不会(在本例中是Account,因为它已经在DB中)。
因此,您会得到异常“传递给持久化的分离实体”。Account实体的意思是!而不是调用持久化的事务。
一般来说,你不希望从子繁殖到父。不幸的是,在书中(甚至是很好的书)和网上有许多代码示例,它们正是这样做的。我不知道,为什么……也许有时只是一遍又一遍地复制,没有多想……
猜猜如果你调用remove(transaction)在@ManyToOne中仍然有“级联ALL”会发生什么?帐户(顺便说一下,所有其他交易!)也将从数据库中删除。但这不是你的本意,对吧?
移除子关联级联
因此,您需要删除@CascadeType。这些都来自@ManyToOne协会。子实体不应该级联到父关联。只有父实体应该级联到子实体。
@ManyToOne(fetch= FetchType.LAZY)
注意,我将fetch属性设置为FetchType。LAZY,因为急切抓取对性能非常不利。
设置关联的双方
当你有一个双向关联时,你需要在父实体中使用addChild和removecchild方法来同步双方:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}