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