【问题标题】:JPA - correct way to insert to a join table (with extra columns)JPA - 插入连接表的正确方法(带有额外的列)
【发布时间】:2014-03-15 20:20:49
【问题描述】:

我设法为我的电影插入了工作人员 - 现在我想以正确的方式去做。实体(缩写):

@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:我希望在持久化 MoviesHasCrew 实体时更新 Crew 和 Movie 实体的字段 moviesHasCrews(即删除 m.addMoviesHasCrew(moviesHasCrew); em.merge(m);),但我的级联注释似乎没有做。我应该反过来做吗?这是添加到电影中的moviesHasCrews 和合并/保存电影并更新MoviesHasCrew - 我read 需要休眠,但我使用通用 JPA - 它在原版 JPA 中仍然不可行吗?

问题 2:将不胜感激如何完成此操作(例如,我应该将 fetch=Lazy(在 MovieCrew 中)@Transient 添加到 @987654337 @字段?)。连接表实体中是否需要@MapsId("moviesIdmovie") 等?这是最简单/最优雅的方式吗?

架构:

参考资料:

【问题讨论】:

  • 如果 persist() 失败第 (1) 行仍然执行,这是一个问题还是正在发生?,如果坚持失败,它应该不抛出 PersistenceException 吗?
  • @Koitoer:是的,但它是异步的——它正在发生

标签: jpa eclipselink jpa-2.0 entitymanager


【解决方案1】:

问题在于 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->Movies 和 moviesHasCrew->Crew 关系来为您处理它,方法是在这些关系上设置 CascadeType.MERGE 选项,然后使用 em.merge (电影有船员);而不是 3 em 电话。合并moviesHasCrew 将导致它像persist 一样插入到数据库中,但是合并将级联所有具有标记CascadeType.MERGE 关系的引用实体-因此引用的Crew 和Movie 也将合并。

【讨论】:

  • 编辑了包含您的一些建议的问题 - 在某些时候我遇到了“无法刷新非托管对象”IAE。仍然对一些框架代码感兴趣,就像我提供但评论/修改的那样。顺便说一句,一个机组成员可以扮演许多角色。
  • 无法刷新是因为我认为merge(m) 就足够了——而我需要m=merge(m)——会检查这个。
  • 关于你的回答 - 你认为crew.addMoviesHasCrews(this); movie.addMoviesHasCrews(this); 是不可避免的(一种或另一种方式)吗?因为我打电话给em.merge(moviesHasCrew);,所以应该有办法告诉JPA,相关(加入)实体应该更新他们的List&lt;MoviesHasCrew&gt; moviesHasCrews;。这是org.hibernate.annotations.CascadeType.SAVE_UPDATE 所做的吗(见here)?在 vanilla JPA 中没有办法(可能在实体上添加其他注释或在 m,w 上执行 merge)?
  • 您的答案还假定w,m instances 是否由 JPA 管理?因为我从 UI 中获取它们,并且据我所知 JPA 没有对其进行管理(因此我的 Can not refresh not managed object
  • 答案中显示的映射设置了 MoviesHasCrew->crew 和 Movie 关系上的 cascade.Merge,因此如果您在 MoviesHasCrew 实例上使用合并,它会级联到工作人员和电影并导致它们成为管理的。 SAVE_UPDATE 是 Hibernate 特定的级联选项;它似乎类似于 JPA cascade.merge 和 cascade.persist。它不执行您似乎所追求的关系维护。 JPA 要求您设置双向关系的双方,并且不会为您这样做。有很多方法可以解决,但是还有很多其他的帖子和他们的问题
【解决方案2】:

我认为你不应该追逐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 对我来说看起来更干净,但是连接表中有一个额外的列。

【讨论】:

  • 也许没有正确的方法,但我对一些框架代码感兴趣,就像我提供的那样 - 它至少包含设置这种关系的绝对最小值和可能的扩展点。你为什么用em.getReference
  • 这不会重新加载整个实体,只需获取它的引用 (id)。与em.find 进行比较。但我之所以不传递整个实体,只是传递 id,是因为这样更安全。想象一下,如果有人修改了CrewMovie,而你修改了merge。显然你不想更新它们,只想更新它们的关系。
猜你喜欢
  • 2015-03-12
  • 1970-01-01
  • 2017-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多