Статьи

Таблица присоединения JPA с дополнительным состоянием

У JPA есть свои загадки, и время от времени полезно записать это хитрое решение для наших задач отображения. В этой записи описывается отношение ManyToMany с дополнительным состоянием в промежуточной таблице. Все мои примеры связаны с проектом Arena-PUJ, любимым проектом, над которым я сейчас работаю. Arena — это онлайн-система для управления академическими соревнованиями, и в ее нескольких таблицах и сопоставлениях есть угловой случай, который я объясню ниже. Во-первых, позвольте мне определить сущности и их отношения для моделирования данных отношений Institutions X Competition .

Учреждения X Соревнования

У нас есть две сущности, имеющие отношения ManyToMany:

  • Конкурс : это виртуальный конкурс, в котором учащиеся подают лучшие домашние задания в регионе (город, штат или даже страна). Несколько соревнований проходят одновременно в разных местах, спонсируются и организуются разными учреждениями.
  • Учреждение : компания, кувшин или школа. Он моделирует университет конкурентов, JUG, организующий соревнование, или компанию, спонсирующую соревнование. Учреждения играют роли в конкурсе.

Проблема: роли учреждения динамичны

Соревнования проводятся ежегодно, и для разных соревнований одно и то же учреждение может играть разные роли. Классический пример касается спонсорства: компания, которая была партнером в 2008 году, становится платиновым спонсором в 2009 году. Моделирование ролей учреждений динамично — учреждения могут играть разные роли в разных конкурсах .

Решение: таблица соединений с дополнительным состоянием

Связь между соревнованиями и учреждениями — это связь @ManyToMany, но мы не можем просто аннотировать сущности . Чтобы поддерживать динамические роли учреждений, нам нужно настроить таблицу отношений , что означает, что нам нужно добавить дополнительные столбцы в таблицу соединений , как показано на диаграмме ниже.

Реализация таблицы соединений с JPA 1.x Аннотации

Стол соединения имеет отношения @ManyToOne с двумя объектами, а также перечисление с возможными ролями, которые учреждение может играть в соревновании. Чтобы работать как настоящая таблица соединений, вы должны использовать @ClassId в качестве составного первичного ключа таблицы соединений. Вы можете проверить полный исходный код отсюда , но соответствующие части находятся в фрагментах ниже.

  1. Таблица соединения:

    @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;
    }
  2. Составной первичный ключ таблицы соединений

    public class PujInstitutionRoles_PK implements Serializable {
    private String institution;
    private String competition;
    }

    * Обратите внимание, что @IdClass — это простой тип Java, а не сущность .

    * Важная деталь : имена полей класса ID должны совпадать с именами полей идентификаторов таблицы соединений .

  3. Модель института *

    @Entity
    public class PujInstitutionEntity implements Serializable {
    @Id
    @Column(length = 20)
    private String acronym;
    }
  4. Модель конкурса *

    @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). Я задокументировал решение для быстрого ознакомления в будущем, и я надеюсь, что вы также можете извлечь из этого пользу — если вы не согласны с моим моделированием или если у вас есть какие-либо хорошие предложения, пожалуйста, дайте мне свой отзыв.

От http://weblogs.java.net/blog/felipegaucho