簡體   English   中英

如何修復 Hibernate“對象引用未保存的瞬態實例 - 在刷新前保存瞬態實例”錯誤

[英]How to fix the Hibernate "object references an unsaved transient instance - save the transient instance before flushing" error

當我使用 Hibernate 保存 object 時收到以下錯誤

object references an unsaved transient instance - save the transient instance before flushing

你應該在你的集合映射中包含cascade="all" (如果使用xml)或cascade=CascadeType.ALL (如果使用注釋)。

發生這種情況是因為您的實體中有一個集合,並且該集合具有一個或多個數據庫中不存在的項目。 通過指定上述選項,您可以告訴 hibernate 在保存它們的父項時將它們保存到數據庫中。

我相信這可能只是重復的答案,但只是為了澄清,我在@OneToOne映射和@OneToMany上得到了這個。 在這兩種情況下,事實是我添加到ParentChild對象尚未保存在數據庫中。 因此,當我將Child添加到Parent ,然后保存Parent ,Hibernate 會在保存Parent拋出"object references an unsaved transient instance - save the transient instance before flushing"消息。

Parent'sChild Parent's引用上添加cascade = {CascadeType.ALL}解決了這兩種情況下的問題。 這拯救了ChildParent

對不起,任何重復的答案,只是想為人們進一步澄清。

@OneToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "performancelog_id")
public PerformanceLog getPerformanceLog() {
    return performanceLog;
}

介紹

使用 JPA 和 Hibernate 時,實體可以處於以下 4 種狀態之一:

  • New - 從未與 Hibernate Session(又名 Persistence Context)關聯且未映射到任何數據庫表行的新創建的對象被視為處於 New 或 Transient 狀態。

要成為持久化,我們需要顯式調用persist方法或利用傳遞持久化機制。

  • 持久性- 持久性實體已與數據庫表行相關聯,並且由當前運行的持久性上下文管理。

將檢測到對此類實體所做的任何更改並將其傳播到數據庫(在會話刷新時間期間)。

  • 分離- 一旦當前運行的持久性上下文關閉,所有以前管理的實體都將分離。 將不再跟蹤后續更改,也不會發生自動數據庫同步。

  • Removed - 盡管 JPA 要求只允許刪除托管實體,但 Hibernate 也可以刪除分離的實體(但只能通過remove方法調用)。

實體狀態轉換

要將實體從一種狀態移動到另一種狀態,您可以使用persistremovemerge方法。

JPA 實體狀態

解決問題

您在問題中描述的問題:

object references an unsaved transient instance - save the transient instance before flushing

是由於將處於New狀態的實體與處於Managed狀態的實體相關聯引起的。

當您將子實體與父實體中的一對多集合相關聯時,可能會發生這種情況,並且該集合不會cascade實體狀態轉換。

因此,您可以通過向觸發此故障的實體關聯添加級聯來解決此問題,如下所示:

@OneToOne關聯

