使用Hibernate保存对象时收到以下错误

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

当前回答

我相信这可能只是重复答案,但为了澄清,我在@OneToOne映射和@OneToMany上得到了这个答案。在这两种情况下,我添加到Parent的Child对象尚未保存在数据库中。因此,当我将Child添加到Parent,然后保存Parent时,Hibernate会在保存Parent之前抛出“对象引用未保存的瞬态实例-在刷新之前保存瞬态实例”消息。

在父级对子级的引用上添加cascade={CascadeType.ALL}解决了这两种情况下的问题。这保存了子对象和父对象。

很抱歉有重复的回答,只是想进一步澄清一下。

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

其他回答

不要使用Cascade.All,除非你真的需要。角色和权限具有双向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);

而如果对象只是一个“新”对象,那么它将抛出相同的错误。

在我的例子中,使用Cascade.ALL是在表中添加不必要的条目,表中已经有了相同的对象(生成没有id的子值就可以做到这一点)。所以我必须先从存储库中获取子对象,并将其设置为主对象,而不是导致问题的对象。

Student s = studentRepo.findByName(generatedObject.getStudent().getName())
generatedObject.student(s);   

再加上我的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设置为一个新的雇员实例,而实际上没有首先获取部门实体,因为我不想再启动另一个select查询。

在某些情况下,deptId PKID在调用方法时变为null,我得到了同样的错误。

因此,请注意PK ID的空值

介绍

使用JPA和Hibernate时,实体可以处于以下4种状态之一:

新建-新创建的对象从未与Hibernate会话(也称为持久性上下文)关联,且未映射到任何数据库表行,则被视为处于新建或临时状态。

为了持久化,我们需要显式调用持久化方法或使用传递持久化机制。

持久性-持久性实体已与数据库表行关联,并由当前运行的持久性上下文管理。

对此类实体所做的任何更改都将被检测并传播到数据库(在会话刷新时间内)。

分离-一旦当前运行的Persistence Context关闭,所有以前管理的实体都将分离。将不再跟踪连续的更改,并且不会发生自动数据库同步。移除-虽然JPA要求只允许移除托管实体,但Hibernate也可以删除分离的实体(但只能通过移除方法调用)。

实体状态转换

要将实体从一个状态移动到另一个状态,可以使用persistent、remove或merge方法。

解决问题

您在问题中描述的问题:

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

是通过将状态为“新建”的实体与状态为“托管”的实体关联而导致的。

当您将子实体关联到父实体中的一对多集合,并且集合不级联实体状态转换时,可能会发生这种情况。

因此,您可以通过向触发此故障的实体关联添加级联来解决此问题,如下所示:

@OneToOne协会

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

请注意我们为级联属性添加的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.ALL或orphanRemove,因为这会将删除实体状态转换从一个父实体传播到另一个父图元。

因此,对于@ManyToMany关联,通常级联CascadeType.PERSIST或CascadeType.MERGE操作。或者,可以将其扩展到DETACH或REFRESH。

我刚刚收到这个错误,因为我在保存之前设置了一个未代理的实体而不是另一个实体。

我应该链接一个代理实体实例。

参见以下说明:

Child saved = childRepository.save(child);

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

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