我有一个包含多对一关系的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);

如何正确地保存与已经持久化的帐户对象相关联的事务?


当前回答

您需要为每个帐户设置事务。

foreach(Account account : accounts){
    account.setTransaction(transactionObj);
}

或者在许多方面将id设置为null就足够了(如果合适的话)。

// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());

foreach(Account account : accounts){
    account.setId(null);
}

transactionObj.setAccounts(accounts);

// just persist transactionObj using EntityManager merge() method.

其他回答

如果上述解决方案不工作,只需一次注释实体类的getter和setter方法,并且不设置id的值。(主键) 这样就可以了。

这是一个典型的双向一致性问题。在这个链接和这个链接中都有很好的讨论。

根据前两个链接中的文章,您需要在双向关系的两侧修复您的setter。一方的示例setter在此链接中。

在此链接中有一个用于多方的示例setter。

在你纠正你的setter之后,你想要声明实体访问类型为“属性”。声明“Property”访问类型的最佳实践是将所有注释从成员属性移动到相应的getter。一个重要的警告是不要在实体类中混合使用“Field”和“Property”访问类型,否则JSR-317规范没有定义行为。

不要将id(pk)传递给persist方法或尝试save()方法而不是persist()。

移除子关联级联

因此,您需要删除@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);
}

从子实体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”会发生什么?帐户(顺便说一下,所有其他交易!)也将从数据库中删除。但这不是你的本意,对吧?