0

I am practicing JavaEE technologies. Now I am focusing in JPA with Hibernate. The project has the following entities:

Book:

@Entity @Table(name = "book")
public class Book extends BaseEntity<Long> {
   @Id
   private Long id;

   @NotNull
   private String name;

   @OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
   private BookDetails details;

   //getters/setters
}

BookDetails:

@Entity
@Table(name = "book_details")
public class BookDetails extends BaseEntity<Long> {

    @Id
    private Long id;

    @MapsId
    @OneToOne
    private Book book;

    @NotNull
    @Column(name = "number_of_pages")
    private int numberOfPages;

    //getters/setters
}

Now, the respective EJB Service classes:

BookService:

@Stateless
public class BookService extends BaseEntityService<Long, Book> {

    public void createBook(Book book) {
        super.getEntityManager().persist(book);
    }

    //update, delete, find and findAll methods
}

BookDetailsService:

@Stateless
public class BookDetailsService extends BaseEntityService<Long, BookDetails> {

    public void createBookDetails(BookDetails details) {
        super.getEntityManager().persist(details);
        //super.persist(details); //This method would not work to persisting entity with shared Id, because it verifies if ID is null
    }
    //update, delete and find methods
}

The problem:

When I try to persist a new book along with its details as follows:

Book b = new Book();
b.setId(123L);
b.setName("Bible");

bookService.createBook(b);
//The entity Book  is correctly persisted in the DB.
BookDetails d = new BookDetails();
d.setNumberOfPages(999);
d.setBook(b);

//b.setDetails(d); //don't do this

bookDetailsService.createBookDetails(d);
//BookDetails is not persisted in the DB, throws exception....

Throws the following exception:

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '123' for key 'PRIMARY'

The Book entity is persisted but not the BookDetails.


I followed this tutorials:

Aditional Information:

  • JDK 1.8
  • Payara 5
  • MySQL 8.0
  • Hibernate 5.3.4
  • OmniPersistence library. (Utilities for JPA, JDBC and DataSources)

You can look at the project repository here.

David Ferreira
  • 1,233
  • 17
  • 24
  • You can see it here: http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#identifiers-derived Anyway, I will try with the @PrimaryKeyJoinColumn annotation. – David Ferreira Aug 27 '18 at 02:59
  • @NathanHughes Although I replaced `@MapsId` with `@PrimaryKeyJoinColumn`, it still does not work. However, the exception changed to: `java.sql.SQLException: Field 'book_id' doesn't have a default value`. So I tried setting the id of `BookDetails` ( `d.setId(b.getId())` ) but it still shows the same error. – David Ferreira Aug 27 '18 at 03:34

3 Answers3

1

I could already solve the problem and it was due to the way the entities persisted and the transactionality of the methods in a JavaEE application.

When calling a business method of an EJB in which classes are persisted, upon completion of this method the transaction ends, therefore the persistence context is closed and the attached classes (managed) become detached (unmanaged). See this.

So after persisting Book b:

bookService.createBook (b);

That transaction ends and that is why the book is persisted and, in addition, it is no longer managed. So when I link the book to the details:

d.setBook (b);

That is something that must be done to persist an entity that shares Id, the parent entity (in this case b) has to be managed.


There are two solutions that i could find:

Solución 1:

Attach book b to the persistence context of the details creation transaction. See this.

b = getEntityManager.merge(b);

Solución 2: (The one that I chose)

Make the call of the BookService business method within the business method of the BookDetailsService, which implies transferring the dependency injection. Thus, a single transaction is made by persisting the two entities.

//BookDetailsService class
@Inject
BookService bookService;

public void createBookDetails(BookDetails details) {
    Book b = new Book();
    b.setId(details.getId());
    b.setName("Bible");

    bookService.createBook(b);

    details.setBook(b);//b still managed

    super.persist(details);
}

I think this solution is more cleaner and coherent, because BookDetailsService will need always the services from BookService.

David Ferreira
  • 1,233
  • 17
  • 24
0

You have to add cascadetype on book details:

@OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
private BookDetails details;

Refer here for more details CascadeType.ALL will do all the operations such as save, delete and merge when book entity is saved. Now as we have added CascadeType.ALL in Book entity you have to set bookdetails object in book. See below:

Book b = new Book();
BookDetails d = new BookDetails();
b.setBookDetails(d);
b.setId(123L);
b.setName("Bible");

d.setId(111L); //random id that is not existing if d is new record
d.setNumberOfPages(999);
bookService.createBook(b);

Saving book entity will save book as well as book details entity. You may also try hibernate.show_sql=true to see what all queries are executed when you save book entity.

Ravik
  • 694
  • 7
  • 12
  • i have updated my answer. If that still not works kindly share how are you saving the book entity. – Ravik Aug 28 '18 at 04:29
0

@OneToOne on BookDetails property of Book class indicates that there is a one-to-one association from Book to BookDetails. Also, note the use of cascade = CascadeType.ALL.Since BookDetails entity cannot exist without Book entity, with this cascade setting, BookDetails entity will be updated/deleted on subsequent update/delete on Book entity.

Pallav Kabra
  • 418
  • 3
  • 7