JPA, смешанный суррогатный ключ с внешним ключом и порядковым номером
-
29-09-2019 - |
Вопрос
У меня есть две таблицы:
DOCUMENT
--------
DOC_ID (PK)
.
.
.
SECTION
-------
DOC_ID (FK, PK)
SECTION_NUM (PK)
.
.
.
Записи в базе данных могут выглядеть так:
Документ:
DOC_ID | . . .
--------------
1 | . . .
2 | . . .
Раздел:
DOC_ID | SECTION_NUM | . . .
---------------------------
1 | 1 | . . .
1 | 2 | . . .
1 | 3 | . . .
2 | 1 | . . .
Document
имеет сгенерированный идентификатор на doc_id, пока Section
Имеет композитный первичный ключ над doc_id и section_num.
Раздел_num - это локально (приложение), создаваемое порядковым числом, начинающим свежим для каждого документа.
Мои классы сущности выглядят следующим образом:
@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
@Id
@Column(name = "DOC_ID", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DocIdSeq")
@SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
}
@Entity
@Table(name = "SECTION")
@IdClass(SectionId.class)
public class Section implements java.io.Serializable {
@Id
@Column(name = "DOC_ID", nullable = false)
private Long docId;
@Id
@Column(name = "SECTION_NUM", nullable = false)
private Integer sectionNum;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "DOC_ID")
private Document document;
}
public class SectionId implements java.io.Serializable {
private Long docId;
private Integer sectionNum;
}
При вставке нового документа и соответствующего раздела я делаю следующее:
Document doc = new Document();
Section section = new Section();
section.setDocument(doc);
section.setSectionNum(1);
entityManager.persist(doc);
При сохранении я получаю исключение, указав, что NULL не допускается для раздела столбца_num. Я использую OpenEJB (который опирается на OpenJPA за сценами для тестирования подразделения), и нашел при наступлении через код OpenJPA, который он успешно сохраняет объект документа, но когда речь идет о объекте разделе, он создает новый экземпляр и устанавливает все Поля в NULL, поэтому теряя ценность в разрезе, прежде чем связывать его на объект документа, сохранялся ранее.
К сожалению, я не могу изменить схему БД, как это устаревшая система. Кто-нибудь сделал что-то похожее и получил его работать?
Решение
Я имел в виду, чтобы обновить это в течение некоторого времени, но был слишком занят ...
Хорошо, так оказывается, что это не возможно с JPA. Тем не менее, есть обходной путь.
Ранее я упомянул, что класс документа выглядит так.
@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
@Id
@Column(name = "DOC_ID", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
@SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
}
Это была только укороченная версия, чтобы уточнить вопрос. Настоящий класс также имеет коллекцию разделов:
@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
@Id
@Column(name = "DOC_ID", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
@SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
@OneToMany
private Set<Section> sections = new HashSet<Section>(0);
}
Если бы раздел был простым первичным ключом, JPA будет легко справиться с отношениями, так как он примет ID из приложения или генерирует его из последовательности, но она не будет делать оба с одним идентификатором.
Итак, решение состоит в том, чтобы управлять отношениями самостоятельно и добавить функцию жизненного цикла:
@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
@Id
@Column(name = "DOC_ID", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
@SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
@Transient
private Set<Section> sections = new HashSet<Section>(0);
@PostPersist
public void updateChildIds() {
for (Section section : this.sections) {
section.getId().setDocId(this.docId);
}
}
}
Как видите, соотношение раздела теперь переходно, то есть JPA не будет управлять этим. После того, как сохраняют документ, структура позвонит функцию UpdateChildIds, вручную обновляю идентификатор раздела с новым сохраненным идентификатором документа.
Это может быть продемонстрировано в следующем фасаде:
@Stateless
public void DocumentFacade implements DocumentFacadeLocal {
@PersistenceContext
private EntityManager entityManager;
public void save(Document entity) throws Exception {
this.entityManager.persist(entity);
this.entityManager.flush();
this.persistTransientEntities(entity);
this.entityManager.flush();
}
private void persistTransientEntities(CaseInstructionSheet entity) {
for (Section section : entity.getSections()) {
this.entityManager.persist(section);
}
}
}
Другие советы
На самом деле, JPA идеально может справиться с этим. Аннотация, которую вы ищете, это MapsId
.
В вашем случае, в вашем Section
, на docId
Вам просто нужно добавить следующее:
@MapsId("docId")
Значение MapsId
Аннотация - это имя атрибута вашего составного первичного ключа (который в этом случае одинаково)