我有一个JPA持久化对象模型,它包含多对一关系:一个帐户有很多交易 . 交易有一个帐户 .
这是代码的片段:
@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对象 . 但是,当我创建一个事务,使用现有已经存在的帐户,并持久化事务时,我得到一个例外:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
因此,我能够持久保存包含交易的账户,但不能持有具有账户的交易 . 我认为这是因为帐户可能没有附加,但是这段代码仍然给了我同样的例外:
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);
如何正确保存与已持久的Account对象关联的事务?
11 回答
如果没有任何帮助,并且您仍然遇到此异常,请查看您的
equals()
方法 - 并且不要在其中包含子集合 . 特别是如果你有深入的嵌入式集合结构(例如A包含Bs,B包含Cs等) .在
Account -> Transactions
的示例中:在上面的示例中,从
equals()
检查中删除事务 . 这是因为hibernate将暗示您不是在尝试更新旧对象,而是在更改子集合上的元素时传递新对象以保持持久性 .当然,此解决方案不适合所有应用程序,您应该仔细设计要包含在
equals
和hashCode
方法中的内容 .不要将id(pk)传递给persist方法或尝试使用save()方法而不是persist() .
这是典型的双向一致性问题 . 它在this link以及this link.中得到了很好的讨论 .
根据前两个链接中的文章,您需要在双向关系的两侧修复您的setter . 一方的示例设置器位于this link.
多边的示例设置器位于this link.
在更正setter之后,您要将Entity访问类型声明为“Property” . 声明“属性”访问类型的最佳做法是将所有注释从成员属性移动到相应的getter . 值得注意的是,不要在实体类中混合使用“Field”和“Property”访问类型,否则JSR-317规范不会定义行为 .
您需要为每个帐户设置交易 .
或者它的colud足够(如果合适的话)在许多方面将id设置为null .
也许这是OpenJPA的错误,当回滚它重置@Version字段时,但pcVersionInit保持为真 . 我有一个AbstraceEntity,它声明了@Version字段 . 我可以通过重置pcVersionInit字段来解决它 . 但这不是一个好主意 . 我认为当有级联持久化实体时它不起作用 .
在您的实体定义中,您没有为加入
Transaction
的Account
指定@JoinColumn . 你会想要这样的东西:编辑:嗯,我想如果你在你的 class 上使用
@Table
注释那将是有用的 . 嘿 . :)即使您的注释被正确声明以正确管理一对多关系,您仍可能遇到此精确异常 . 将新的子对象
Transaction
添加到附加的数据模型时,您需要管理主键值 - 除非您不应该这样做 . 如果在调用persist(T)
之前为声明如下的子实体提供主键值,则会遇到此异常 .在这种情况下,注释声明数据库将在插入时管理实体的主键值的生成 . 自己提供一个(例如通过Id的setter)会导致此异常 .
或者,但实际上相同,此注释声明会导致相同的异常:
因此,当应用程序代码已经被管理时,请不要在应用程序代码中设置
id
值 .解决方案很简单,只需使用
CascadeType.MERGE
而不是CascadeType.PERSIST
或CascadeType.ALL
.我遇到了同样的问题,
CascadeType.MERGE
对我有用 .我希望你排序 .
使用合并是冒险和棘手的,因此在您的情况下这是一个肮脏的解决方法 . 至少需要记住,当您将实体对象传递给合并时,它会停止附加到事务,而是返回一个新的,现在附加的实体 . 这意味着如果任何人仍然拥有旧的实体对象,则对它的更改会被默认忽略并在提交时被丢弃 .
您没有在此处显示完整的代码,因此我无法仔细检查您的交易模式 . 达到这种情况的一种方法是,如果在执行合并并持久化时没有活动事务 . 在这种情况下,持久性提供程序应为您执行的每个JPA操作打开一个新事务,并在调用返回之前立即提交并关闭它 . 如果是这种情况,合并将在第一次运行事务然后在merge方法返回之后,事务完成并关闭,并且返回的实体现在已分离 . 然后,它下面的持久化将打开第二个事务,并尝试引用一个已分离的实体,并给出异常 . 除非您非常清楚自己在做什么,否则始终将代码包装在事务中 .
使用容器管理的事务,它看起来像这样 . 请注意:这假设该方法位于会话bean中并通过本地或远程接口调用 .
可能在这种情况下,您使用合并逻辑获取了
account
对象,并且persist
用于持久保存新对象,如果层次结构具有已持久化的对象,它将会抱怨 . 在这种情况下,您应该使用saveOrUpdate
,而不是persist
.