What is the best way to change n-to-n relationships between two entities in JPA.
Let me explain with an example: suppose I have both an Author
and a Book
entity, where a Book
can have various Author
s and an Author
can write on various Book
s. This model is represented as a n-to-n eager relationship:
@Entity
public class Author {
@ManyToMany(fetch=FetchType.EAGER)
private List<Book> books;
// ...
}
Also:
@Entity
public class Book {
@ManyToMany(fetch=FetchType.EAGER)
private List<Author> authors;
// ...
}
I want to e开发者_JAVA技巧xecute a series of operations in those entities - for example, mark an Author
as the author of a Book
:
public class SomeService {
public void addAuthor(Book book, Author author) {
book.getAuthors().add(author);
em.persist(book);
}
}
or remove a Book
from the books of an Author
:
public class SomeOtherService {
public void removeBook(Author author, Book book) {
author.getBooks().remove(book);
em.persist(author);
}
}
However, I would like to have the update registered in the entity not being updated too. When I call addAuthor()
, I expect the updated Book
to be also present in the list of books of the Author
. I want the Author
to be removed of the Book
's list of authors when I call removeBook()
.
If the code is such as the above ones it does not happen. My current solution is to execute de corresponding operation in both entities, more or less like shown below:
public class SomeService {
public void addAuthor(Book book, Author author) {
book.getAuthors().add(author);
author.getBooks().add(book);
em.persist(book);
em.persist(author);
}
}
It looks like a lame solution to me, though. So, my question is: is there a better way to do it?
Thanks in advance?
It's not really a lame solution to me as it is very explicit on what's going on, an advantage is that you have a very explicit code. However, if to you that is excessively explicit then you have several alternatives.
As Jay suggested you could write a @PreUpdate callback method that does the:
author.getBooks().add(book);
for this to work however you have to cascade changes to the authors using the cascade attribute of the @ManyToMany annotation
@ManyToMany(cascade=CascadeType.ALL)
for instance, or you can increase the granularity of the CascadeType.
There's an alternative that is handling the collections through delegate methods. This has the advantage that you have a total control over the addAuthor method but is less intuitive to people using your entity. It could be written as:
public void addAuthor(Author A) {
this.getAuthors().add(a);
a.addBook(this);
}
you would then need to implement addBook. And cascade changes.
Your option isn't bad either. You can use helper classes to achieve this but this increases complexity so bare that in mind.
Maintaining bidirectional relation state can be challenging. In the end both
book.getAuthors().add(author);
author.getBooks().add(book);
need to be called.
Where I work we use aspectJ with some internal metadata to handle the setting both sides of the association. For small project this is a bit overkill, and the way you do it is fine.
Vincent Partington once wrote a blog post about bidirectional assocations which i believe you will find interesting. Essentially, he suggests having two relationship-adding methods on each partner in the relationship: an inner (package-scoped, presumably) one which simply adds an item to the collection, and an outer one which calls the inner methods on both partners. The same pattern can be applied to removal methods.
Look into @PreXXXX & @PostXXXX annotations. Depending on the DB you're using, you might be better off going in and writing your own triggers.
If you're looking for the most agnostic approach, the solution you put down should do the trick.
Edit: Other approach is to save your trigger code and run it as native SQL after the CRUD (assuming you create & drop in your tests).
精彩评论