我有一个包含多对一关系的 JPA 持久对象模型:一个 Account
有多个 Transactions
。 Transaction
有一个 Account
。
这是代码片段:
@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:分离实体传递给持久化:com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
因此,我能够保留包含事务的 Account
,但不能保留具有 Account
的事务。我认为这是因为可能没有附加 Account
,但这段代码仍然给了我同样的异常:
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
对象关联的 Transaction
?
解决方案很简单,只需使用 CascadeType.MERGE
而不是 CascadeType.PERSIST
或 CascadeType.ALL
。
我遇到了同样的问题,CascadeType.MERGE
对我有用。
我希望你被排序。
这是一个典型的双向一致性问题。它在 this link 和 this link. 中得到了很好的讨论
根据前面 2 个链接中的文章,您需要在双向关系的两侧修复您的设置器。一侧的示例设置器位于 this link.
多方的示例设置器位于 this link.
更正设置器后,您希望将实体访问类型声明为“属性”。声明“属性”访问类型的最佳实践是将所有注释从成员属性移动到相应的 getter。一个重要的词是不要在实体类中混合“字段”和“属性”访问类型,否则 JSR-317 规范未定义行为。
detached entity passed to persist
为什么提高一致性会使其发挥作用?好的,一致性已修复,但对象仍然分离。
从子实体 Transaction
中删除级联,它应该只是:
@Entity class Transaction {
@ManyToOne // no cascading here!
private Account account;
}
(FetchType.EAGER
可以删除,它是 @ManyToOne
的默认值)
就这样!
为什么?通过对子实体 Transaction
说“全部级联”,您要求每个数据库操作都传播到父实体 Account
。如果您随后执行 persist(transaction)
,则 persist(account)
也将被调用。
但只能将临时(新)实体传递给 persist
(在本例中为 Transaction
)。分离的(或其他非瞬态状态)可能不是(在这种情况下为 Account
,因为它已经在 DB 中)。
因此,您会得到异常“传递给持久化的分离实体”。 Account
实体是指!不是您调用 persist
的 Transaction
。
您通常不希望从孩子传播到父母。不幸的是,书籍(甚至是好书)和网络中有很多代码示例,它们正是这样做的。我不知道,为什么......也许有时只是一遍又一遍地复制而不加思考......
猜猜如果您调用 remove(transaction)
在 @ManyToOne 中仍然有“cascade ALL”会发生什么? account
(顺便说一句,还有所有其他事务!)也将从数据库中删除。但这不是你的本意,是吗?
不要将 id(pk) 传递给 persist 方法或尝试使用 save() 方法而不是 persist()。
TestEntityManager.persistAndFlush()
使实例受管理和持久化,然后将持久性上下文同步到底层数据库。返回原始源实体
删除子关联级联
因此,您需要从 @ManyToOne
关联中删除 @CascadeType.ALL
。子实体不应级联到父关联。只有父实体应该级联到子实体。
@ManyToOne(fetch= FetchType.LAZY)
请注意,我将 fetch
属性设置为 FetchType.LAZY
,因为急切获取对性能非常不利。
设置关联的双方
每当您有双向关联时,都需要使用父实体中的 addChild
和 removeChild
方法同步双方:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}
this
引用会怎样? void prePersist(){ transactions.foreach( t -> t.setAccount(this))
使用合并既冒险又棘手,因此在您的情况下这是一个肮脏的解决方法。您至少需要记住,当您将实体对象传递给合并时,它会停止附加到事务,而是返回一个新的、现在附加的实体。这意味着如果任何人仍然拥有旧的实体对象,对它的更改将被默默地忽略并在提交时被丢弃。
你没有在这里显示完整的代码,所以我不能仔细检查你的交易模式。解决这种情况的一种方法是,如果您在执行合并和持久化时没有活动的事务。在这种情况下,持久性提供程序应该为您执行的每个 JPA 操作打开一个新事务,并在调用返回之前立即提交和关闭它。如果是这种情况,合并将在第一个事务中运行,然后在合并方法返回后,事务完成并关闭,返回的实体现在被分离。然后,它下面的持久化将打开第二个事务,并尝试引用一个已分离的实体,从而给出异常。除非您非常清楚自己在做什么,否则请始终将您的代码包装在事务中。
使用容器管理的事务,它看起来像这样。请注意:这假定该方法位于会话 bean 内并通过本地或远程接口调用。
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
...
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
entityManager.persist(account);
}
@Transaction(readonly=false)
。我仍然遇到同样的问题,
可能在这种情况下,您使用合并逻辑获得了 account
对象,并且 persist
用于持久化新对象,如果层次结构具有已持久化的对象,它会报错。在这种情况下,您应该使用 saveOrUpdate
而不是 persist
。
transaction
对象上使用 merge
你会得到同样的错误吗?
transaction
吗?你是怎么查的。请注意,merge
返回对持久对象的引用。
一个老问题,但最近遇到了同样的问题。在这里分享我的经验。
实体
@Data
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GeneratedValue
private Long id;
}
保存实体 (JUnit)
Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
使固定
Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
结论:如果实体类具有主键(id)的@GeneratedValue,则确保您没有传递主键(id)的值
我基于 Spring Data JPA 的答案:我只是在我的外部方法中添加了一个 @Transactional
注释。
为什么有效
由于没有活动的 Hibernate Session 上下文,子实体立即变得分离。提供 Spring (Data JPA) 事务可确保存在 Hibernate Session。
参考:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
@Transactional
方法拆分为两个单独的 @Transactional
方法才能正常工作。
如果没有任何帮助并且您仍然遇到此异常,请检查您的 equals()
方法 - 并且不要在其中包含子集合。特别是如果你有深层的嵌入式集合结构(例如 A 包含 Bs,B 包含 Cs 等)。
在 Account -> Transactions
的示例中:
public class Account {
private Long id;
private String accountName;
private Set<Transaction> transactions;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Account))
return false;
Account other = (Account) obj;
return Objects.equals(this.id, other.id)
&& Objects.equals(this.accountName, other.accountName)
&& Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
}
}
在上面的示例中,从 equals()
检查中删除事务。这是因为休眠意味着您不会尝试更新旧对象,而是在您更改子集合上的元素时传递一个新对象以保持不变。
当然,这种解决方案并不适合所有应用程序,您应该小心设计您想要包含在 equals
和 hashCode
方法中的内容。
在您的实体定义中,您没有为连接到 Transaction
的 Account
指定 @JoinColumn。你会想要这样的东西:
@Entity
public class Transaction {
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
@JoinColumn(name = "accountId", referencedColumnName = "id")
private Account fromAccount;
}
编辑:好吧,我想如果您在课堂上使用 @Table
注释会很有用。呵呵。 :)
即使您的注释被正确声明以正确管理一对多关系,您仍然可能会遇到这个精确的异常。将新的子对象 Transaction
添加到附加的数据模型时,您需要管理主键值 - 除非您不应该这样做。如果在调用 persist(T)
之前为声明如下的子实体提供主键值,则会遇到此异常。
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
在这种情况下,注释声明数据库将在插入时管理实体主键值的生成。自己提供一个(例如通过 Id 的设置器)会导致此异常。
或者,但实际上相同,此注释声明导致相同的异常:
@Entity
public class Transaction {
@Id
@org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
@GeneratedValue(generator="system-uuid")
private Long id;
....
因此,请勿在您的应用程序代码中设置 id
值,因为它已经被管理。
也许这是 OpenJPA 的错误,回滚时它会重置 @Version 字段,但 pcVersionInit 保持真实。我有一个 AbstraceEntity 声明了 @Version 字段。我可以通过重置 pcVersionInit 字段来解决它。但这不是一个好主意。我认为当有级联持久实体时它不起作用。
private static Field PC_VERSION_INIT = null;
static {
try {
PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
PC_VERSION_INIT.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
}
}
public T call(final EntityManager em) {
if (PC_VERSION_INIT != null && isDetached(entity)) {
try {
PC_VERSION_INIT.set(entity, false);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
em.persist(entity);
return entity;
}
/**
* @param entity
* @param detached
* @return
*/
private boolean isDetached(final Object entity) {
if (entity instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable) entity;
if (pc.pcIsDetached() == Boolean.TRUE) {
return true;
}
}
return false;
}
您需要为每个账户设置交易。
foreach(Account account : accounts){
account.setTransaction(transactionObj);
}
或者在许多方面将 ids 设置为 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.
cascadeType.MERGE,fetch= FetchType.LAZY
@OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) 为我工作。
通过在下一个之前保存依赖对象来解决。
这发生在我身上,因为我没有设置 Id(不是自动生成的)。并尝试保存关系@ManytoOne
这是我的修复。
下面是我的实体。标记 id 用@GeneratedValue(strategy = GenerationType.AUTO) 注释,这意味着 id 将由 Hibernate 生成。创建实体对象时不要设置它。因为这将由 Hibernate 自动生成。请注意,如果实体 id 字段没有用@GeneratedValue 标记,那么不手动分配 id 值也是一种犯罪,这将遇到 IdentifierGenerationException:必须在调用 save() 之前手动分配此类的 id
@Entity
@Data
@NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String key;
@Column
private String value;
}
这是我的主要课程。
public class SimpleObjectMain {
public static void main(String[] args) {
System.out.println("Hello Hello From SimpleObjectMain");
SimpleObject simpleObject = new SimpleObject();
simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
simpleObject.setKey("Friend");
simpleObject.setValue("Bani");
EntityManager entityManager = EntityManagerUtil.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(simpleObject);
entityManager.getTransaction().commit();
List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
for(SimpleObject simple : simpleObjectList){
System.out.println(simple);
}
entityManager.close();
}
}
当我尝试保存它时,它正在抛出那个
PersistentObjectException: detached entity passed to persist.
我需要修复的只是删除 main 方法中 simpleObject 的 id 设置行。
在我的情况下,当使用持久方法时,我正在提交事务。在更改持久保存方法时,它得到了解决。
如果上述解决方案不起作用,只需注释一次实体类的 getter 和 setter 方法,并且不要设置 id.(Primary key) 的值,那么这将起作用。
我遇到此问题的另一个原因是在事务中具有未由 Hibernate 版本化的实体。
向所有映射实体添加 @Version
注释
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
@Version
private Integer version;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "orders")
private CustomerOrders orders;
}
@Entity
public class CustomerOrders {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
@Version
private Integer version;
private BigDecimal value;
}
此错误来自 JPA 生命周期。要解决,无需使用特定的装饰器。只需像这样使用合并加入实体:
entityManager.merge(transaction);
并且不要忘记正确设置您的 getter 和 setter,以便双方同步。
不定期副业成功案例分享