Требования к JPA Entity
Entity — объект с persistent identity, состояние которого persistence provider отображает в реляционные данные.
Минимальный пример
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue
private Long id;
@Version
private long version;
@Column(nullable = false)
private String state;
protected Order() {
}
public Order(String state) {
this.state = Objects.requireNonNull(state);
}
public Long getId() {
return id;
}
}Портируемые требования Jakarta Persistence
Entity class:
- отмечен
@Entityили объявлен в XML mapping; - является top-level class или
staticnested class; - не является
final; - имеет
publicилиprotectedconstructor без параметров; - не является enum, record или interface;
- содержит primary key через
@Idили@EmbeddedId; - не объявляет persistent fields/properties и соответствующие методы
final.
Hibernate иногда умеет работать с более мягкими условиями, но такой код уже опирается на provider-specific behavior. Например, final class ограничивает proxy-based lazy loading.
Field access и property access
Положение @Id обычно определяет access type:
@Id
private Long id; // field accessProvider читает persistent fields напрямую. Mapping annotations ставятся на поля.
@Id
public Long getId() { // property access
return id;
}Provider работает через properties. Mapping annotations ставятся на getters. Смешивать placement случайно нельзя; для явного переключения существует @Access.
См. Аннотация Access.
Identity и equals
Database id может появиться только после persist/flush. Если equals/hashCode основаны на mutable generated id, объект способен изменить hash после помещения в HashSet.
Единого универсального шаблона нет. Нужно решить:
- есть ли стабильный natural/business key;
- сравниваются ли transient entities;
- проходит ли объект detach/merge;
- используется ли proxy subclass;
- попадает ли entity в hash collection.
См. Equals и hashCode.
Entity и inheritance
Абстрактный class может быть entity и участвовать в polymorphic query. Стратегия хранения hierarchy задаётся отдельно: Cтратегии маппинга иерархии наследования Inheritance Mapping Strategies в JPA.
Entity может наследоваться:
- от другой entity;
- от
@MappedSuperclass; - от обычного non-entity class.
Persistent mapping обычного non-entity superclass не наследуется автоматически.
@MappedSuperclass
Mapped superclass сам не является entity и не имеет собственной table:
@MappedSuperclass
public abstract class Audited {
@Column(nullable = false)
private Instant createdAt;
}Его mappings применяются к таблицам entity-наследников. По самому Audited нельзя выполнить entity query или создать association к нему как к entity.
Entity и Embeddable
| Entity | Embeddable |
|---|---|
| имеет identity | identity определяется владельцем |
| управляется отдельно | является частью состояния owner |
| может иметь lifecycle | сохраняется вместе с owner |
| участвует в associations | встраивается через @Embedded |
Подробнее: Встраиваемый класс Embeddable.
Практические ограничения
- Entity не стоит отдавать прямо как API DTO: mapping, lazy loading и contract API начинают зависеть друг от друга.
- Constructor без параметров нужен provider, но application может использовать отдельный constructor с инвариантами.
toString, JSON serialization и debugger способны обратиться к lazy association.- Entity instance не потокобезопасен и не должен совместно изменяться несколькими threads.
- Setter для каждого поля не является требованием JPA.
Вопросы для собеседования
Может ли entity быть abstract?
Да.
Может ли entity быть record?
Нет по портируемому контракту Jakarta Persistence.
Обязан ли no-arg constructor быть public?
Нет, допустим protected. Дополнительные constructors разрешены.
Зачем entity не должна быть final?
Это требование portable persistence и возможность provider создавать proxy/subclass.
Нужен ли Serializable?
Не для самого факта persistence. Он нужен, если detached entity передаётся by value через соответствующую удалённую границу.