1. Overview
1.概述
In this tutorial, we’ll see how to solve a common Hibernate error – “org.hibernate.TransientObjectException: object references an unsaved transient instance”. We get this error from the Hibernate session when we try to persist a managed entity, and that entity references an unsaved transient instance.
在本教程中,我们将看到如何解决一个常见的Hibernate错误 – “org.hibernate.TransientObjectException: object references an unsaved transient instance”/em>。当我们试图持久化一个管理的实体时,我们从Hibernate会话中得到这个错误,并且该实体引用了一个未保存的瞬时实例。
2. Describing the Problem
2.描述问题
The TransientObjectException is “Thrown when the user passes a transient instance to a session method that expects a persistent instance”.
TransientObjectException是 “当用户将一个瞬时实例传递给一个期望有持久实例的会话方法时抛出”。
Now, the most straightforward solution to avoid this exception would be to get a persisted instance of the required entity by either persisting a new instance or fetching one from the database and associate it in the dependant instance before persisting it. However, doing so only covers this particular scenario and doesn’t cater to other use cases.
现在,避免这种异常的最直接的解决方案是:通过持久化一个新的实例或从数据库中获取一个所需实体的持久化实例,并在持久化之前将其关联到依赖实例中。然而,这样做只涵盖了这个特定的场景,并不能满足其他用例。
To cover all scenarios, we need a solution to cascade our save/update/delete operations for entity relationships that depend on the existence of another entity. We can achieve that by using a proper CascadeType in the entity associations.
为了涵盖所有的情况,我们需要一个解决方案来级联我们的保存/更新/删除操作的实体关系,这取决于另一个实体的存在。我们可以通过在实体关联中使用适当的CascadeType来实现这一点。
In the following sections, we’ll create some Hibernate entities and their associations. Then, we’ll try to persist those entities and see why the session throws an exception. Finally, we’ll solve those exceptions by using proper CascadeType(s).
在下面的章节中,我们将创建一些Hibernate实体和它们的关联。然后,我们将尝试持久化这些实体,看看为什么session会抛出一个异常。最后,我们将通过使用适当的CascadeType(s)来解决这些异常。
3. @OneToOne Association
3.@OneToOne 协会
In this section, we’ll see how to solve the TransientObjectException in @OneToOne associations.
在本节中,我们将看到如何解决TransientObjectException在@OneToOne关联中的问题。
3.1. Entities
3.1.实体
First, let’s create a User entity:
首先,让我们创建一个User实体。
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@OneToOne
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
// standard getters and setters
}
And let’s create the associated Address entity:
让我们创建相关的Address实体。
@Entity
@Table(name = "address")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "city")
private String city;
@Column(name = "street")
private String street;
@OneToOne(mappedBy = "address")
private User user;
// standard getters and setters
}
3.2. Producing the Error
3.2.产生错误
Next, we’ll add a unit test to save a User in the database:
接下来,我们将添加一个单元测试,在数据库中保存一个User。
@Test
public void whenSaveEntitiesWithOneToOneAssociation_thenSuccess() {
User user = new User("Bob", "Smith");
Address address = new Address("London", "221b Baker Street");
user.setAddress(address);
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
session.close();
}
Now, when we run the above test, we get an exception:
现在,当我们运行上述测试时,我们得到一个异常。
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.baeldung.hibernate.exception.transientobject.entity.Address
Here, in this example, we associated a new/transient Address instance with a new/transient User instance. Then, we got the TransientObjectException when we tried to persist the User instance because the Hibernate session is expecting the Address entity to be a persistent instance. In other words, the Address should have already been saved/available in the database when persisting the User.
在这个例子中,我们将一个新的/瞬时的Address实例与一个新的/瞬时的User实例联系起来。然后,当我们试图持久化User实例时,我们得到了TransientObjectException,因为Hibernatesession期望Address实体是一个持久化实例。换句话说,在持久化User时,Address应该已经在数据库中被保存/可用。
3.3. Solving the Error
3.3.解决错误
Finally, let’s update the User entity and use a proper CascadeType for the User-Address association:
最后,让我们更新用户实体,并为用户-地址关联使用适当的CascadeType。
@Entity
@Table(name = "user")
public class User {
...
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
...
}
Now, whenever we save/delete a User, the Hibernate session will save/delete the associated Address as well, and the session will not throw the TransientObjectException.
现在,每当我们保存/删除一个User时,Hibernate会话也将保存/删除相关的Address,并且会话不会抛出TransientObjectException。
4. @OneToMany and @ManyToOne Associations
4.@OneToMany和@ManyToOne关联
In this section, we’ll see how to solve the TransientObjectException in @OneToMany and @ManyToOne associations.
在这一节中,我们将看到如何解决TransientObjectException在@OneToMany和@ManyToOne关联中的问题。
4.1. Entities
4.1.实体
First, let’s create an Employee entity:
首先,让我们创建一个雇员实体。
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
// standard getters and setters
}
And the associated Department entity:
以及相关的Department实体。
@Entity
@Table(name = "department")
public class Department {
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<>();
public void addEmployee(Employee employee) {
employees.add(employee);
}
// standard getters and setters
}
4.2. Producing the Error
4.2.产生错误
Next, we’ll add a unit test to persist an Employee in the database:
接下来,我们将添加一个单元测试,在数据库中持久化一个Employee。
@Test
public void whenPersistEntitiesWithOneToManyAssociation_thenSuccess() {
Department department = new Department();
department.setName("IT Support");
Employee employee = new Employee("John Doe");
employee.setDepartment(department);
Session session = sessionFactory.openSession();
session.beginTransaction();
session.persist(employee);
session.getTransaction().commit();
session.close();
}
Now, when we run the above test, we get an exception:
现在,当我们运行上述测试时,我们得到一个异常。
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.baeldung.hibernate.exception.transientobject.entity.Department
Here, in this example, we associated a new/transient Employee instance with a new/transient Department instance. Then, we got the TransientObjectException when we tried to persist the Employee instance because the Hibernate session is expecting the Department entity to be a persistent instance. In other words, the Department should have already been saved/available in the database when persisting the Employee.
在这个例子中,我们将一个新的/临时的Employee实例与一个新的/临时的Department实例关联起来。然后,当我们试图持久化Employee实例时,我们得到了TransientObjectException,因为Hibernatesession期望Department实体是一个持久化实例。换句话说,在持久化Employee时,Department应该已经在数据库中被保存/可用。
4.3. Solving the Error
4.3.解决错误
Finally, let’s update the Employee entity and use a proper CascadeType for the Employee-Department association:
最后,让我们更新Employee实体并为Employee-Department关联使用适当的CascadeType。
@Entity
@Table(name = "employee")
public class Employee {
...
@ManyToOne
@Cascade(CascadeType.SAVE_UPDATE)
@JoinColumn(name = "department_id")
private Department department;
...
}
And let’s update the Department entity to use a proper CascadeType for the Department-Employees association:
让我们更新Department实体,为Department-Employees关联使用适当的CascadeType。
@Entity
@Table(name = "department")
public class Department {
...
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Employee> employees = new HashSet<>();
...
}
Now, by using @Cascade(CascadeType.SAVE_UPDATE) on the Employee-Department association, whenever we associate a new Department instance with a new Employee instance and save the Employee, the Hibernate session will save the associated Department instance as well.
现在,通过使用@Cascade(CascadeType.SAVE_UPDATE)在Employee-Department关联上,每当我们将一个新的Department实例与一个新的Employee实例关联并保存Employee时,Hibernate会话也会保存关联Department实例。
Similarly, by using cascade = CascadeType.ALL on the Department-Employees association, the Hibernate session will cascade all operations from a Department to the associated Employee(s). For example, removing a Department will remove all Employee(s) associated with that Department.
同样,通过在Department-Employees关联上使用cascade = CascadeType.ALL,Hibernate session将把所有操作从Department级联到相关的Employee。例如,删除一个部门将删除与该部门相关的所有雇员。
5. @ManyToMany Association
5.@ManyToMany关联
In this section, we’ll see how to solve the TransientObjectException in @ManyToMany associations.
在本节中,我们将看到如何解决@ManyToMany关联中的 TransientObjectException。
5.1. Entities
5.1.实体
Let’s create a Book entity:
让我们创建一个书实体。
@Entity
@Table(name = "book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "title")
private String title;
@ManyToMany
@JoinColumn(name = "author_id")
private Set<Author> authors = new HashSet<>();
public void addAuthor(Author author) {
authors.add(author);
}
// standard getters and setters
}
And let’s create the associated Author entity:
并让我们创建相关的Author实体。
@Entity
@Table(name = "author")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
@ManyToMany
@JoinColumn(name = "book_id")
private Set<Book> books = new HashSet<>();
public void addBook(Book book) {
books.add(book);
}
// standard getters and setters
}
5.2. Producing the Problem
5.2.问题的产生
Next, let’s add some unit tests to save a Book with multiple authors, and an Author with multiple books in the database, respectively:
接下来,让我们添加一些单元测试,分别在数据库中保存一个有多个作者的Book和一个有多个书籍的Author。
@Test
public void whenSaveEntitiesWithManyToManyAssociation_thenSuccess_1() {
Book book = new Book("Design Patterns: Elements of Reusable Object-Oriented Software");
book.addAuthor(new Author("Erich Gamma"));
book.addAuthor(new Author("John Vlissides"));
book.addAuthor(new Author("Richard Helm"));
book.addAuthor(new Author("Ralph Johnson"));
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(book);
session.getTransaction().commit();
session.close();
}
@Test
public void whenSaveEntitiesWithManyToManyAssociation_thenSuccess_2() {
Author author = new Author("Erich Gamma");
author.addBook(new Book("Design Patterns: Elements of Reusable Object-Oriented Software"));
author.addBook(new Book("Introduction to Object Orient Design in C"));
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(author);
session.getTransaction().commit();
session.close();
}
Now, when we run the above tests, we get the following exceptions, respectively:
现在,当我们运行上述测试时,我们分别得到以下异常。
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.baeldung.hibernate.exception.transientobject.entity.Author
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.baeldung.hibernate.exception.transientobject.entity.Book
Similarly, in these examples, we got TransientObjectException when we associated new/transient instances with an instance and tried to persist that instance.
同样,在这些例子中,当我们将新的/临时性的实例与一个实例相关联并试图持久化该实例时,我们得到了TransientObjectException 。
5.3. Solving the Problem
5.3.解决问题
Finally, let’s update the Author entity and use proper CascadeTypes for the Authors-Books association:
最后,让我们更新Author实体,并为Authors-Books关联使用适当的CascadeType。
@Entity
@Table(name = "author")
public class Author {
...
@ManyToMany
@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.PERSIST})
@JoinColumn(name = "book_id")
private Set<Book> books = new HashSet<>();
...
}
Similarly, let’s update the Book entity and use proper CascadeTypes for the Books-Authors association:
同样地,让我们更新Book实体,并为Books-Authors关联使用适当的CascadeTypes。
@Entity
@Table(name = "book")
public class Book {
...
@ManyToMany
@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.PERSIST})
@JoinColumn(name = "author_id")
private Set<Author> authors = new HashSet<>();
...
}
Note that we cannot use the CascadeType.ALL in a @ManyToMany association because we don’t want to delete the Book if an Author is deleted and vice versa.
请注意,我们不能在@ManyToMany 关联中使用CascadeType.ALL,因为如果Author被删除,我们不希望删除Book,反之亦然。
6. Conclusion
6.结语
To sum up, this article shows how defining a proper CascadeType can solve the “org.hibernate.TransientObjectException: object references an unsaved transient instance” error.
综上所述,本文展示了定义一个合适的CascadeType可以解决“org.hibernate.TransientObjectException: object references an unsaved transient instance” 错误。
As always, you can find the code for this example over on GitHub.
一如既往,你可以在GitHub上找到这个例子的代码。