JPA - the correct way to insert into the connection table (with additional columns)

I managed to insert the crew into my film - now I want to do it right. Objects (abbreviated):

@Entity @Table(name = "movies") public class Movie implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int idmovie; // bi-directional many-to-one association to MoviesHasCrew @OneToMany(mappedBy = "movy", cascade = CascadeType.PERSIST) private List<MoviesHasCrew> moviesHasCrews; } @Entity @Table(name = "movies_has_crew") public class MoviesHasCrew implements Serializable { @EmbeddedId @GeneratedValue(strategy = GenerationType.IDENTITY) private MoviesHasCrewPK id; // bi-directional many-to-one association to Crew @ManyToOne @JoinColumn(name = "crew_idcrew", columnDefinition = "idcrew") @MapsId("crewIdcrew") private Crew crew; // bi-directional many-to-one association to Movy @ManyToOne @JoinColumn(name = "movies_idmovie") @MapsId("moviesIdmovie") private Movie movy; // bi-directional many-to-one association to Role @ManyToOne @JoinColumn(name = "roles_idrole") @MapsId("rolesIdrole") private Role role; } @Entity @Table(name = "crew") public class Crew implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int idcrew; // bi-directional many-to-one association to MoviesHasCrew @OneToMany(mappedBy = "crew", cascade = CascadeType.PERSIST) private List<MoviesHasCrew> moviesHasCrews; } 

Sorry for the "movy" and "crews" tools that (and qualifies for a bug report)

Controller and form:

 @ManagedBean @ViewScoped public class MovieController implements Serializable { @EJB private MovieService service; private Crew crewMember; private Movie movie; public String addCrewMember() { if (movie.getIdmovie() == 0) { movie = (Movie) FacesContext.getCurrentInstance() .getExternalContext() .getSessionMap().get("movie"); } service.addCrew(movie, crewMember); return null; } } <h:form id="movie_add_crew_form" rendered="#{sessionScope.movie != null}"> <h:panelGrid columns="2"> <h:selectOneListbox id="crewMember" redisplay="true" size="8" value="#{movieController.crewMember}" converter="#{movieController$CrewConverter}"> <f:selectItems value="#{movieController.allCrew}" var="entry" itemValue="#{entry}" itemLabel="#{entry.name}" /> <f:ajax event="blur" render="crewMemberMessage" /> </h:selectOneListbox> <h:message id="crewMemberMessage" for="crewMember" /> </h:panelGrid> <h:commandButton value="Add" action="#{movieController.addCrewMember}"> <f:ajax execute="@form" render="@form :movie_crew" /> </h:commandButton></h:form> 

And finally, the service:

 @Stateless public class MovieService { @PersistenceContext private EntityManager em; public void addCrew(Movie m, Crew w) { MoviesHasCrew moviesHasCrew = new MoviesHasCrew(); moviesHasCrew.setCrew(w); moviesHasCrew.setMovy(m); moviesHasCrew.setRole(Role.DEFAUT_ROLE); em.persist(moviesHasCrew); m.addMoviesHasCrew(moviesHasCrew); // (1) em.merge(m); // noop } } 

Question 1 . I want the moviesHasCrews crew and movie moviesHasCrews updated when the MoviesHasCrew object is MoviesHasCrew (i.e. drop m.addMoviesHasCrew(moviesHasCrew); em.merge(m); ), but my cascading annotations don't look do it. Should I do it the other way around? This is an addition to moviesHasCrews in movies and merging / perist Movie and updating MoviesHasCrew - this I read requires sleep mode, but I work with general JPA - is it still not feasible in vanilla JPA?

Question 2 : a summary of how this should be done should be appreciated (for example, should I add fetch=Lazy (in Movie and Crew ) @Transient in the moviesHasCrews fields?). Is @MapsId("moviesIdmovie") etc. In essence, a connection table? Is this the most minimal / elegant way to do this?

Scheme:

enter image description here

Literature:

+6
source share
2 answers

The problem is that JPA does not support both sides of a bidirectional relationship for you. This is much more obvious if you are using a JPA provider that has a second level cache. The reason is that when you establish your own side of the relationship - in this case, call moviesHasCrew.setCrew (w) and then em.flush () - this will update the FK database. But if you immediately check your object model, you will see that the crew member mentioned does not have a corresponding movieHasCrew instance in his collection. JPA does not manage your links and install them for you, so it is not synchronized with what is in the database.

This is to be expected in the same EntityManager. When a second level cache is involved, every time you request a Crew instance, it returns a cached copy, which is now deprecated.

The only way to update the collection is to reload the Crew instance from the database. This can be done by clearing the cache or force updating.

The best alternative is to maintain both sides of a bidirectional relationship and synchronize them with each other. In the code you have, this means a call:

 public void addCrew(Movie m, Crew w) { MoviesHasCrew moviesHasCrew = new MoviesHasCrew(); moviesHasCrew.setCrew(w); w.addMoviesHasCrew(moviesHasCrew); moviesHasCrew.setMovy(m); m.addMoviesHasCrew(moviesHasCrew); // (1) moviesHasCrew.setRole(Role.DEFAUT_ROLE); em.persist(moviesHasCrew); em.merge(m); // noop unless it is detached em.merge(w); // noop unless it is detached } 

Merging is required if they are separate instances, since the change in the collection must be placed in the EntityManager so that it can be cached.

If these merges are what you want to avoid, you can rely on MoviesHasCrew-> Movies and moviesHasCrew-> Crew to handle it for you by setting the CascadeType.MERGE parameter to this relationship, and then use em.merge (moviesHasCrew ); instead of calling 3 em. Combining moviesHasCrew will cause it to be inserted into the database in the same way as persist, but merging will cascade across all reference objects with relationships marked as CascadeType.MERGE, so Crew and Movie links will also be merged.

+2
source

I think that you should not pursue the right way , it does not exist. Personally, I don't like too many cascading operations. What is the profit? In this case, I would use something like:

Services:

 public void addCrew(long movieId, long crewId) { Movie m = em.getReference(Movie.class, movieId); Crew w = em.getReference(Crew.class, crewId); MoviesHasCrew moviesHasCrew = new MoviesHasCrew(); moviesHasCrew.setCrewAndMovie(w,m); moviesHasCrew.setRole(Role.DEFAUT_ROLE); em.persist(moviesHasCrew); } 

MoviesHasCrew:

 public void setCrewAndMovie(Crew c, Movie m){ this.crew = c; this.movie = m; m.addMoviesHasCrew(this); c.addMoviesHasCrew(this); } 

It remains readable, cascading operations sometimes work like magic.

About @MapsId : you need them because of the built-in id MoviesHasCrewPK . Thus, the attributes of the inline identifier are mapped to the corresponding @MapsId annotation @MapsId . See also here . I would not use this built-in identifier if I did not need it. The generated id looks cleaner for me, but then you have an extra column in the connection table.

+1
source

All Articles