← Назад к вопросам

Какие знаешь правила описания Entity?

2.3 Middle🔥 151 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Правила описания Entity в JPA

Entity — это класс, который представляет таблицу в базе данных. Правильное описание Entity критически важно для корректной работы ORM и избежания проблем с производительностью и консистентностью данных.

Основные требования к Entity

1. Обязательная аннотация @Entity

@Entity
@Table(name = "users")
public class User {
    // Класс должен быть помечен как Entity
}

2. Обязательный no-arg конструктор

JPA требует наличие конструктора без аргументов для создания экземпляров через рефлексию:

@Entity
public class User {
    private Long id;
    private String name;
    
    // Требуется для JPA
    public User() {
    }
    
    // Конструктор с аргументами для удобства
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

3. Не должен быть final

Entity не должен быть объявлен как final, так как JPA использует прокси для ленивой загрузки:

// ❌ Неправильно
@Entity
public final class User { }

// ✅ Правильно
@Entity
public class User { }

4. Методы не должны быть final

Это же касается методов — они не должны быть final для работы с прокси:

@Entity
public class User {
    // ❌ Неправильно
    public final void setName(String name) { }
    
    // ✅ Правильно
    public void setName(String name) { }
}

Определение первичного ключа

Простой первичный ключ

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String email;
}

Стратегии генерации ключа

// AUTO — JPA выбирает оптимальную стратегию (по умолчанию)
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

// IDENTITY — использует auto-increment БД
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// SEQUENCE — использует последовательность БД
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "public.user_id_seq")
private Long id;

// TABLE — использует специальную таблицу для генерации
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_gen")
@TableGenerator(name = "user_gen", table = "sequences")
private Long id;

Составной первичный ключ (Composite Key)

@Embeddable
public class UserId implements Serializable {
    private Long userId;
    private String region;
    
    public UserId() { }
    
    public UserId(Long userId, String region) {
        this.userId = userId;
        this.region = region;
    }
    
    // equals и hashCode обязательны
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserId that = (UserId) o;
        return Objects.equals(userId, that.userId) && 
               Objects.equals(region, that.region);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(userId, region);
    }
}

@Entity
public class UserRegion {
    @EmbeddedId
    private UserId id;
    
    private String data;
}

Типы полей и маппинг

Базовые типы данных автоматически маппируются

@Entity
public class User {
    @Id
    private Long id;
    
    private String name;        // VARCHAR(255)
    private int age;            // INTEGER
    private BigDecimal salary;  // NUMERIC
    private boolean active;     // BOOLEAN
    private LocalDate birthDate; // DATE
    private LocalDateTime createdAt; // TIMESTAMP
}

Пользовательское маппинг колонки

@Entity
public class User {
    @Column(name = "user_email", length = 100, nullable = false, unique = true)
    private String email;
    
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @Column(columnDefinition = "VARCHAR(500) DEFAULT Active")
    private String status;
}

Транзиентные поля

@Transient — поле не будет сохранено в БД:

@Entity
public class User {
    @Id
    private Long id;
    
    private String firstName;
    private String lastName;
    
    @Transient
    private String fullName;
    
    @PostLoad
    public void initFullName() {
        this.fullName = firstName + " " + lastName;
    }
}

Отношения между Entity

OneToMany и ManyToOne

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Post> posts = new ArrayList<>();
}

@Entity
public class Post {
    @Id
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    
    private String title;
}

ManyToMany

@Entity
public class User {
    @Id
    private Long id;
    
    @ManyToMany(cascade = CascadeType.PERSIST)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();
}

@Entity
public class Role {
    @Id
    private Long id;
    
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();
}

OneToOne

@Entity
public class User {
    @Id
    private Long id;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "profile_id", unique = true)
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id
    private Long id;
    
    @OneToOne(mappedBy = "profile")
    private User user;
}

Жизненный цикл Entity

Callback аннотации

@Entity
public class User {
    @Id
    private Long id;
    
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
    
    @PostLoad
    protected void onLoad() {
        // Логика после загрузки
    }
    
    @PreRemove
    protected void onRemove() {
        // Логика перед удалением
    }
}

Наследование в Entity

Single Table Strategy — все классы в одной таблице:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "person_type")
public class Person {
    @Id
    private Long id;
    private String name;
}

@Entity
@DiscriminatorValue("EMPLOYEE")
public class Employee extends Person {
    private String jobTitle;
}

Важные практики

1. Используйте Serializable для составных ключей

@Embeddable
public class CompositeKey implements Serializable {
    // ...
}

2. Переопределяйте equals() и hashCode() для правильной работы Set и Map

3. Избегайте циклических зависимостей в отношениях

4. Используйте @JsonIgnore для избежания циклов при сериализации

@Entity
public class User {
    @OneToMany(mappedBy = "user")
    @JsonIgnore
    private List<Post> posts;
}

5. Не забывайте про fetch type при наличии больших коллекций

@OneToMany(fetch = FetchType.LAZY) // По умолчанию для OneToMany
private List<Post> posts;

@ManyToOne(fetch = FetchType.EAGER) // По умолчанию для ManyToOne
private User user;

Соблюдение этих правил обеспечивает надёжную работу ORM, правильную маппинг данных и оптимальную производительность приложения.

Какие знаешь правила описания Entity? | PrepBro