@OneToOne(
    mappedBy = "post",
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private PostDetails details;

請注意我們為cascade屬性添加的CascadeType.ALL值。

@OneToMany關聯

@OneToMany(
    mappedBy = "post", 
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();

同樣, CascadeType.ALL適用於雙向@OneToMany關聯。

現在,為了讓級聯在雙向中正常工作,您還需要確保父子關聯是同步的。

@ManyToMany關聯

@ManyToMany(
    mappedBy = "authors",
    cascade = {
        CascadeType.PERSIST, 
        CascadeType.MERGE
    }
)
private List<Book> books = new ArrayList<>();

@ManyToMany關聯中,您不能使用CascadeType.ALLorphanRemoval因為這會將刪除實體狀態轉換從一個父實體傳播到另一個父實體。

因此,對於@ManyToMany關聯,您通常會級聯CascadeType.PERSISTCascadeType.MERGE操作。 或者,您可以將其擴展為DETACHREFRESH

當 Hibernate 認為它需要保存與您正在保存的對象相關聯的對象時,會在保存對象時發生這種情況。

我遇到了這個問題,不想保存對引用對象的更改,所以我希望級聯類型為 NONE。

訣竅是確保引用對象中的 ID 和 VERSION 已設置,以便 Hibernate 不會認為引用的對象是需要保存的新對象。 這對我有用。

查看您正在保存的類中的所有關系以計算出關聯對象(以及關聯對象的關聯對象),並確保在對象樹的所有對象中設置了 ID 和 VERSION。

或者,如果您想使用最小的“權力”(例如,如果您不想級聯刪除)來實現您想要的,請使用

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

...

@Cascade({CascadeType.SAVE_UPDATE})
private Set<Child> children;

就我而言,這是由於雙向關系的@ManyToOne一側沒有CascadeType引起的。 更准確地說,我在@OneToMany端有CascadeType.ALL@OneToMany@ManyToOne CascadeType.ALL添加到@ManyToOne解決了該問題。 一對多側:

@OneToMany(cascade = CascadeType.ALL, mappedBy="globalConfig", orphanRemoval = true)
private Set<GlobalConfigScope>gcScopeSet;

多對一(導致問題)

@ManyToOne
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

多對一(通過添加CascadeType.PERSIST修復)

@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

這發生在我持久化一個實體時,其中數據庫中的現有記錄對於用@Version(用於樂觀鎖定)注釋的字段具有 NULL 值。 將數據庫中的 NULL 值更新為 0 更正了這一點。

這不是錯誤的唯一原因。 我剛剛在編碼中遇到了拼寫錯誤,我相信它設置了一個已經保存的實體的值。

X x2 = new X();
x.setXid(memberid); // Error happened here - x was a previous global entity I created earlier
Y.setX(x2);

我通過准確找出導致錯誤的變量(在本例中為String xid )來發現錯誤。 我在整個代碼塊周圍使用了一個catch ,用於保存實體並打印跟蹤。

{
   code block that performed the operation
} catch (Exception e) {
   e.printStackTrace(); // put a break-point here and inspect the 'e'
   return ERROR;
}

不要使用Cascade.All除非你真的需要。 RolePermission具有雙向manyToMany關系。 然后下面的代碼可以正常工作

    Permission p = new Permission();
    p.setName("help");
    Permission p2 = new Permission();
    p2.setName("self_info");
    p = (Permission)crudRepository.save(p); // returned p has id filled in.
    p2 = (Permission)crudRepository.save(p2); // so does p2.
    Role role = new Role();
    role.setAvailable(true);
    role.setDescription("a test role");
    role.setRole("admin");
    List<Permission> pList = new ArrayList<Permission>();
    pList.add(p);
    pList.add(p2);
    role.setPermissions(pList);
    crudRepository.save(role);

而如果對象只是一個“新”對象,那么它會拋出相同的錯誤。

如果您的集合可以為空,請嘗試: object.SetYouColection(null);

除了所有其他好的答案之外,如果您使用merge來持久化對象並且不小心忘記在父類中使用對象的合並引用,則可能會發生這種情況。 考慮下面的例子

merge(A);
B.setA(A);
persist(B);

在這種情況下,您合並A但忘記使用A合並對象。 要解決這個問題,你必須像這樣重寫代碼。

A=merge(A);//difference is here
B.setA(A);
persist(B);

當我在標記為@Transactional的方法中創建一個新實體和一個關聯實體,然后在保存之前執行查詢時,這個問題發生在我身上。 前任

@Transactional
public someService() {
    Entity someEntity = new Entity();
    AssocaiatedEntity associatedEntity = new AssocaitedEntity();
    someEntity.setAssociatedEntity(associatedEntity);
    associatedEntity.setEntity(someEntity);

    // Performing any query was causing hibernate to attempt to persist the new entity. It would then throw an exception
    someDao.getSomething();

    entityDao.create(someEntity);
}

為了解決這個問題,我在創建新實體之前執行了查詢。

為了增加我的 2 美分,當我不小心發送null作為 ID 時,我遇到了同樣的問題。 下面的代碼描述了我的場景(OP 沒有提到任何特定場景)

Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // --> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);

在這里,我將現有部門 ID 設置為新員工實例,而沒有先實際獲取部門實體,因為我不想觸發另一個選擇查詢。

在某些情況下,調用方法時deptId PKID 為null ,我遇到了相同的錯誤。

因此,注意 PK ID 的null

我在使用時收到此錯誤

getSession().save(object)

但是當我使用時它沒有問題

getSession().saveOrUpdate(object) 

為完整起見:A

org.hibernate.TransientPropertyValueException 

有消息

object references an unsaved transient instance - save the transient instance before flushing

當您嘗試保留/合並一個實體並引用另一個碰巧分離的實體時,也會發生這種情況。

我也面臨同樣的情況。 通過在屬性上方設置以下注釋,可以解決提示的異常。

我遇到的異常。

Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.model.Car_OneToMany

為了克服,我使用的注釋。

    @OneToMany(cascade = {CascadeType.ALL})
    @Column(name = "ListOfCarsDrivenByDriver")
    private List<Car_OneToMany> listOfCarsBeingDriven = new ArrayList<Car_OneToMany>();

是什么讓 Hibernate 拋出異常:

這個異常是在你的控制台上拋出的,因為我附加到父對象的子對象當時不在數據庫中。

通過提供@OneToMany(cascade = {CascadeType.ALL}) ,它告訴 Hibernate 在保存父對象的同時將它們保存到數據庫中。

當您擁有 OneToMany 關系並且您嘗試將子實體添加到父實體的列表中,然后通過父實體檢索此列表(在保存此父實體之前)而不保存子實體本身時,也會發生這種情況,例如:

Child childEntity = new Child();
parentEntity.addChild(childEntity);
parentEntity.getChildren(); // I needed the retrieval for logging, but one may need it for other reasons.
parentRepository.save(parentEntity);

當我保存父實體時拋出錯誤。 如果我刪除了前一行中的檢索,則不會拋出錯誤,但這當然不是解決方案。

解決方案是保存childEntity並加入救孩子實體父實體,就像這樣:

Child childEntity = new Child();
Child savedChildEntity = childRepository.save(childEntity);
parentEntity.addChild(savedChildEntity);
parentEntity.getChildren();
parentRepository.save(parentEntity);

另一個可能的原因:就我而言,我試圖在一個全新的實體上拯救孩子,然后再拯救父母。

User.java 模型中的代碼是這樣的:

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.setNewPassword(password);
this.timeJoin = new Date();
create();

setNewPassword() 方法創建一個 PasswordHistory 記錄並將其添加到 User 中的歷史記錄集合中。 由於尚未為父級執行 create() 語句,因此它試圖保存到尚未創建的實體的集合中。 我所要做的就是在調用 create() 之后移動 setNewPassword() 調用來修復它。

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.timeJoin = new Date();
create();
this.setNewPassword(password);

還有另一種可能會在休眠中導致此錯誤。 您可以將對象A的未保存引用設置為附加實體B並希望保留對象C 即使在這種情況下,您也會收到上述錯誤。

這個錯誤有很多可能性,在添加頁面或編輯頁面上還有其他一些可能性。 就我而言,我試圖保存一個對象 AdvanceSalary。 問題是在編輯 AdvanceSalary employee.employee_id 是空的,因為在編輯時我沒有設置employee.employee_id。 我制作了一個隱藏字段並設置了它。 我的代碼工作得很好。

    @Entity(name = "ic_advance_salary")
    @Table(name = "ic_advance_salary")
    public class AdvanceSalary extends BaseDO{

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Integer id;

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "employee_id", nullable = false)
        private Employee employee;

        @Column(name = "employee_id", insertable=false, updatable=false)
        @NotNull(message="Please enter employee Id")
        private Long employee_id;

        @Column(name = "advance_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        @NotNull(message="Please enter advance date")
        private Date advance_date;

        @Column(name = "amount")
        @NotNull(message="Please enter Paid Amount")
        private Double amount;

        @Column(name = "cheque_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        private Date cheque_date;

        @Column(name = "cheque_no")
        private String cheque_no;

        @Column(name = "remarks")
        private String remarks;

        public AdvanceSalary() {
        }

        public AdvanceSalary(Integer advance_salary_id) {
            this.id = advance_salary_id;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public Employee getEmployee() {
            return employee;
        }

        public void setEmployee(Employee employee) {
            this.employee = employee;
        }


        public Long getEmployee_id() {
            return employee_id;
        }

        public void setEmployee_id(Long employee_id) {
            this.employee_id = employee_id;
        }

    }

如果您使用的是 Spring Data JPA,那么在您的服務實現中添加@Transactional注釋將解決該問題。

我認為是因為您嘗試持久化一個對象,該對象引用另一個尚未持久化的對象,因此它嘗試在“數據庫端”中放置對不存在的行的引用

案例 1:當我嘗試創建父項並將該父項引用保存到其子項,然后是其他一些 DELETE/UPDATE 查詢 (JPQL) 時,我遇到了此異常。 所以我只是在創建父級和使用相同的父級引用創建子級之后刷新()新創建的實體。 它對我有用。

案例2:

父類

public class Reference implements Serializable {

    @Id
    @Column(precision=20, scale=0)
    private BigInteger id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date modifiedOn;

    @OneToOne(mappedBy="reference")
    private ReferenceAdditionalDetails refAddDetails;
    . 
    .
    .
}

兒童班:

public class ReferenceAdditionalDetails implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @OneToOne
    @JoinColumn(name="reference",referencedColumnName="id")
    private Reference reference;

    private String preferedSector1;
    private String preferedSector2;
    .
    .

}

在上述情況下,父(Reference) 和子(ReferenceAdditionalDetails) 具有OneToOne 關系,當您嘗試創建Reference 實體然后創建其子實體(ReferenceAdditionalDetails) 時,它會給您相同的異常。 因此,為了避免異常,您必須為子類設置 null,然后創建父類。(示例代碼)

.
.
reference.setRefAddDetails(null);
reference = referenceDao.create(reference);
entityManager.flush();
.
.

就我而言,問題完全不同。 我有兩個班級,比如說 c1 和 c2。 C1 和 C2 之間的依賴是 OneToMany。 現在,如果我將 C1 保存在 DB 中,它會拋出上述錯誤。

這個問題的解決方案是首先從消費者請求中獲取 C2 的 id 並通過存儲庫調用找到 C2。然后將 c2 保存到 C1 對象中。現在如果我正在保存 C1,它工作正常。

在引入樂觀鎖定(@Version)后,我對所有 PUT HTTP 事務都面臨同樣的錯誤

在更新實體時,必須發送該實體的 ID 和版本。 如果任何實體字段與其他實體相關,那么對於該字段,我們也應該提供 id 和 version 值,而不是 JPA 嘗試首先將該相關實體作為新實體持久化

示例:我們有兩個實體 --> Vehicle (id,Car,version) ; 汽車(ID、版本、品牌); 更新/保留 Vehicle 實體,確保 Vehicle 實體中的 Car 字段提供了 id 和 version 字段

解決這個問題的簡單方法是保存兩個實體。 先保存子實體,再保存父實體。 因為父實體依賴於子實體的外鍵值。

下面簡單考查一對一的關系

insert into Department (name, numOfemp, Depno) values (?, ?, ?)
Hibernate: insert into Employee (SSN, dep_Depno, firstName, lastName, middleName, empno) values (?, ?, ?, ?, ?, ?)

Session session=sf.openSession();
        session.beginTransaction();
        session.save(dep);
        session.save(emp);

錯誤的一種可能原因是父實體的值設置不存在; 例如,對於部門與員工的關系,您必須這樣寫才能修復錯誤:

Department dept = (Department)session.load(Department.class, dept_code); // dept_code is from the jsp form which you get in the controller with @RequestParam String department
employee.setDepartment(dept);

當我沒有堅持父對象但我正在保存孩子時,我遇到了這個異常。 為了解決這個問題,在同一個會話中,我同時保留了子對象和父對象,並在父對象上使用了 CascadeType.ALL。

我的問題與 JUnit 的@BeforeEach相關。 即使我保存了相關實體(在我的例子中是@ManyToOne ),我也遇到了同樣的錯誤。

這個問題在某種程度上與我父母的順序有關。 如果我將值分配給該屬性,問題就解決了。

前任。 如果我有可以有一些類別(一個或多個)的實體 Question 並且實體 Question 有一個序列:

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "feedbackSeq")
@Id
private Long id;

我必須分配值question.setId(1L);

只需在基類中創建映射的構造函數即可。 就像你想在實體 A、實體 B 中建立一對一關系一樣。如果你把 A 作為基類,那么 A 必須有一個構造函數,將 B 作為參數。

使用 Hibernate 保存對象時收到以下錯誤

object references an unsaved transient instance - save the transient instance before flushing

在我的情況下,當我嘗試使用對具有 null id 的實體的引用來檢索相關實體時,就會發生這種情況。

@Entity
public class User {
@Id
private Long id;
}

@Entity
public class Address {
@Id
private Long id;
@JoinColumn(name="user_id")
@OneToOne
private User user;
}

interface AddressRepository extends JpaRepository<Address, Long> {
Address findByUser(User user);
}

User user = new User(); // this is transient, does not have id populated
// user.setId(1L) // works fine if this is uncommented

addressRepository.findByUser(user); // throws exception

我剛剛收到此錯誤,因為我在保存之前設置了一個未代理的實體而不是另一個實體。

我應該鏈接一個代理實體實例。

請看下面的解釋:

Child saved = childRepository.save(child);

// INCORRECT
parent.setChild(child);  // <-- 'child' is NOT managed (not proxied)

// CORRECT
parent.setChild(saved);  // <-- 'saved' is managed (proxied)

我是彈簧靴的新手。 我有兩個實體,當我執行時,收到此錯誤:“對象引用未保存的瞬態實例 - 在刷新之前保存瞬態實例”

我的springboot版本:2.5.7

下面給出兩個實體類:

@Entity
public class Topic {

    @Id
    private String id;
    private String name;
    private String description;

    public Topic() {

    }

    public Topic(String id, String name, String description) {
        super();
        this.id = id;
        this.name = name;
        this.description = description;
    }

@Entity
public class Course {
    
    @Id
    private String id;
    private String name;
    private String description;

    @ManyToOne
    private Topic topic;

    public Course() {

    }

    public Course(String id, String name, String description, String topicId) {
        super();
        this.id = id;
        this.name = name;
        this.description = description;
        this.topic = new Topic(topicId,"","");
    }

不知道我該如何解決這個問題。 有人可以幫忙嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM