简体   繁体   中英

Hibernate: add existing child entity to new entity with OneToMany bidirectional relationship and persist it ('detached entity passed to persist')

In order to understand why I have persisted child entities, here is the mapping.

I have Author (id, name, books) and Book (id, title, authors) entities. Their relationship is ManyToMany since any Author may have more than one Book, and any Book may have more than one Author. Also I have BookClient (id, name, rentDate, books) - relationship with Book entity is OneToMany since any Client may rent zero to many books.

Author.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;

Some business logic behind the scenes: there're a lot of books written by different authors which are persisted in DB of some, let's say, library. And this library gives books to clients. Any new client may register in the library when he/she takes a book.

Book clients are persisted using Entity Manager:

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

Here is an example of how it may look like in code:

    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
    }

After running this code I get detached entity passed to persist exception since Book instance has already been persisted earlier.

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

If I persist BookClient berforehand, then connection between BookClient and Book is set correctly since both entities are existed in DB. But it seems to me as some workaround.

Is it possible to create new object, connect already persisted entity to it and persist this object with cascade update of all its children?

In your example, save operation for the author and the bookClient executes in different persistence contexts. So as for the main method, first you should merge books (into saveOrUpdate method) that were detached during saving the author.

Is it possible to create new object, connect already persisted entity to it and persist this object with cascade update of all its children?

It may depend on your application requirements and functionality. In this main() example, it looks like you want to save all these entities transactionally.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM