
Проблему, решения которой я сегодня хотел бы описать — это повторяющийся набор полей в Hibernate сущностях. Конечно, её можно было бы решить с помощью нормализации БД, но это неудобно при выборках и влияет на быстродействие, лишние джойны ради нескольких колонок — никому не нужны.
Итак, представим, есть какая-то система учёта, в ней в любой сущности важно хранить историю, кто менял, кто создавал, когда были последние изменения, кем созданы. На самом деле в любом проекте можно найти подобные наборы и не один. В результате, когда программисты создают эти поля, в лучшем случае получается копипаст, а иногда рождаются новые названия для тех же полей.
Я хотел бы рассмотреть два способа решения этой задачи.
Первый способ
Эту задачу можно решить при помощи @Embeddable сущности:
package ru.kabit.entity.embeded; import javax.persistence.Embeddable; import java.util.Date; @Embeddable public class HistoryFields { private Long lastModifierId; private Long creatorId; private Date lastModifyDate; private Date createDate; /* getters and setters */ }
Убираем из класса выносимые поля, вставляем одно Embedded свойство. На практике пройтись по коду и добавить лишний геттер перед вызовом этих полей не составляет труда, а вот в XML, JSP найти все места бывает сложно.
package ru.kabit.entity; import ru.kabit.entity.embeded.HistoryFields; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Post { @Id @GeneratedValue private Long id; @Embedded private HistoryFields historyFields; /* getters and setters */ }
Если хоть одно поле из HistoryFields будет заполнено, то создастся объект HistoryFields и эти поля заполнятся, иначе вместо объекта будет лежать null, это кстати, очень удобно при написании логики. Имена полей в БД могут быть разными, чтобы их изменить используется аннотация @AttributeOverride.
Преимущества такого подхода:
- Логическая группа полей выделена в отдельную сущность, поля всегда будут называться одинаково, никому не придёт в голову их написать «по правильному»
- Выделенные данные выбираются без подзапросов, так как лежат в этой же таблице
- Поисковые критерии по этим полям будут неизменными
- Таких полей в сущности может быть несколько
Недостатки:
- В некоторых случаях набор полей может быть излишним, убрать их не получится
- При рефакторинге придётся изменять JSP руками, трудно это сделать в большом проекте, придётся всё перепроверять, либо сделать дополнительные геттеры, достающие данные из Embedded поля
Второй способ
Второе решение — использовать @MappedSuperclass аннотацию:
package ru.kabit.entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import java.util.Date; @MappedSuperclass public class HistoryEntity { @Id @GeneratedValue private Long id; private Long lastModifierId; private Long creatorId; private Date lastModifyDate; private Date createDate; /* getters and setters */ }
Теперь есть базовый класс с набором полей и когда мы видим похожий набор, можно просто отнаследоваться от него:
package ru.kabit.entity; import javax.persistence.Entity; @Entity public class Table1 extends HistoryEntity { private Long otherFieldTable1; public Long getOtherFieldTable1() { return otherFieldTable1; } public void setOtherFieldTable1(Long otherFieldTable1) { this.otherFieldTable1 = otherFieldTable1; } }
Преимущества такого подхода:
- Выделенные данные выбираются без подзапросов, так как лежат в этой же таблице
- При вынесении полей на рабочем проекте, не придётся ничего рефакторить
Недостатки:
- В некоторых случаях набор полей может быть излишним, убрать их не получится
- Множественного наследования в Java нет, поэтому если сущность подходит под два типа, то этот способ не подходит
Вывод
Я рассказал как избавиться от повторного описания набора полей в Hibernate сущностях, а также описал преимущества и недостатки каждого. Надеюсь, что эта статья окажется вам полезна.
