Object Relational Mapping (ORM) — это концепция/процесс преобразования данных из объектно-ориентированного языка в реляционные БД и наоборот.
Например, в JAVA это делается с помощью рефлексии и JDBC.
JDBC (Java DataBase Connectivity) — API для работы с реляционными (зависимыми) БД.
Платформенно независимый промышленный стандарт взаимодействия Java-приложений с различными СУБД, реализованный в виде пакета java.sql, входящего в состав JAVA SE.
Предоставляет методы для получения и обновления данных. Не зависит от конкретного типа базы. Библиотека, которая входит в стандартную библиотеу, содержит: набор классов и интерфейсов для работы с БД (для нас разработчиков api) + интерфейсы баз данных.
JDBC реализует механизмы работы подключений к базе данных, создания запросов и обработки результатов.
Connection (класс) объект которого отвечает за соединение с базой и режим работы с ней.
Statement (объект для оператора JDBC) используется для отправки SQL-оператора на сервер баз данных.
Объект для оператора связан с объектом Connection и является объектом, обрабатывающим взаимодействие между приложением и сервером баз данных.
Можно:
Виды Statement-ов:
это API, который позволяет:
в JAVA есть специальный класс по имени Class. Поэтому его и называют классом класса. С помощью него осуществляется работа с рефлексией.
Java Persistence API (JPA) - это спецификация (стандарт, технология), обеспечивающая объектно-реляционное отображение простых JAVA-объектов (Plain Old Java Object - POJO) и предоставляющая универсальный API для сохранения, получения и управления такими объектами.
Сам JPA не умеет ни сохранять, ни управлять объектами, JPA только определяет правила игры: как должен действовать каждый провайдер (Hibernate, EclipseLink, OJB, Torque и т.д.), реализующий стандарт JPA. Для этого JPA определяет интерфейсы, которые должны быть реализованы провайдерами. Также JPA определяет правила, как должны описываться метаданные отображения и как должны работать провайдеры. Каждый провайдер обязан реализовывать всё из JPA, определяя стандартное получение, сохранение и управление объектами. Помимо этого, провайдеры могут добавлять свои личные классы и интерфейсы, расширяя функционал JPA.
JPA
Hibernate - это провайдер, реализующий спецификацию JPA. Hibernate полностью реализует JPA плюс добавляет функционал в виде своих классов и интерфейсов, расширяя свои возможности по работе с сущностями и БД.
Это интерфейс JPA, используемый для взаимодействия с персистентным контекстом.
EntityManager описывает API для всех основных операций над Entity, а также для получения данных и других сущностей JPA.
По сути - главный API для работы с JPA.
Персистентный контекст - это набор экземпляров сущностей, загруженных из БД или только что созданных.
Персистентный контекст является своего рода кэшем данных в рамках транзакции - это и есть кэш первого уровня.
Внутри контекста персистентности происходит управление экземплярами сущностей и их жизненным циклом.
EntityManager автоматически сохраняет в БД все изменения, сделанные в его персистентном контексте, в момент коммита транзакции, либо при явном вызове метода flush().
Один или несколько EntityManager образуют или могут образовать persistence context.
Если проводить аналогию с обычным JDBC, то EntityManagerFactory будет аналогом DataSource, а EntityManager аналогом Connection.
Создание EntityManagerFactory довольно дорогая операция, поэтому обычно её создают один раз и на всё приложение. А чаще всего не создают сами, а делегируют это фреймворку, такому как Spring, например.
Интерфейс Session из Hibernate представлен в JPA как раз интерфейсом EntityManager.
JPA | JDBC (по аналогии) | Hibernate |
---|---|---|
EntityManagerFactory | DataSource | SessionFactory |
EntityManager | Connection | Session |
JPQL | HQL |
Операции над Entity:
Получение данных:
Получение других сущностей JPA
Работа с EntityGraph
Общие операции
Объекты EntityManager не являются потокобезопасными.
Это означает, что каждый поток должен получить свой экземпляр EntityManager, поработать с ним и закрыть его в конце.
Сущность (entity) - это объект персистентной области.
Как правило, сущность представляет таблицу в реляционной базе данных, и каждый экземпляр сущности соответствует строке в этой таблице.
Основным программным представлением сущности является класс сущности.
Класс сущности может использовать другие классы, которые служат вспомогательными классами или используются для представления состояния сущности (например embedded).
Персистентное состояние сущности представлено персистентными полями или персистентными свойствами.
Персистентное поле - поле сущности, которое отражается в БД в виде столбца таблицы.
Персистентное свойство - это методы, которые аннотированы вместо полей для доступа провайдера к ним (полям).
Эти поля или свойства используют аннотации объектно-реляционного сопоставления (маппинга) для сопоставления сущностей и отношений между ними с реляционными данными в хранилище данных.
Примеры аннотаций: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany.
Есть два вида доступа к состоянию сущности:
Доступ по полю, когда аннотации стоят над полями.
В этом случае провайдер, например, Hibernate, обращается к полям класса напрямую, используя Reflection.
Доступ по свойству, когда аннотации стоят над методами-геттерами.
В этом случае провайдер, например, Hibernate, обращается к полям класса через методы.
В JPA принято называть эти персистентные поля и свойства атрибутами класса-сущности.
Entity класс не может быть финальным классом (final class).
Entity класс не может содержать финальные поля или методы, если они участвуют в маппинге (persistent final methods or persistent final instance variables).
Как обычный так и абстрактный класс может быть Entity.
Entities могут наследоваться как от не Entity классов, так и от Entity классов.
А не Entity классы могут наследоваться от Entity классов.
Поля Entity класса должны быть объявлены private, protected или package-private, быть напрямую доступными только методам самого Entity класса и не должны быть напрямую доступны другим классам, использующим этот Entity.
Другие классы должны обращаться только к специальным методам Entity класса, предоставляющим доступ к этим полям (getter/setter-методам или другим методам бизнес-логики в Entity классе).
Hibernate не так строг в своих требованиях. Вот отличия от требований JPA:
Технически Hibernate может сохранять финальные классы или классы с финальными методами (getter / setter).
Однако, как правило, это не очень хорошая идея, так как это лишит Hibernate возможности генерировать прокси для отложенной загрузки сущности.
Hibernate не запрещает разработчику приложения открывать прямой доступ к переменным экземпляра и ссылаться на них извне класса сущности.
Однако обоснованность такого подхода спорна.
Абстрактный класс может быть Entity классом.
Абстрактный Entity класс отличается от обычных Entity классов только тем, что нельзя создать объект этого класса. Имена абстрактных классов могут использоваться в запросах.
Абстрактные Entity классы используются в наследовании, когда их потомки наследуют поля абстрактного класса:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Employee {
@Id
@GeneratedValue
private long id;
private String name;
...
}
@Entity
@Table(name = "FULL_TIME_EMP")
public class FullTimeEmployee extends Employee
private int salary;
...
}
@Entity
@Table(name = "PART_TIME_EMP")
public class PartTimeEmployee extends Employee {
private int hourlyRate;
...
}
Таблица вариантов наследования Entity классов:
Родитель/Наследник | Entity класс | не Entity класс |
---|---|---|
Entity класс | + | + |
не Entity класс | + | обычное наследование в Java |
Да, сущности могут наследоваться от не Entity классов, которые, в свою очередь, могут быть как абстрактными, так и обычными.
Состояние (поля) не Entity суперкласса не является персистентным, то есть не хранится в БД и не обрабатывается провайдером (Hibernate), поэтому любое такое состояние (поля), унаследованное Entity классом, также не будет отображаться в БД.
Не Entity суперклассы не могут участвовать в операциях EntityManager или Query. Любые маппинги или аннотации отношений в не Entity суперклассах игнорируются.
Да, может.
Да, может.
Это класс, который не используется сам по себе, а только как часть одного или нескольких Entity классов.
Hibernate называет эти классы компонентами.
JPA называет их встраиваемыми.
В любом случае, концепция одна и та же: композиция значений.
Встраиваемый класс помечается аннотацией @Embeddable.
Встраиваемый класс может быть встроен в несколько классов-сущностей, но встроенный объект с конкретным состоянием принадлежит исключительно владеющей им сущности и не может использоваться одновременно другими сущностями, он не является общим для нескольких сущностей.
То есть, если класс Person с полями name и age встроен и в класс Driver, и в класс Baker, то у обоих последних классов появятся оба поля из класса Person. Но если у объекта Driver эти поля будут иметь значения "Иван" и "35", то эти же поля у объекта Baker могут иметь совершенно иные значения, никак не связанные с объектом Driver.
В целом, встраиваемый класс служит для того, чтобы выносить определение общих атрибутов для нескольких сущностей, можно считать что JPA просто встраивает в сущность вместо объекта такого класса те атрибуты, которые он содержит.
встраиваемые классы могут использовать в качестве полей:
Например, у нас может быть встраиваемый класс ClassA, который представляет собой композицию строкового и числового значений, и эти два поля будут добавлены в класс EntityA:
@Entity
public class EntityA {
@Id
@GeneratedValue
private int id;
...
@Embedded
private ClassA classARef;
...
}
@Embeddable
public class ClassA {
private String myStr;
private int myInt;
...
}
Так как мы можем встраивать классы в неограниченное количество других классов, то у каждого класса, содержащего встраиваемый класс, мы можем изменить названия полей из встраиваемого класса.
Например, у класса Driver поля из встраиваемого класса Person будут изменены с name на driver_name и с age на driver_age:
@Embeddable
public class Person {
private String name;
name; private int age;
}
@Entity
public class Driver {
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "driver_name")),
@AttributeOverride(name = "age", column = @Column(name = "driver_age")),
})
private Person person;
...
}
Сущности, которые имеют встраиваемые классы, могут аннотировать поле или свойство аннотацией @Embedded, но не обязаны это делать.
Mapped Superclass (сопоставленный суперкласс) - это класс, от которого наследуются Entity, он может содержать аннотации JPA, однако сам такой класс не является Entity, ему не обязательно выполнять все требования, установленные для Entity (например, он может не содержать первичного ключа).
Эти суперклассы чаще всего используются, когда у нас есть общая для нескольких классов сущностей информация о состоянии и отображении, которую можно вынести в Mapped Superclass.
Для того, чтобы использовать Mapped Superclass, достаточно унаследовать его в классах-потомках:
@MappedSuperclass
public class Employee {
@Id
@GeneratedValue
private long id;
private String name;
...
}
@Entity
@Table(name = "FULL_TIME_EMP")
public class FullTimeEmployee extends Employee {
private int salary;
...
}
@Entity
@Table(name = "PART_TIME_EMP")
public class PartTimeEmployee extends Employee {
private int hourlyRate;
...
}
В указанном примере кода в БД будут таблицы FULL_TIME_EMPLOYEE и PART_TIME_EMPLOYEE, но таблицы EMPLOYEE не будет:
Это похоже на стратегию наследования “Таблица для каждого конкретного класса сущностей”, но в модели данных нет объединения таблиц или наследования. Также тут нет таблицы для Mapped Superclass. Наследование существует только в объектной модели.
Основным недостатком использования сопоставленного суперкласса является то, что полиморфные запросы невозможны, то есть мы не можем загрузить всех наследников Mapped Superclass.
Сходства:
Различия:
Стратегии наследования нужны для того, чтобы дать понять провайдеру (Hibernate) как ему отображать в БД сущности-наследники.
Для этого нам нужно декорировать родительский класс аннотацией @Inheritance и указать один из типов отображения.
Следующие три стратегии используются для отображения данных сущности-наследника и родительской сущности:
SINGLE_TABLE
Одна таблица на всю иерархию классов.
JOINED
Стратегия "соединения", при которой поля или свойства, специфичные для подклассов, отображаются в таблицах этих подклассов, а поля или свойства родительского класса отображаются в таблице родительского класса.
TABLE_PER_CLASS
Таблица для каждого конкретного класса сущностей.
Одна таблица на всю иерархию классов
Является стратегией по умолчанию и используется, когда аннотация @Inheritance не указана в родительском классе или когда она указана без конкретной стратегии.
Все entity, со всеми наследниками записываются в одну таблицу.
Для идентификации типа entity (наследника) определяется специальная колонка "discriminator column".
Минусом стратегии является невозможность применения ограничения NOT NULL для тех колонок таблицы, которые характерны только для классов-наследников.
Например, если есть entity Employee c классами-потомками FullTimeEmployee и PartTimeEmployee, то при такой стратегии все FullTimeEmployee и PartTimeEmployee записываются в таблицу Employee, и при этом в таблице появляется дополнительная колонка с именем DTYPE, в которой будут записаны значения, определяющие принадлежность к классу.
По умолчанию эти значения формируются из имён классов, в нашем случае - либо «FullTimeEmployee» либо «PartTimeEmployee».
Но мы можем их поменять в аннотации у каждого класса-наследника: @DiscriminatorValue("F").
Если мы хотим поменять имя колонки, то мы должны указать её новое имя в параметре аннотации у класса-родителя: @DiscriminatorColumn(name=EMP_TYPE).
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Entity
@DiscriminatorColumn(name = "EMP_TYPE")
public class Employee {
@Id
@GeneratedValue
private long id;
private String name;
}
@Entity
@DiscriminatorValue("F")
public class FullTimeEmployee extends Employee {
private int salary;
}
@Entity
@DiscriminatorValue("P")
public class PartTimeEmployee extends Employee {
private int hourlyRate;
}
Эта стратегия обеспечивает хорошую поддержку полиморфных отношений между сущностями и запросами, которые охватывают всю иерархию классов сущностей:
-- Persisting entities --
FullTimeEmployee{id=0, name='Sara', salary=100000}
PartTimeEmployee{id=0, name='Tom', hourlyRate='60'}
-- Native queries --
'Select * from Employee'
[F, 1, Sara, null, 100000]
[P, 2, Tom, 60, null]
-- Loading entities --
FullTimeEmployee{id=1, name='Sara', salary=100000}
PartTimeEmployee{id=2, name='Tom', hourlyRate='60'}
Стратегия "соединения"
В данной стратегии корневой класс иерархии представлен отдельной таблицей, а каждый класс-наследник имеет свою таблицу, в которой отображены только поля этого класса-наследника.
То есть таблица подкласса не содержит столбцы для полей, унаследованных от родительского класса, за исключением поля для первичного ключа @Id, который должен быть определен только в родительской таблице.
Столбец первичного ключа в таблице подкласса служит внешним ключом первичного ключа таблицы суперкласса.
Также в таблице родительского класса добавляется столбец DiscriminatorColumn с DiscriminatorValue для определения типа наследника.
@Inheritance(strategy = InheritanceType.JOINED)
@Entity
@DiscriminatorColumn(name = "EMP_TYPE") //определение типа наследника
public class Employee {
@Id
@GeneratedValue
private long id;
private String name;
...
}
@Entity
@DiscriminatorValue("F")
@Table(name = "FULL_TIME_EMP")
public class FullTimeEmployee extends Employee {
private int salary;
...
}
@Entity
@DiscriminatorValue("P")
@Table(name = "PART_TIME_EMP")
public class PartTimeEmployee extends Employee {
private int hourlyRate;
...
}
-- Persisting entities --
FullTimeEmployee{id=0, name='Sara', salary=100000}
PartTimeEmployee{id=0, name='Robert', hourlyRate='60'}
-- Native queries --
'Select * from Employee'
[F, 1, Sara]
[P, 2, Robert]
'Select * from FULL_TIME_EMP'
[100000, 1]
'Select * from PART_TIME_EMP'
[60, 2]
Эта стратегия обеспечивает хорошую поддержку полиморфных отношений, но требует выполнения одной или нескольких операций соединения таблиц при создании экземпляров подклассов сущностей.
В глубоких иерархиях классов это может привести к недопустимому снижению производительности.
Точно так же запросы, которые покрывают всю иерархию классов, требуют операций соединения между таблицами подклассов, что приводит к снижению производительности:
-- Loading entities --
List<Employee> entityAList = em
.createQuery("Select t from Employee t")
.getResultList();
// Hibernate создает соединения для сборки объектов
-- Result --
FullTimeEmployee{id=1, name='Sara', salary=100000}
PartTimeEmployee{id=2, name='Robert', hourlyRate='60'}
Таблица для каждого класса сущностей
Каждый класс-наследник имеет свою таблицу. Во всех таблицах подклассов хранятся все поля этого класса плюс те, которые унаследованы от суперкласса.
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Employee {
@Id
@GeneratedValue
private long id;
private String name;
...
}
@Entity
@Table(name = "FULL_TIME_EMP")
public class FullTimeEmployee extends Employee {
private int salary;
...
}
@Entity
@Table(name = "PART_TIME_EMP")
public class PartTimeEmployee extends Employee {
private int hourlyRate;
...
}
-- Persisting entities --
FullTimeEmployee{id=0, name='Sara', salary=100000}
PartTimeEmployee{id=0, name='Robert', hourlyRate='60'}
-- Native queries --
'Select * from Employee'
no data
'Select * from FULL_TIME_EMP'
[1, Sara, 100000]
'Select * from PART_TIME_EMP'
[2, Robert, 60]
-- Loading entities --
List<Employee> entityAList = em
.createQuery("Select t from Employee t")
.getResultList();
// Hibernate выполнит дополнительные sql-запросы
// или запросы объединения для получения сущностей
-- Result ---
FullTimeEmployee{id=1, name='Sara', salary=100000}
PartTimeEmployee{id=2, name='Robert', hourlyRate='60'}
Минусом является плохая поддержка полиморфизма (polymorphic relationships) и то, что для выборки всех классов иерархии потребуется большое количество отдельных sql-запросов для каждой таблицы-наследника или использование UNION-запроса для соединения таблиц всех наследников в одну таблицу.
Также недостатком этой стратегии является повторение одних и тех же атрибутов в таблицах.
При TABLE PER CLASS не работает стратегия генератора первичных ключей IDENTITY, поскольку может быть несколько объектов подкласса, имеющих один и тот же идентификатор, и запрос базового класса приведет к получению объектов с одним и тем же идентификатором (даже если они принадлежат разным типам).
Если мы сохраняем в БД сущность, у которой есть поле-перечисление (Enum), то в таблице этой сущности создаётся колонка для значений этого перечисления и по умолчанию в ячейки сохраняется порядковый номер этого перечисления (ordinal).
public enum MyEnum {
ConstA,
ConstB,
ConstC;
}
@Entity
public class MyEntity {
@Id
private long myId;
private MyEnum myEnum;
public MyEntity() {
}
public MyEntity(long myId, MyEnum myEnum) {
this.myId = myId;
this.myEnum = myEnum;
}
...
}
В JPA типы Enum могут быть помечены аннотацией @Enumerated, которая может принимать в качестве атрибута EnumType.ORDINAL или EnumType.STRING, определяющий, отображается ли перечисление (enum) на столбец с типом Integer или String соответственно.
@Enumerated(EnumType.ORDINAL) - значение по умолчанию, говорит о том, что в базе будут храниться порядковые номера Enum (0, 1, 2…).
Проблема с этим типом отображения возникает, когда нам нужно изменить наш Enum.
Если мы добавим новое значение в середину или просто изменим порядок перечисления, мы сломаем существующую модель данных.
Такие проблемы могут быть трудно уловимыми, и нам придется обновлять все записи базы данных.
@Enumerated(EnumType.STRING) - означает, что в базе будут храниться имена Enum.
Мы можем безопасно добавлять новые значения перечисления или изменять порядок перечисления.
Однако переименование значения enum все равно нарушит работу базы данных.
Кроме того, даже несмотря на то, что это представление данных гораздо более читаемо по сравнению с параметром @Enumerated(EnumType.ORDINAL), оно потребляет намного больше места, чем необходимо.
Это может оказаться серьезной проблемой, когда нам нужно иметь дело с большим объемом данных.
Другой вариант - использование стандартных методов обратного вызова из JPA.
Мы можем смапить наши перечисления в БД и обратно в методах с аннотациями @PostLoad и @PrePersist.
Идея состоит в том, чтобы в сущности иметь не только поле с Enum, но и вспомогательное поле. Поле с Enum аннотируем @Transient, а в БД будет храниться значение из вспомогательного поля.
Создадим Enum с полем priority содержащем числовое значение приоритета:
public enum Priority {
LOW(100), MEDIUM(200), HIGH(300);
private int priority;
private Priority(int priority) {
this.priority = priority;
}
public int getPriority() {
return priority;
}
public static Priority of(int priority) {
return Stream.of(Priority.values())
.filter(p -> p.getPriority() == priority)
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
Мы добавили метод Priority.of(), чтобы упростить получение экземпляра Priority на основе его значения int.
Теперь, чтобы использовать его в нашем классе Article, нам нужно добавить два атрибута и реализовать методы обратного вызова:
@Entity
public class Article {
@Id
private int id;
private String title;
@Enumerated(EnumType.ORDINAL)
private Status status;
@Enumerated(EnumType.STRING)
private Type type;
@Basic
private int priorityValue;
@Transient
private Priority priority;
@PostLoad
void fillTransient() {
if (priorityValue > 0) {
this.priority = Priority.of(priorityValue);
}
}
@PrePersist
void fillPersistent() {
if (priority != null) {
this.priorityValue = priority.getPriority();
}
}
}
Несмотря на то, что этот вариант дает нам большую гибкость по сравнению с ранее описанными решениями, он не идеален. Просто кажется неправильным иметь в сущности целых два атрибута, представляющих одно перечисление.
Кроме того, если мы используем этот вариант, мы не сможем использовать значение Enum в запросах JPQL.
В JPA с версии 2.1 можно использовать Converter для конвертации Enum’а в некое его значение для сохранения в БД и получения из БД.
Все, что нам нужно сделать, это создать новый класс, который реализует javax.persistence.AttributeConverter и аннотировать его с помощью @Converter.
public enum Category
SPORT("S"), MUSIC("M"), TECHNOLOGY("T");
private String code;
private Category(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
@Entity
public class Article {
@Id
private int id;
private String title;
@Basic
private int priorityValue;
@Transient
private Priority priority;
private Category category;
}
@Converter(autoApply = true)
public class CategoryConverter implements AttributeConverter<Category, String> {
@Override
public String convertToDatabaseColumn(Category category) {
if (category == null) {
return null;
}
return category.getCode();
}
@Override
public Category convertToEntityAttribute(String code) {
if (code == null) {
return null;
}
return Stream.of(Category.values())
.filter(c -> c.getCode().equals(code))
.findFirst()
.orElseThrow(IllegalArgumentException::new); } }
}
}
Мы установили @Converter(autoApply=true), чтобы JPA автоматически применял логику преобразования ко всем сопоставленным атрибутам типа Category.
В противном случае нам пришлось бы поместить аннотацию @Converter непосредственно над полем Category у каждой сущности, где оно имеется.
В результате в столбце таблицы будут храниться значения: "S", "M" или "T".
Как мы видим, мы можем просто установить наши собственные правила преобразования перечислений в соответствующие значения базы данных, если мы используем интерфейс AttributeConverter.
Более того, мы можем безопасно добавлять новые значения enum или изменять существующие, не нарушая уже сохраненные данные.
Это решение просто в реализации и устраняет все недостатки с @Enumerated(EnumType.ORDINAL), @Enumerated(EnumType.STRING) и методами обратного вызова.
При работе с датами рекомендуется установить определенный часовой пояс для драйвера JDBC. Таким образом, наше приложение будет независимым от текущего часового пояса системы.
Другой способ - настроить свойство hibernate.jdbc.time_zone в файле свойств Hibernate, который используется для создания фабрики сессий. Таким образом, мы можем указать часовой пояс один раз для всего приложения.
Hibernate позволяет отображать различные классы даты/времени из Java в таблицах баз данных.
Стандарт SQL определяет три типа даты/времени:
DATE- Представляет календарную дату путем хранения лет, месяцев и дней.
Эквивалентом JDBC является java.sql.Date.
TIME - Представляет время дня и хранит часы, минуты и секунды.
Эквивалентом JDBC является java.sql.Time.
TIMESTAMP - Хранит как DATE, так и TIME плюс наносекунды.
Эквивалентом JDBC является java.sql.Timestamp.
Поскольку эти типы соответствуют SQL, их сопоставление относительно простое. Мы можем использовать аннотацию
public class TemporalValues {
private java.sql.Date
private java.sql.Time
private java.sql.Timestamp
}
Затем мы могли бы установить соответствующие значения следующим образом:
temporalValues.setSqlDate(java.sql.Date.valueOf("2017-11-15"));
temporalValues.setSqlTime(java.sql.Time.valueOf("15:30:14"));
temporalValues.setSqlTimestamp(java.sql.Timestamp.valueOf("2017-11-15 15:30:14.332"));
Обратите внимание, что использование типов JAVA.sql для полей сущностей не всегда может быть хорошим выбором.
Эти классы специфичны для JDBC и содержат множество устаревших функций.
Чтобы избежать зависимостей от пакета java.sql, начали использовать классы даты/времени из пакета java.util вместо классов JAVA.sql.Timestamp и java.sql.Time.
Точность представления времени составляет одна миллисекунда.
Для большинства практических задач этого более чем достаточно, но иногда хочется иметь точность повыше.
Поскольку классы в данном API изменяемые (не immutable), использовать их в многопоточной среде нужно с осторожностью.
В частности java.util.Date можно признать «эффективно» потоко-безопасным, если вы не вызываете у него устаревшие методы.
Тип java.util.Date содержит информацию о дате и времени с точностью до миллисекунд.
Но так как классы из этого пакета не имели прямого соответствия типам данных SQL, приходилось
использовать над полями java.util.Date аннотацию
private java.util.Date
private java.util.Date
private java.util.Date
Тип java.util.Date имеет точность до миллисекунд, и недостаточно точен для обработки SQL-значения Timestamp, который имеет точность вплоть до наносекунд.
Поэтому, когда мы извлекаем сущность из базы данных, неудивительно, что в этом поле мы находим экземпляр java.sql.Timestamp, даже если изначально мы сохранили java.util.Date.
Но это не страшно, так как Timestamp наследуется от Date.
Как и в случае java.util.Date, тип java.util.Calendar может быть сопоставлен с различными типами SQL, поэтому мы должны указать их с помощью @Temporal.
Разница лишь в том, что Hibernate не поддерживает отображение (маппинг) Calendar на TIME:
private java.util.Calendar
private java.util.Calendar
Начиная с Java 8, доступен новый API даты и времени для работы с временными значениями.
Этот API-интерфейс устраняет многие проблемы классов JAVA.util.Date и java.util.Calendar.
Все классы в новом API неизменяемые (immutable) и, как следствие, потоко-безопасные.
Точность представления времени составляет одна наносекунда, что в миллион раз точнее чем в пакете java.util.
Типы данных из пакета java.time напрямую отображаются (маппятся) на соответствующие типы SQL.
Поэтому нет необходимости явно указывать аннотацию @Temporal:
Это означает, что мы можем пометить эти поля только аннотацией @Basic (или @Column), например:
private java.time.LocalDate
private java.time.LocalTime
private java.time.OffsetTime
private java.time.Instant
private java.time.LocalDateTime
private java.time.OffsetDateTime
private java.time.ZonedDateTime
Каждый временной класс в пакете java.time имеет статический метод parse() для анализа предоставленного значения типа String с использованием соответствующего формата.
Вот как мы можем установить значения полей сущности:
temporalValues.setLocalDate(LocalDate.parse("2017-11-15"));
temporalValues.setLocalTime(LocalTime.parse("15:30:18"));
temporalValues.setOffsetTime(OffsetTime.parse("08:22:12+01:00"));
temporalValues.setInstant(Instant.parse("2017-11-15T08:22:12Z"));
temporalValues.setLocalDateTime(LocalDateTime.parse("2017-11-15T08:22:12"));
temporalValues.setOffsetDateTime(OffsetDateTime.parse("2017-11-15T08:22:12+01:00"));
temporalValues.setZonedDateTime( ZonedDateTime.parse("2017-11-15T08:22:12+01:00[Europe/Paris]"));
Если у нашей сущности есть поле с коллекцией, то мы привыкли ставить над ним аннотации @OneToMany либо @ManyToMany. Но данные аннотации применяются в случае, когда это коллекция других сущностей (entities).
Но что, если у нашей сущности коллекция не других сущностей, а базовых или встраиваемых (embeddable) типов, то есть коллекция элементов?
Для этих случаев в JPA имеется специальная аннотация
Конфигурация для таблицы коллекции элементов указывается с помощью аннотации @CollectionTable, которая используется для указания имени таблицы коллекции и JoinColumn, который ссылается на первичную таблицу.
public class
private int
private String
private List <String>
.............
}
Аннотация @ElementCollection похожа на отношение @OneToMany, за исключением того, что целью являются базовые и встраиваемые типы, а не сущности.
Можно использовать аннотации @AttributeOverrides и @AttributeOverride для настройки отображения в таблице полей базовых или встраиваемых типов.
Коллекции могут иметь тип java.util.Map, которые состоят из ключа и значения.
Для этого типа коллекций применяются следующие правила:
Ключ или значение Map может быть базовым типом языка программирования Java, встраиваемым классом или сущностью.
Если значение Map является встраиваемым классом или базовым типом, используйте аннотацию @ElementCollection.
Если значение Map является сущностью, используйте аннотацию @OneToMany или @ManyToMany.
Аннотация @MapKeyColumn позволяет настроить столбец «ключ» в таблице Map.
Аннотация @Column позволяет настроить столбец «значение» в таблице Map.
Использование коллекций элементов имеет один большой недостаток:
элементы коллекции не имеют идентификатора, и Hibernate не может обращаться индивидуально к каждому элементу коллекции. Когда мы добавляем новый объект в коллекцию или удаляем из коллекции существующий элемент, Hibernate удаляет все строки из таблицы элементов и вставляет новые строки по одной для каждого элемента в коллекции.
То есть при добавлении одного элемента в коллекцию Hibernate не добавит одну строку в таблицу коллекции, а очистит её и заполнит по новой всеми элементами.
Поэтому коллекции элементов следует использовать только для очень маленьких коллекций, чтобы Hibernate не выполнял слишком много операторов SQL.
Во всех других случаях рекомендуется использовать коллекции сущностей с @OneToMany
Существуют следующие четыре типа связей между сущностями:
OneToOne - когда один экземпляр Entity может быть связан не больше чем с одним экземпляром другого Entity.
OneToMany - когда один экземпляр Entity может быть связан с несколькими экземплярами других Entity.
ManyToOne - обратная связь для OneToMany. Несколько экземпляров Entity могут быть связаны с одним экземпляром другого Entity.
ManyToMany - экземпляры Entity могут быть связаны с несколькими экземплярами друг друга.
Направление отношений может быть как двунаправленным, так и однонаправленным.
Сторона-владелец отношения определяет, как среда выполнения Persistence обновляет отношение в базе данных.
Двунаправленные отношения имеют как сторону-владельца, так и владеемую сторону.
Однонаправленные отношения имеют только сторону-владельца.
В двунаправленном отношении каждая сущность имеет поле, которое ссылается на другую сущность. Через это поле код первой сущности может получить доступ ко второй сущности, находящейся на другой стороне отношений. Если у первой сущности есть поле, ссылающееся на вторую сущность, и наоборот, то в этом случае говорят, что обе сущности знают друг о друге, и что они состоят в двунаправленных отношениях.
Двунаправленные отношения должны следовать следующим правилам:
Владеемая сторона в двунаправленных отношениях должна ссылаться на владеющую сторону используя элемент mappedBy аннотаций @OneToOne, @OneToMany, или @ManyToMany.
Если применить атрибут mappedBy на одной стороне связи, то Hibernate не станет создавать сводную таблицу:
public class
//...
private Set<Items>
// getters and setters
}
public class
//...
private Cart
public Items() {}
// getters and setters
}
В данном примере таблица класса Items является владеющей стороной и будет иметь колонку с внешними ключами на таблицу Cart. Таблица класса Cart будет владеемой.
Для двунаправленных отношений one-to-one, сторона-владелец это та сторона, чья таблица имеет столбец с внешним ключом на другую таблицу.
Если не указан параметр mappedBy, то колонки с айдишниками появляются у каждой таблицы.
В однонаправленных отношениях только одна сущность имеет поле, которое ссылается на вторую сущность.
Вторая сущность (сторона) не имеет поля первой сущности и не знает об отношениях.
Язык запросов JAVA Persistence и запросы API Criteria часто перемещаются между отношениями.
Направление отношений определяет, может ли запрос перемещаться от одной сущности к другой.
Например:
В отношениях между двумя сущностями всегда есть одна владеющая сторона, а владеемой может и не быть, если это однонаправленные отношения.
По сути, у кого есть внешний ключ на другую сущность - тот и владелец связи. То есть, если в таблице одной сущности есть колонка, содержащая внешние ключи от другой сущности, то первая сущность признаётся владельцем связи, вторая сущность - владеемой.
В однонаправленных отношениях сторона, которая имеет поле с типом другой сущности, является владельцем этой связи по умолчанию, например:
@Entity public classLineItem {@Id private Long id@OneToOne private Product product; }@Entity public classProduct {@Id private Long id; private String name; private Double price; }
Это сущность (таблица), на которую ссылается внешний ключ из дочерней сущности (таблицы).
Это сущность (таблица), в которой есть колонка с внешним ключом, ссылающимся на родительскую сущность (таблицу).
Сущности, между которыми есть отношения, часто зависят от существования друг друга.
Например, позиции (LineItem) являются частью заказа (CustomerOrder), и если заказ удален, все позиции также должны быть удалены.
Это называется - каскадным удалением.
JPA позволяет распространять операции с сущностями (например, persist или remove) на связанные сущности. Это означает, что при включенном каскадировании, если сущность A сохраняется или удаляется, тогда сущность B (связанная с A отношением, например через ManyToOne) также будет сохраняться или удаляться без явных команд сохранения или удаления.
Каскадирования можно добиться, указав у любой из аннотаций @OneToOne, @ManyToOne, @OneToMany, @ManyToMany элемент cascade и присвоив ему одно или несколько значений из перечисления CascadeType (ALL, DETACH, MERGE, PERSIST, REFRESH, REMOVE).
Как правило каскадные операции применяются от родительской сущности к дочерним, но они могут распространяться и в обратном направлении - от дочерней к родительской.
Конечно это не всегда нужно, а зачастую совсем не нужно, но главное условие для этого - чтобы между ними было двунаправленное отношение, иначе каскадные операции выполняются только в одном направлении.
Представим, что у нас есть класс Customer, у которого есть коллекция Order:
@Entity public classCustomer { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List<Order> orders = new ArrayList<>(); // other mappings, getters and setters }
Пусть у нас есть один объект Customer - родитель, в коллекции которого есть 4 объекта Order - дети, и мы установили атрибут orphanRemoval = true над этой коллекцией. В нашей базе данных в таблице Customer будет одна строка, а в таблице Order будет четыре строки. Также в таблице Order будет колонка с внешними ключами на таблицу Customer. В каждой из четырех ячеек этой колонки будут ссылки на один и тот же первичный ключ объекта Customer.
Например, мы удалим из коллекции orders один объект Order - любой из четырех, в результате чего у объекта Customer останется три объекта Order:
Customer customer = entityManager.find(Customer.class, 1L); Order order = customer.getOrders().get(0); customer.getOrders().remove(order); flushAndClear();
После запуска метода flushAndClear() - обновления объекта Customer отправятся в базу данных, и произойдет следующее:
Если не будет атрибута orphanRemoval = true, то пункт 4 не выполнится, и в таблице Order останется сущность Order, не связанная ни с одной сущностью Customer, то есть её ячейка с внешним ключом будет пустой.
Такая сущность будет считаться осиротевшей.
В JPA описаны два типа fetch-стратегии:
LAZY — данные поля сущности будут загружены только во время первого обращения к этому полю.
EAGER — данные поля будут загружены немедленно вместе с сущностью.
В Hibernate:
FetchType.EAGER
Hibernate должен сразу загрузить соответствующее аннотированное поле или свойство.
Это поведение по умолчанию для полей, аннотированных @Basic, @ManyToOne и @OneToOne (все что быстро).
FetchType.LAZY
Hibernate может загружать данные не сразу, а при первом обращении к ним, но так как это необязательное
требование, то Hibernate имеет право изменить это поведение и загружать их сразу.
Это поведение по умолчанию для полей, аннотированных @OneToMany, @ManyToMany
и @ElementCollection (все что медленно).
Раньше у Hibernate все поля были LAZY, но в последних версиях - всё как в JPA 2.0.
Согласно JPA объект сущности может иметь один из четырех статусов жизненного цикла:
new - объект создан, не имеет primary key, не является частью контекста персистентности (не управляется JPA);
managed - объект создан, имеет primary key, является частью контекста персистентности (управляется JPA);
detached - объект создан, имеет primary key, не является (или больше не является) частью контекста персистентности (не управляется JPA);
removed - объект создан, является частью контекста персистентности (управляется JPA), будет удален при commit-е транзакции.
new → managed
объект будет сохранен в базу при commit-е транзакции или в результате flush-операции.
managed → {ignored}
операция игнорируется, однако связанные entity могут поменять статус на managed,
если у них есть аннотации каскадных изменений.
removed → managed
detached → {exception}
исключение сразу или на этапе commit-а транзакции
(так как у detached уже есть первичный ключ).
new → {ignored}
операция игнорируется, однако связанные entity могут поменять статус на removed, если у них есть
аннотации каскадных изменений и они имели статус managed
managed → removed
запись в базе данных будет удалена при commit-е транзакции
(также произойдут операции remove для всех каскадно зависимых объектов)
removed → {ignored}
операция игнорируется
detached → {exception}
исключение сразу или на этапе commit-а транзакции
new → ...
будет создана новая managed entity, в которую будут скопированы данные объекта.
managed → {ignored}
операция игнорируется, однако операция merge сработает на каскадно зависимых entity,
если их статус не managed.
removed → {exception}
исключение сразу или на этапе commit-а транзакции.
detached → ...
либо данные будут скопированы в существующую БД managed entity с тем же первичным ключом,
либо создана новая managed entity, в которую скопируются данные.
managed → ...
будут восстановлены все изменения из базы данных данного entity,
также произойдет refresh всех каскадно зависимых объектов
new, removed, detached → {exception}
будет вызвано исключение.