我设法为我的电影插入工作人员 - 现在我想以正确的方式做到这一点。实体(缩写):
@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;
}
抱歉'movy'和'crews'是工具(并且有资格获得错误报告)
控制器和表格:
@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>
最后服务:
@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
}
}
问题1 :我想更新船员和电影实体的字段moviesHasCrews
以保留MoviesHasCrew
实体(即删除m.addMoviesHasCrew(moviesHasCrew); em.merge(m);
)但我的级联注释似乎没有这样做。我应该反过来做吗?这是添加到电影中的moviesHasCrews
和合并/ perist电影并更新MoviesHasCrew
- 这个我read需要休眠但我使用通用JPA - 它仍然不能在香草JPA ?
问题2 :我们将不胜感激(例如,我应该添加fetch=Lazy
(Movie
和Crew
){ {1}}到@Transient
字段?)。联接表实体中是否需要moviesHasCrews
等?这是最简单/最优雅的做法吗?
架构:
参考文献:
答案 0 :(得分:2)
问题在于JPA不会为您维护双向关系的双方。当您使用具有二级缓存的JPA提供程序时,这一点更加明显。显而易见的原因是当你设置关系的拥有方 - 在这种情况下调用moviesHasCrew.setCrew(w)然后调用em.flush() - 这会导致数据库FK被更新。但是,如果您立即检查对象模型,您将看到引用的Crew成员在其集合中没有相应的moviesHasCrew实例。 JPA不管理您的引用并为您设置它们,因此它与数据库中的内容不同步。
这应该在同一个EntityManager中。当涉及到二级缓存时,每次查询该Crew实例时,它都将返回现在过时的缓存副本。
更新集合的唯一方法是从数据库重新加载Crew实例。这可以通过清除缓存或强制刷新来完成。
更好的选择是保持双向关系的双方并使它们保持彼此同步。在您拥有的代码中,这意味着调用:
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
}
如果它们是分离的实例,则需要合并,因为需要将对集合的更改放入EntityManager,以便将其合并到缓存中。
如果这些合并是您想要避免的,您可以依靠moviesHasCrew-&gt; Movies和moviesHasCrew-&gt; Crew关系通过在这些关系上设置CascadeType.MERGE选项来为您处理它,然后使用em .merge(moviesHasCrew);而不是3个电话。合并moviesHasCrew将导致它与persist一样插入到数据库中,但合并将级联所有引用的实体,其关系标记为CascadeType.MERGE - 因此引用的Crew和Movie也将合并。
答案 1 :(得分:1)
我认为你不应该追逐right way
,它不存在。我个人不喜欢太多的级联操作。有什么好处?在这种情况下,我会使用类似的东西:
服务:
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);
}
它保持可读性,级联操作有时像魔法一样工作。
关于@MapsId
:由于内嵌ID MoviesHasCrewPK
,您需要它们。这样,嵌入式id的属性将映射到@MapsId
注释的相应值。另见here。如果我不需要,我不会使用这个嵌入式ID。生成的ID对我来说看起来更干净,但是在连接表中有一个额外的列。