JPA-правильный способ вставки в таблицу соединений (с дополнительными столбцами)
Мне удалось вставить съемочную группу для моего фильма - теперь я хочу сделать это правильно. Сущности (сокращенно):
@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 : я хочу, чтобы поля Crew и Movie entities moviesHasCrews
обновились при сохранении сущности MoviesHasCrew
(т. е. drop m.addMoviesHasCrew(moviesHasCrew); em.merge(m);
), но мои каскадные аннотации, похоже, этого не делают. Должен ли я сделать это наоборот ? То есть добавить к moviesHasCrews
в фильмах и объединить / perist Movie и обновить MoviesHasCrew
- это я читаю должен hibernate, но я работаю с generic JPA - это все еще не выполнимо в vanilla JPA ?
Вопрос 2 : краткое изложение того, как это должно быть сделано, было бы желательно (например, я должен добавить fetch=Lazy
(в Movie
и Crew
) @Transient
к полям moviesHasCrews
?). Это @MapsId("moviesIdmovie")
и т. д. Необходимо в присоединяемой таблице сущностей ? Является ли это самым минимальным / элегантным способом в том, чтобы сделать это ?
Схема:
Ссылки:
2 ответа:
Проблема в том, что JPA не поддерживает обе стороны двунаправленных отношений для вас. Это гораздо более очевидно при использовании поставщика JPA, который имеет кэш второго уровня. Причина очевидна в том, что когда вы устанавливаете собственническую сторону отношений - в этом случае называйте moviesHasCrew.setCrew (w), а затем em.flush () - это приводит к обновлению базы данных FK. Но если вы сразу же проверите свою объектную модель, то увидите, что упомянутый член экипажа не имеет соответствующий экземпляр moviesHasCrew в своей коллекции. JPA не управляет вашими ссылками и не устанавливает их для вас, поэтому он не синхронизирован с тем, что находится в базе данных.
Этого следует ожидать в том же EntityManager. Однако при использовании кэша второго уровня каждый раз, когда вы выполняете запрос к этому экземпляру команды, он возвращает кэшированную копию, которая уже устарела.
Единственный способ обновить коллекцию-это перезагрузить экземпляр 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 - >фильмы и moviesHasCrew - >отношения экипажа, чтобы обработать его для вас, установив CascadeType.Параметр слияния для этих отношений, а затем использовать их.слияние (moviesHasCrew); вместо вызовов 3 em. Слияние moviesHasCrew приведет к тому, что он будет вставлен в базу данных так же, как и persist, но слияние будет каскадным по всем ссылочным сущностям с отношениями, помеченными CascadeType.Слияние-таким образом, упомянутая съемочная группа и фильм также будут объединены.
Я думаю, что вы не должны преследовать
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
: они нужны вам из-за встроенного идентификатораMoviesHasCrewPK
. Таким образом, атрибуты встроенного идентификатора сопоставляются с соответствующими значениями аннотаций@MapsId
. Видеть также здесь . Я бы не стал использовать этот встроенный идентификатор, если бы не было необходимости. Сгенерированный код выглядит чище меня, но тогда у вас есть дополнительный столбец в таблице.