情景復現
這周在寫issue遇到一個需求,培訓通知和大綱是@ManyToOne的關係,需要在創建培訓通知的時候,大綱是選填項,即大綱可以為空。
在實施過程中,就遇到了這個問題。
object references an unsaved transient instance - save the transient instance before flushing
問題分析
該問題是在説:對象引用了一個未保存還是瞬時態的實例,它希望我們在事務flushing之前先保存這個瞬時態的實例。
用簡單的實體描述問題就是:
@Entity
public class A {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ManyToOne
private B b;
}
A a = new A();
B b = new B();
aRepository.save(a); // 由於a中有b 關聯字段,但是在保存a的時候,a.b這個字段的值需要在B實體表能夠找到一條數據。
解決問題
-
我們可以採用在保存a之前先保存b實體。
A a = new A();
B b = new B();
aRepository.save(b);
a.b = b;
aRepository.save(a); -
我們可以在註解@ManyToOne裏添加cascade = CascadeType.ALL設置。該設置了級聯的自動操作,也就是在A保存的之前會自動保存B。
@Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; @ManyToOne(cascade = CascadeType.ALL) private B b; }拓展
JPA/Hibernate 中實體的狀態
- Transient(瞬時):剛new出來的對象,未持久化到數據庫,未分配到ID,。在此狀態下,對實體所做的更改不會持久保存到數據庫中。
- Persistent (持久狀態):實體已經存入到數據庫中,已有ID,能通過find()函數在數據庫中檢索到實體,它處於持久狀態。在這種狀態下對實體所做的任何更改都將在持久上下文刷新時自動與數據庫同步。
- Detached(分離狀態):當實體之前與持久性上下文關聯但當前未與任何持久性上下文關聯時,該實體處於分離狀態。在此狀態下,必須通過將實體重新附加到持久性上下文或將更改合併回新的持久性上下文來顯式管理對實體所做的更改。
- Removed (刪除狀態):該實體被持久的狀態remove()刪除。
實體狀態之間的轉換
// todo代碼示例
下面我們看一下spring框架中的save方法和delete方法的源代碼:
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
數據的save 操作就是將瞬時態的對象變更為持久態。
@Transactional
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (!this.entityInformation.isNew(entity)) {
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = this.em.find(type, this.entityInformation.getId(entity));
if (existing != null) {
this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
}
}
}
delete操作就是把持久態的數據變更為刪除態。