У JPA есть свои загадки, и время от времени полезно записать это хитрое решение для наших задач отображения. В этой записи описывается отношение ManyToMany с дополнительным состоянием в промежуточной таблице. Все мои примеры связаны с проектом Arena-PUJ, любимым проектом, над которым я сейчас работаю. Arena — это онлайн-система для управления академическими соревнованиями, и в ее нескольких таблицах и сопоставлениях есть угловой случай, который я объясню ниже. Во-первых, позвольте мне определить сущности и их отношения для моделирования данных отношений Institutions X Competition .
Учреждения X Соревнования
У нас есть две сущности, имеющие отношения ManyToMany:
- Конкурс : это виртуальный конкурс, в котором учащиеся подают лучшие домашние задания в регионе (город, штат или даже страна). Несколько соревнований проходят одновременно в разных местах, спонсируются и организуются разными учреждениями.
- Учреждение : компания, кувшин или школа. Он моделирует университет конкурентов, JUG, организующий соревнование, или компанию, спонсирующую соревнование. Учреждения играют роли в конкурсе.
Проблема: роли учреждения динамичны
Соревнования проводятся ежегодно, и для разных соревнований одно и то же учреждение может играть разные роли. Классический пример касается спонсорства: компания, которая была партнером в 2008 году, становится платиновым спонсором в 2009 году. Моделирование ролей учреждений динамично — учреждения могут играть разные роли в разных конкурсах .
Решение: таблица соединений с дополнительным состоянием
Связь между соревнованиями и учреждениями — это связь @ManyToMany, но мы не можем просто аннотировать сущности . Чтобы поддерживать динамические роли учреждений, нам нужно настроить таблицу отношений , что означает, что нам нужно добавить дополнительные столбцы в таблицу соединений , как показано на диаграмме ниже.
Реализация таблицы соединений с JPA 1.x Аннотации
Стол соединения имеет отношения @ManyToOne с двумя объектами, а также перечисление с возможными ролями, которые учреждение может играть в соревновании. Чтобы работать как настоящая таблица соединений, вы должны использовать @ClassId в качестве составного первичного ключа таблицы соединений. Вы можете проверить полный исходный код отсюда , но соответствующие части находятся в фрагментах ниже.
-
Таблица соединения:
@Entity
@IdClass(PujInstitutionRoles_PK.class)
public class PujInstitutionRoles implements Serializable {
public enum Role {
SCHOOL, PUJ_OWNER, SPONSOR, PARTNER
}
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "VARCHAR(20)")
private PujInstitutionRoles.Role role;
@Id
@ManyToOne
@PrimaryKeyJoinColumn(name = "INSTITUTION_ACRONYM", referencedColumnName = "acronym")
private PujInstitutionEntity institution;
@Id
@ManyToOne
@PrimaryKeyJoinColumn(name = "COMPETITION_NAME", referencedColumnName = "name")
private PujCompetitionEntity competition;
} -
Составной первичный ключ таблицы соединений
public class PujInstitutionRoles_PK implements Serializable {
private String institution;
private String competition;
}* Обратите внимание, что @IdClass — это простой тип Java, а не сущность .
* Важная деталь : имена полей класса ID должны совпадать с именами полей идентификаторов таблицы соединений .
-
Модель института *
@Entity
public class PujInstitutionEntity implements Serializable {
@Id
@Column(length = 20)
private String acronym;
} -
Модель конкурса *
@Entity
public class PujCompetitionEntity implements Serializable {
@Id
@Column(length = 12)
private String name;
}
* В моей реальной модели у меня также есть сопоставление сущностей с таблицей соединений, но я здесь опущен, чтобы примеры были короче.
Использование модели : наше объединение служит двум основным целям: поддерживать отношения между учреждениями и соревнованиями, а также позволяет нам запрашивать эти отношения. Вставка нового отношения — это обычная операция вставки, но запросы в таблице соединений требуют использования именованных запросов .
Как найти конкурсы по заведениям?
Правильный способ найти это отношение — определить @NamedQuery, где я могу найти учреждения по конкурсам, как показано ниже. Я использую некоторые константы для облегчения ссылки на запросы в других классах.
@NamedQueries( {
@NamedQuery(name = PujCompetitionEntity.SQL.FIND_BY_INSTITUTION,
query = "SELECT roles.competition FROM PujInstitutionRoles roles JOIN roles.institution inst WHERE inst.acronym=:"
+ PujCompetitionEntity.SQL.PARAM_INSTITUTION_ACRONYM) })
@Entity
public class PujCompetitionEntity implements Serializable {
public static final String FIND_BY_INSTITUTION = "findCompetitionByInstitution";
public static final String PARAM_INSTITUTION_ACRONYM = "institutionAcronym";
}
Пример использования:
@Stateless
public class PujCompetitionFacadeImpl {
@PersistenceUnit(name = "arenapuj")
protected EntityManagerFactory emf;
public Collection<PujCompetitionEntity> findByInstitution(String acronym,
int start, int max) throws IllegalStateException, IllegalArgumentException {
EntityManager manager = emf.createEntityManager();
try {
Query query = manager
.createNamedQuery(PujCompetitionEntity.FIND_BY_INSTITUTION);
query.setParameter(PujCompetitionEntity.PARAM_INSTITUTION_ACRONYM,
acronym);
query.setFirstResult(start);
query.setMaxResults(max);
return getResultList(query);
} finally {
if (manager != null && manager.isOpen()) {
manager.close();
}
}
}
}
Некоторые живые примеры:
Резюме
Решение вышеуказанной проблемы предсказывается в спецификации JPA, но подробности аннотаций для реализации таблицы соединений с дополнительным состоянием не столь интуитивны (IMO). Я задокументировал решение для быстрого ознакомления в будущем, и я надеюсь, что вы также можете извлечь из этого пользу — если вы не согласны с моим моделированием или если у вас есть какие-либо хорошие предложения, пожалуйста, дайте мне свой отзыв.