Hibernate:将现有子实体添加到具有 OneToMany 双向关系的新实体并将其持久化(“分离的实体传递给持久化”)

Posted

技术标签:

【中文标题】Hibernate:将现有子实体添加到具有 OneToMany 双向关系的新实体并将其持久化(“分离的实体传递给持久化”)【英文标题】:Hibernate: add existing child entity to new entity with OneToMany bidirectional relationship and persist it ('detached entity passed to persist') 【发布时间】:2020-02-16 15:02:37 【问题描述】:

为了理解我为什么要保留子实体,这里是映射。

我有 Author (id, name, books) 和 Book (id, title, authors) 实体。他们的关系是多对多,因为任何作者都可能有不止一本书,而任何一本书可能有不止一个作者。我也有 BookClient (id, name,rentDate, books) - 与 Book 实体的关系是 OneToMany,因为任何客户都可以租零到多本书。

作者.java

@Table
public class Author 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "books_authors",
            joinColumns =  @JoinColumn(name = "author_id") ,
            inverseJoinColumns =  @JoinColumn(name = "book_id") 
    )
    private Set<Book> books = new HashSet<>();

Book.java

@Entity
@Table
public class Book 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, unique = true)
    private Long id;

    @Column(nullable = false)
    private String title;

    @ManyToMany(mappedBy = "books", fetch = FetchType.EAGER)
    private Set<Author> authors = new HashSet<>();

    @ManyToOne
    @JoinColumn(name = "client_id")
    private BookClient bookClient;

BookClient.java

@Entity
@Table
public class BookClient 

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

    @Column(nullable = false)
    private String name;

    @OneToMany(mappedBy = "bookClient", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private Set<Book> books = new HashSet<>();

    private LocalDate rentDate;

幕后的一些业务逻辑:有很多不同作者写的书都保存在一些数据库中,比如图书馆。这个图书馆为客户提供书籍。任何新客户在取书时都可以在图书馆注册。

图书客户端使用实体管理器进行持久化:

@Transactional
@Repository("bookClientDao")
public class BookClientDaoImpl implements BookClientDao 

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void save(BookClient bookClient) 
        entityManager.persist(bookClient);
    


    @Override
    public void saveOrUpdate(BookClient bookClient) 
        if(bookClient.getId() == null) 
            save(bookClient);
         else 
            entityManager.merge(bookClient);
        
    

下面是它在代码中的外观示例:

    public static void main(String[] args) 
        ApplicationContext appContext = new ClassPathXmlApplicationContext("META-INF/context.xml");
        AuthorDao authorDao = (AuthorDao) appContext.getBean("authorDao");
        BookClientDao bookClientDao = (BookClientDao) appContext.getBean("bookClientDao");

        //Create a book and its author
        Book book = new Book();
        book.setTitle("John Doe Book the 1st");

        Author author = new Author();
        author.setName("John Doe");
        author.getBooks().add(book);
        authorDao.save(author);

        //Registering new Book Client
        BookClient bookClient = new BookClient();
        bookClient.setName("First Client");
        bookClient.getBooks().add(book);
        bookClient.setRentDate(LocalDate.now());

        book.setBookClient(bookClient);

        //book is expected to be updated by cascade
        bookClientDao.saveOrUpdate(bookClient); //'detached entity passed to persist' occurs here
    

运行此代码后,我收到 detached entity passed to persist 异常,因为 Book 实例之前已被持久化。

Exception in thread "main" javax.persistence.PersistenceException:
org.hibernate.PersistentObjectException: detached entity passed to persist: entity.manager.example.entity.Book
...
Caused by: org.hibernate.PersistentObjectException:
detached entity passed to persist: entity.manager.example.entity.Book

如果我预先保存 BookClient,则 BookClient 和 Book 之间的连接设置正确,因为这两个实体都存在于 DB 中。但在我看来,这是一种解决方法。

是否可以创建新对象,将已持久化的实体连接到它,并通过级联更新其所有子对象来持久化该对象?

【问题讨论】:

【参考方案1】:

在您的示例中,作者的保存操作和 bookClient 在不同的持久性上下文中执行。所以对于main方法,首先要合并保存作者时分离的书籍(到saveOrUpdate方法中)。

是否可以创建新对象,将已持久化的实体连接到它,并通过级联更新其所有子对象来持久化该对象?

这可能取决于您的应用程序要求和功能。在这个 main() 示例中,您似乎希望以事务方式保存所有这些实体。

【讨论】:

以上是关于Hibernate:将现有子实体添加到具有 OneToMany 双向关系的新实体并将其持久化(“分离的实体传递给持久化”)的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 对具有复合键的子实体执行错误的插入顺序

Hibernate入门之one to one关系映射详解

如何将项目添加到具有Android Room中父实体的外键引用的子实体?

使用注解的Hibernate one-to-many映射

Hibernate HQL:当且仅当所有子实体都具有相同值的属性时,如何选择父实体?

Erlang:supervisor(3),添加子进程