JPA АНОТАЦИИ (ENTITY МОДЕЛИРАНЕ)

Jakarta Persistence (JPA) е стандарт за обектно-релационно свързване (ORM) в Java, който позволява работа с релационни бази от данни чрез обекти. Вместо да пишем директно SQL заявки, JPA ни позволява да работим с Java обекти, които автоматично се преобразуват към записи в таблиците на базата.

JPA използва обикновени Java класове, наречени POJO (Plain Old Java Objects), за да представи данни. Тези класове съдържат само полета и методи за достъп (get/set) без да зависят от специфична инфраструктура или базови класове. Когато добавим JPA анотации към POJO клас, той се превръща в entity, който може да бъде запазен в базата. Един entity представлява таблица, a всеки ред в таблицата съответства на обект от този клас.

@Entity

Анотацията се добавя на ниво клас, като задава последният като entity. Entity класът трябва да разполага с дефолтен конструктор и поле, зададено като първичен ключ.

@Entity
public class Student {
    @Id
    private Long id;
    private String name;
    // getters and setters
}

@Table

Задава първичната таблица на анотирания entity.

В повечето случаи не желаем името на таблицата в базата от данни да съвпада с името на entity класа. При подобна ситуация, можем изрично да го посочим посредством анотацията @Table.

@Entity
@Table(name="Students")
public class Student {
    
    // fields, getters and setters
}

С помощта на опционалните елементи на анотацията indexes, uniqueConstraints, schema и catalog могат да се посочат индексите, ограниченията за уникалност, схемата и каталог на таблицата.

@Id

Всеки JPA обект трябва да има първичен ключ, който го идентифицира уникално. Анотацията @Id дефинира първичния ключ. Можем да генерираме идентификаторите по различни начини, които са посочени от анотацията @GeneratedValue. Можем да избираме от четири стратегии за генериране на идентификатор с елемента strategy. Стойността може да бъде AUTO, TABLE, SEQUENCE, UUID или IDENTITY.

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // getters and setters
}

@Column

Уточнява детайлите на колона в таблицата. Може да бъдат зададени елементи като name, length, nullable и unique.

@Entity
@Table(name="Students")
public class Student {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    
    @Column(name="student_name", length=50, nullable=false, unique=false)
    private String name;
    
    // other fields, getters and setters
}

@Transient

Понякога може да искаме да направим дадено поле преходно. Можем да постигнем това с анотацията @Transient. Тя указва, че полето няма да бъде запазено. Например можем да изчислим възрастта на ученик от датата на раждане. Така че нека да коментираме възрастта на полето с анотацията @Transient:

@Entity
@Table(name="Students")
public class Student {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    
    @Column(name="student_name", length=50, nullable=false)
    private String name;
    
    @Transient
    private Integer age;
    
    // other fields, getters and setters
}

@CreationTimestamp

Hibernate анотация. Маркира полето като печат за времето на създаване на обекта.

@Entity
public class Book {
    //other fields

    @CreationTimestamp
    private Instant createdOn;

    // standard setters and getters
}

@UpdateTimestamp

Hibernate анотация. Маркира полето като печат за времето на актуализиране на обекта.

@Entity
public class Book {
    //other fields

    @UpdateTimestamp
    private Instant lastUpdatedOn;

    // standard setters and getters
}

ВРЪЗКИ МЕЖДУ ТАБЛИЦИ

В релационните бази връзките се реализират чрез foreign keys, а в JPA връзките се моделират чрез обектни релации, които се мапват към тези foreign keys от базата данни.

Това означава, че вместо да се работи с ID стойности, се използват директно обекти.

@OneToMany

Указва асоциация едно към много (1:M).

Анотацията @OneToMany може да се използва в рамките на клас за вграждане, съдържащ се в entity клас, за да се укаже връзка с колекция от обекти. Ако връзката е двупосочна, елементът mappedBy трябва да се използва за указване на полето на връзката или свойството на entity, който е собственик на връзката.

