1. 概要

このチュートリアルでは、一般的な Hibernate エラー– 「org.hibernate.TransientObjectException:オブジェクトが未保存の一時インスタンスを参照しています」を解決する方法を説明します。 このエラーは、管理対象エンティティを永続化しようとすると、 Hibernate セッションから発生し、そのエンティティは未保存のtransientインスタンスを参照します。

2. 問題の説明

TransientObjectException は、「ユーザーが一時インスタンスを永続インスタンスを期待するセッションメソッドに渡すときにスローされます」です。

現在、この例外を回避するための最も簡単な解決策は、新しいインスタンスを永続化するか、データベースから1つをフェッチして、必要なエンティティの永続化されたインスタンスを取得し、以前に依存インスタンスに関連付けることです。それを永続化する。 ただし、これはこの特定のシナリオのみを対象としており、他のユースケースには対応していません。

すべてのシナリオをカバーするには、別のエンティティの存在に依存するエンティティ関係の保存/更新/削除操作をカスケードするソリューションが必要です。 これは、エンティティの関連付けで適切なCascadeTypeを使用することで実現できます。

次のセクションでは、いくつかのHibernateエンティティとそれらの関連付けを作成します。 次に、これらのエンティティを永続化して、sessionが例外をスローする理由を確認します。 最後に、適切な CascadeType (s)を使用して、これらの例外を解決します。

3. @OneToOneアソシエーション

このセクションでは、@OneToOneアソシエーションのTransientObjectExceptionを解決する方法を説明します。

3.1. エンティティ

まず、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
}

そして、関連付けられた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. エラーの生成

次に、データベースにユーザーを保存するための単体テストを追加します。

@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();
}

ここで、上記のテストを実行すると、例外が発生します。

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

ここで、この例では、新しい/一時的なアドレスインスタンスを新しい/一時的なユーザーインスタンスに関連付けました。 次に、Hibernate セッションがAddressエンティティが永続インスタンスであることを期待しているため、Userインスタンスを永続化しようとしたときにTransientObjectExceptionが発生しました。 つまり、 User を永続化するときに、Addressがデータベースにすでに保存/使用可能になっている必要があります。

3.3. エラーの解決

最後に、 User entityを更新し、User-Addressの関連付けに適切なCascadeTypeを使用しましょう。

@Entity
@Table(name = "user")
public class User {
    ...
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;
    ...
}

これで、 User を保存/削除するたびに、Hibernateセッションは関連する Address も保存/削除し、セッションはTransientObjectException。をスローしません。

4. @OneToManyおよび@ManyToOneの関連付け

このセクションでは、@OneToManyおよび@ManyToOneの関連付けでTransientObjectExceptionを解決する方法を説明します。

4.1. エンティティ

まず、従業員エンティティを作成しましょう。

@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
}

そして、関連する 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. エラーの生成

次に、データベースに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();
}

ここで、上記のテストを実行すると、例外が発生します。

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

ここで、この例では、新しい/一時的なEmployeeインスタンスを新しい/一時的なDepartmentインスタンスに関連付けました。 次に、Hibernate セッションがDepartmentエンティティが永続インスタンスであることを期待しているため、Employeeインスタンスを永続化しようとしたときにTransientObjectExceptionが発生しました。 つまり、 Department は、 Employee を永続化するときに、データベースにすでに保存/使用可能になっているはずです。

4.3. エラーの解決

最後に、 Employee エンティティを更新し、Employee-Departmentの関連付けに適切なCascadeTypeを使用しましょう。

@Entity
@Table(name = "employee")
public class Employee {
    ...
    @ManyToOne
    @Cascade(CascadeType.SAVE_UPDATE)
    @JoinColumn(name = "department_id")
    private Department department;
    ...
}

そして、 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<>();
    ...
}

ここで、 Employee-Department アソシエーションで@Cascade(CascadeType.SAVE_UPDATE)を使用し、新しいDepartmentインスタンスを新しいEmployeeインスタンスに関連付けてEmployee[を保存するたびにX208X]、Hibernateセッションは関連するDepartmentインスタンスも保存します

同様に、Department-Employeesアソシエーションでcasino = CascadeType.ALL を使用することにより、Hibernateセッションはすべての操作をDepartmentから関連するものにカスケードします従業員。 たとえば、 Department を削除すると、そのDepartmentに関連付けられているすべてのEmployeeが削除されます。

5. @ManyToManyアソシエーション

このセクションでは、@ManyToManyアソシエーションのTransientObjectExceptionを解決する方法を説明します。

5.1. エンティティ

Bookエンティティを作成しましょう。

@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
}

そして、関連付けられた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. 問題の発生

次に、いくつかの単体テストを追加して、 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();
}

ここで、上記のテストを実行すると、それぞれ次の例外が発生します。

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

同様に、これらの例では、新しい/一時的なインスタンスをインスタンスに関連付けてそのインスタンスを永続化しようとすると、TransientObjectExceptionが発生しました。

5.3. 問題の解決

最後に、 Author エンティティを更新し、 Author s -Bookの関連付けに適切な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<>();
    ...
}

同様に、 Book エンティティを更新し、 Book s- Authorの関連付けに適切なCascadeTypeを使用しましょう。

@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<>();
    ...
}

@ManyToMany アソシエーションではCascadeType.ALLを使用できないことに注意してください。これは、著者が削除された場合にブックを削除したくないためです

6. 結論

要約すると、この記事では、適切なCascadeTypeを定義することで、「org.hibernate.TransientObjectException:オブジェクトが未保存の一時インスタンスを参照する」エラーを解決する方法を示します。

いつものように、この例のコードはGitHubにあります。