JPA - 插入连接表的正确方法(带有额外的列)

时间:2014-02-16 16:15:29

标签: jpa eclipselink jpa-2.0 entitymanager

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

@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=LazyMovieCrew){ {1}}到@Transient字段?)。联接表实体中是否需要moviesHasCrews等?这是最简单/最优雅的做法吗?

架构:

enter image description here

参考文献:

2 个答案:

答案 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对我来说看起来更干净,但是在连接表中有一个额外的列。