Пример:

Клас Customer:

@Entity
@Table(name="customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    ****

    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, 
    orphanRemoval = true)
    private Set<Order> orders = new HashSet<>();
}

CascadeType

CascadeType определя кои операции от жизнения цикъл на едно entity се прилагат автоматично и върху свързаните с него entity-та.

cascade = CascadeType.ALL

Видове:

  • ALL → всички операции
  • PERSIST → при запис
  • MERGE → при update
  • REMOVE → при delete

@ManyToOne

Анотацията @manytoone указва връзка много към един (M:1) между entity класове, при която множество обекти от един тип са свързани с един обект от друг тип. В релационната база това се реализира чрез foreign key в таблицата на „many“ страната.

Обикновено се използва за foreign key връзки.

Пример:

Клас Order:

@Entity
@Table(name="reports")
public class Report {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

	***

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="task_id", nullable = false)
    private Task task;
}

FetchType (LAZY / EAGER)

FetchType определя кога се зареждат асоциираните entity обекти – веднага при извличане на основния обект (EAGER) или отложено при първи достъп до тях (LAZY).

@ManyToOne(fetch = FetchType.LAZY)

Стойности на FetchType:

  • LAZY
    • данните се зареждат само когато са нужни
    • по-ефективно за производителност
  • EAGER
    • данните се зареждат веднага
    • може да доведе до излишни заявки

@JoinColumn

Анотацията указва колоната в таблицата, която служи като foreign key.

Тя се използва за да дефинира връзката между две таблици.

@JoinColumn(name = "task_id")
private Task task;
  • name → името на колоната в таблицата
  • свързва текущия entity с друг entity

По подразбиране:
Ако НЕ се зададе name, се генерира автоматично:

<field_name>_<referenced_id>

Където:
<field_name> = името на полето в entity-то
<referenced_id> = името на primary key колоната на свързаната таблица

@ManyToMany

Указва асоциация много към много (M:M).

Това е тип връзка в релационните бази данни, при която:

  • един запис от първата таблица може да има много записи от втората
  • и един запис от втората може да има много от първата

Затова НЕ може да се реализира директно с foreign key между двете таблици. Вместо това се използва междинна (join) таблица, която свързва двете entities чрез техните primary keys.

Пример 1

@Entity
@Table(name="Customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
     ***
     
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="Cust_phones")
    private Set<PhoneNumber> phones;
}
  • Customer има множество PhoneNumber обекти
  • @JoinTable указва, че ще бъде създадена междинна таблица с име Cust_phones
  • тъй като не са зададени колони, JPA ще ги генерира автоматично
@Entity
@Table(name="phone")

public class PhoneNumber {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany(mappedBy="phones", fetch = FetchType.EAGER)
    private Set<Customer> customers;
}
  • PhoneNumber също има множество Customer обекти
  • mappedBy=”phones” показва, че тази страна НЕ управлява връзката
  • връзката се управлява от полето phones в класа Customer

Пример 2

@Entity
@Table(name="customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    ***
    
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "CUST_PHONES",
	joinColumns = @JoinColumn(name = "customer_id", referencedColumnName = "id"),
	inverseJoinColumns = @JoinColumn(name = "phone_id", referencedColumnName = "id"))
private Set<PhoneNumber> phones;
}
  • name = “CUST_PHONES” → името на междинната таблица
  • joinColumns → колоната, която сочи към текущия entity (Customer)
  • inverseJoinColumns → колоната, която сочи към другия entity (PhoneNumber)
@Entity
@Table(name="phone_numbers")
public class PhoneNumber {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ****

    @ManyToMany(mappedBy="phones")
    private Set<Customer> customers; 
}
  • отново имаме обратната страна на връзката
  • mappedBy=”phones” показва, че връзката вече е дефинирана в Customer
  • този клас само я използва, без да я управлява

This site uses Just the Docs, a documentation theme for Jekyll.