diff --git a/img/Hibernate2.png b/img/Hibernate2.png new file mode 100644 index 0000000..5adf426 Binary files /dev/null and b/img/Hibernate2.png differ diff --git a/img/Hibernate3.png b/img/Hibernate3.png new file mode 100644 index 0000000..5720b98 Binary files /dev/null and b/img/Hibernate3.png differ diff --git a/img/Hibernate4.png b/img/Hibernate4.png new file mode 100644 index 0000000..4864deb Binary files /dev/null and b/img/Hibernate4.png differ diff --git a/img/Hibernate5.png b/img/Hibernate5.png new file mode 100644 index 0000000..0f85859 Binary files /dev/null and b/img/Hibernate5.png differ diff --git a/img/Hibernate6.png b/img/Hibernate6.png new file mode 100644 index 0000000..43c9993 Binary files /dev/null and b/img/Hibernate6.png differ diff --git a/img/Hibernate7.png b/img/Hibernate7.png new file mode 100644 index 0000000..1295617 Binary files /dev/null and b/img/Hibernate7.png differ diff --git a/img/Hibernate8.png b/img/Hibernate8.png new file mode 100644 index 0000000..ca07f64 Binary files /dev/null and b/img/Hibernate8.png differ diff --git a/jdbc.md b/jdbc.md index 51968eb..b2fa5ce 100644 --- a/jdbc.md +++ b/jdbc.md @@ -8,6 +8,7 @@ + [Что такое JPA?](#Что-такое-jpa) + [Различия между JPA JDBC и Hibernate?](#Различия-между-jpa-jdbc-и-hibernate) + [Что такое JPQL и HQL?](#Что-такое-jpql-и-hql) ++ [Criteria API](#-Criteria-API) + [В чем заключаются преимущества использования JDBC?](#В-чем-заключаются-преимущества-использования-jdbc) + [В чем заключаются преимущества использования Hibernate?](#В-чем-заключаются-преимущества-использования-hibernate) + [Что из себя представляет JDBC URL?](#Что-из-себя-представляет-jdbc-url) @@ -30,6 +31,8 @@ + [Что такое встраиваемый (Embeddable) класс?](#Что-такое-встраиваемый-(embeddable)-класс) + [Может ли встраиваемый (Embeddable) класс содержать другой встраиваемый (Embeddable) класс?](#Может-ли-встраиваемый-(embeddable)-класс-содержать-другой-встраиваемый-(embeddable)-класс) + [Какие требования JPA устанавливает к встраиваемым (Embeddable) классам?](#Какие-требования-jpa-устанавливает-к-встраиваемым-(embeddable)-классам) ++ [Что такое Mapped Superclass?](#Что-такое-mapped-superclass) ++ [Mapped Superclass vs. Embeddable class](#Mapped-Superclass-vs.-Embeddable-class) + [Основные классы и интерфейсы JPA?](#Основные-классы-и-интерфейсы-jpa) + [Что такое Метамодель?](#Что-такое-метамодель) + [Основные классы и интерфейсы Hibernate?](#Основные-классы-и-интерфейсы-hibernate) @@ -40,12 +43,16 @@ + [Аннотации JPA?](#Аннотации-jpa) + [Аннотации Hibernate?](#Аннотации-hibernate) + [Кеширование в Hibernate?](#Кеширование-в-hibernate) ++ [Для чего нужна аннотация @Cacheable?](#Для-чего-нужна-аннотация-@Cacheable) + [Стратегии кеширования?](#Стратегии-кеширования) -+ [Что такое Mapped Superclass?](#Что-такое-mapped-superclass) + [Какие три типа стратегии наследования мапинга (Inheritance Mapping Strategies) описаны в JPA?](#Какие-три-типа-стратегии-наследования-мапинга-(inheritance-mapping-strategies)-описаны-в-jpa) + [Стратегии загрузки объектов в Hibernate?](#Стратегии-загрузки-объектов-в-hibernat) + [Для чего нужна аннотация Basic?](#Для-чего-нужна-аннотация-basic) ++ [Для чего нужна аннотация Column?](#Для-чего-нужна-аннотация-Column) + [Для чего нужна аннотация Access?](#Для-чего-нужна-аннотация-access) ++ [Для чего нужны аннотации @Embedded и @Embeddable?](#Для-чего-нужны-аннотации-@Embedded-и-@Embeddable) ++ [Для чего нужны аннотации @JoinColumn, @JoinColumns и @JoinTable?](#Для-чего-нужны-аннотации-@JoinColumn-@JoinColumns-и-@JoinTable) ++ [Для чего нужны аннотации @OrderBy и @OrderColumn](#Для-чего-нужны-аннотации-@OrderBy-и-@OrderColumn) + [Для чего нужны callback методы в JPA? К каким сущностям применяются аннотации callback методов? Перечислите семь callback методов (или, что тоже самое, аннотаций callback методов)](#Для-чего-нужны-callback-методы-в-jpa) + [Какие видов блокировок (lock) описаны в спецификации JPA?](#Какие-видов-блокировок-(lock)-описаны-в-спецификации-jpa) + [Как можно изменить настройки fetch?](#Как-можно-изменить-настройки-fetch) @@ -54,7 +61,8 @@ + [Как определить владельца связи?](#Как-определить-владельца-связи) + [Что происходит с таблицами при изпользовании mappedBy?](#Что-происходит-с-таблицами-при-изпользовании-mappedby) + [FetchType стратегии по-умолчанию?](#fetchtype-стратегии-по-умолчанию) -+ [Data и Enum значения в БД?](#data-и-enum-значения-в-БД) ++ [Enum значения в БД?](#enum-значения-в-БД) ++ [Как мапятся даты (до Java 8 и после)?](#Как-мапятся-даты-(до-Java-8-и-после)) + [PersistenceContext](#persistencecontext) + [Entity graph](#Entity-graph) + [Пробелма n+1 select и как ее решить?](#Пробелма-n+1-select-и-как-ее-решить) @@ -64,7 +72,7 @@ + [Как создать составной ключ](#Как-создать-составной-ключ) + [GeneratedValue](#generatedvalue) + [OrphanRemoval vs cascadeType.remove](#Orphanremoval-vs-cascadeType.remove) -+ [ElementCollection](#Elementcollection) ++ [ElementCollection - Как сохранять в БД коллекции базовых типов](#Elementcollection-Как-сохранять-в-БД-коллекции-базовых-типов) ## Что такое _ORM_? @@ -95,8 +103,139 @@ Hibernate одна из самых популярных открытых реа ## Что такое JPQL и HQL? + JPQL — Java Persistence Query Language. Фактически это как SQL, только запросы делаются не к таблицам, а к классам. JPQL основан на HQL. + +В отличии от SQL в запросах JPQL есть автоматический __полиморфизм__, то есть каждый запрос к Entity возвращает не только объекты этого Entity, но также объекты всех его классов-потомков, независимо от стратегии наследования (например, запрос select * from Animal, вернет не только объекты Animal, но и объекты классов Cat и Dog, которые унаследованы от Animal). Чтобы исключить такое поведение используется функция TYPE в where условии (например select * from Animal a where TYPE(a) IN(Animal, Cat) уже не вернет объекты класса Dog). + +Запросы, как обычные, так и именованные, формируются из EntityManager: +```java +Query query = entityManager.createQuery( +"select p " + +"from Person p " + +"where p.name like :name" +); +TypedQuery typedQuery = entityManager.createQuery( +"select p " + +"from Person p " + +"where p.name like :name", Person.class +); +@NamedQuery( +name = "get_person_by_name", +query = "select p from Person p where name = :name" +) +Query query = entityManager.createNamedQuery( "get_person_by_name" ); +TypedQuery typedQuery = entityManager.createNamedQuery( +"get_person_by_name", Person.class +); +``` + + HQL — Hibernate Query Language. Аналог SQL, но работает с сохраняемыми объектами (Persistent Objects) и их полями (аттрибутами класса). Мы также имеем возможность испольщовать обычные SQL – запросы в Hibernate используя Native SQL +В Hibernate HQL-запрос представлен org.hibernate.query.Query, полученный из Session. Если HQL является именованным запросом, то будет использоваться Session#getNamedQuery, в противном случае требуется Session#createQuery. HQL пердоставляет дополнительные возможности по сравнению с JPQL. +```java +org.hibernate.query.Query query = session.createQuery( + "select p " + + "from Person p " + + "where p.name like :name" +); +org.hibernate.query.Query query = session.getNamedQuery( "get_person_by_name" ); +``` + +## Criteria API +__Начиная с версии 5.0 собственный Hibernate Criteria API признан устаревшим и не развивается. Вместо него рекомендуется использовать JPA Criteria API. + +Начиная с версии 5.2 Hibernate Criteria API объявлен deprecated и не рекомендуется к использованию.__ + +__Hibernate Criteria API__ + +Это тоже язык запросов, аналогичный JPQL (Java Persistence query language), однако запросы основаны на методах и объектах. Hibernate Criteria API является более объектно-ориентированным для запросов, которые получают результат из базы данных. Для операций update, delete или других DDL манипуляций использовать Criteria API нельзя. Критерии используются только для выборки из базы данных в более объектно-ориентированном стиле. Используется для динамических запросов. Запросы выглядят так: +```java +session.createCriteria(Person.class) +.setMaxResults(10) +.list() +.forEach(System.out::println); +``` + +Запрос выше полностью аналогичен запросу HQL "from Person". С Criteria также работают и все те вещи, которые работают и с Query: пейджинг, таймауты и т.д. + +Разумеется, в Criteria запросах можно и нужно накладывать условия, по которым объекты будут отбираться: +```java +session.createCriteria(Person.class) +.add(Restrictions.eq("lastName", "Testoff")) +.list() +.forEach(System.out::println); +``` + +__JPA Criteria API__ + +Criteria API - это актуальный API, используемый для определения запросов для сущностей. Это альтернативный способ определения JPQL-запроса. Эти запросы типобезопасны, переносимы и легко меняются путем изменения синтаксиса. + +Основные преимущества JPA Criteria API: +- ошибки могут быть обнаружены во время компиляции; +- позволяет динамически формировать запросы на этапе выполнения приложения. + +Запросы на основе строк JPQL и запросы на основе критериев JPA одинаковы по производительности и эффективности. + +Для простых статических запросов предпочтительнее использовать строковые запросы JPQL (например, в виде именованных запросов). Для динамических запросов, которые создаются во время выполнения - JPA Criteria API может быть предпочтительней. Например, построение динамического запроса на основе полей, которые пользователь заполняет в рантайме в форме, которая содержит много необязательных полей. Ожидается, что построение этого запроса будет более ясным и понятным при использовании JPA Criteria API, поскольку устраняет необходимость в создании запроса с использованием многих операций конкатенации строк. + +Пример использования JPA Criteria API: +```java +EntityManager em = entityManagerFactory.createEntityManager(); +em.getTransaction().begin(); +CriteriaBuilder cb = em.getCriteriaBuilder(); +CriteriaQuery personCriteria = cb.createQuery(Person.class); +Root personRoot = personCriteria.from(Person.class); +personCriteria.select(personRoot); +em.createQuery(personCriteria) +.getResultList() +.forEach(System.out::println); +``` + +Алгоритм: +- создаём EntityManager, открываем транзакцию и создаём CriteriaBuilder, который будет строить объекты запросов. С помощью CriteriaBuilder создаём CriteriaQuery, который параметризуется типом, который этот запрос возвращает. +- Затем создаём корневой объект, от которого производится обход дерева свойств при накладывании ограничений или указании, что выбирать. +- Последним шагом говорится, что же мы хотим выбрать и, наконец, запрос отправляется в EntityManager, где и выполняется как обычно. + +Построенный выше весьма многословный пример эквивалентен JPQL запросу «from Person». + +Все шаги, перечисленные выше, являются обязательными для создания запроса с помощью Criteria API. Важно понимать, что корневой объект указывает JPQL, откуда будут браться данные, а CriteriaQuery указывает тип возвращаемых данных. И типы Root и CriteriaQuery могут отличаться: +```java +CriteriaQuery passportCriteria = cb.createQuery(Passport.class); +Root personPassportRoot = passportCriteria.from(Person.class); +passportCriteria.select(personPassportRoot.get("passport")); +em.createQuery(passportCriteria) +.getResultList() +.forEach(System.out::println); +``` + +Этот запрос аналогичен JPQL запросу «select passport from Person» и показывает, что класс, из которого запрашиваются данные и класс, который вернёт запрос, могут быть разными. + +__Metamodel и типобезопасность__ + +Все примеры выше решают проблему с программным созданием запросов, но всё ещё бессильны перед изменениями сущностей. В самом деле, изменив в сущности Person поле workingPlaces на jobs - развалится этот запрос: +```java +CriteriaQuery personWorkCriteria = cb.createQuery(Person.class); +Root personWorkRoot = personWorkCriteria.from(Person.class); +Join company = personWorkRoot.join("workingPlaces"); +personWorkCriteria.select(personWorkRoot); +personWorkCriteria.where(cb.equal(company.get("name"), "Acme Ltd")); +em.createQuery(personWorkCriteria) +.getResultList() +.forEach(System.out::println); +``` + +И мы не узнаем, что он развалится, пока не попробуем его исполнить. Metamodel решает эту проблему, создавая специальные описательные классы, которые используются в Criteria API вместо имён полей. Сам Metamodel класс выглядит примерно вот так: +```java +@StaticMetamodel(Company.class) +public abstract class Company_ extends AbstractIdentifiableObject_ { +public static volatile SingularAttribute name; +public static volatile CollectionAttribute workers; +} +``` + +В Metamodel классе описываются, какие поля присутствуют в сущности, какого они типа, коллекция это или нет и т.д. Для каждой сущности создаётся свой класс Metаmodel. + +Создаются классы Metamodel разумеется не вручную. То есть можно их и вручную создать, но тогда пропадает автоматичность проверки и теряется смысл всей этой затеи. Обычно же классы Metamodel генерируются на этапе компиляции тем или иным методом. Конкретная реализация генерации зависит от конкретной реализации JPA и может меняться + ## В чем заключаются преимущества использования JDBC? Преимуществами JDBC считают: @@ -390,7 +529,7 @@ Entity это легковесный хранимый объект бизнес При этом у одной и той же табличке в БД может быть несколько Entity. -JPA указывает что она может работать как с свойствами классов (property), оформленные в стиле JavaBeans (с геттерами и сеттерами), либо с полями (field), то есть переменными класса (instance variables). Соответственно, при этом тип доступа будет либо property access или field access. Оба типа элементов Entity класса называются _атрибутами Entity класса_. +Через аннотации можно указать, как работать со свойствами классов (property). Через методы, когда аннотации стоят над методами (геттерами и сеттерами), либо когда аннотации стоят над полями (через Reflection), то есть переменными класса (instance variables). Соответственно, при этом тип доступа будет либо property access или field access. Оба типа элементов Entity класса называются _атрибутами Entity класса_. Допустимые типы атрибутов у Entity классов: + примитивные типы и их обертки Java, @@ -401,7 +540,7 @@ JPA указывает что она может работать как с св + embeddable классы + и коллекции типов 1-6 -Требования JPA к Entity классам: +__Требования JPA к Entity классам:__ 1) Entity класс должен быть отмечен аннотацией Entity или описан в XML файле конфигурации JPA, 2) Entity класс должен содержать public или protected конструктор без аргументов (он также может иметь конструкторы с аргументами), 3) Entity класс должен быть классом верхнего уровня (top-level class), @@ -412,10 +551,51 @@ JPA указывает что она может работать как с св 8) Поля Entity класс должны быть напрямую доступны только методам самого Entity класса и не должны быть напрямую доступны другим классам, использующим этот Entity. Такие классы должны обращаться только к методам (getter/setter методам или другим методам бизнес-логики в Entity классе), 9) Enity класс должен содержать первичный ключ, то есть атрибут или группу атрибутов которые уникально определяют запись этого Enity класса в базе данных, + +__Требования Hibernate к Entity классам:__ +1) Класс сущности должен иметь конструктор без аргументов, который может быть не только public или protected, но и package visibility (default). +2) Класс сущности не обязательно должен быть классом верхнего уровня. +3) Технически Hibernate может сохранять финальные классы или классы с финальными методами (getter / setter). Однако, как правило, это не очень хорошая идея, так как это лишит Hibernate возможности генерировать прокси для отложенной загрузки сущности. +4) Hibernate не запрещает разработчику приложения открывать прямой доступ к переменным экземпляра и ссылаться на них извне класса сущности. Однако обоснованность такого подхода спорна. + +[к оглавлению](#jdbc) + +## Может ли абстрактный класс быть Entity? +Абстрактный класс может быть Entity классом. Абстрактный Entity класс +отличается от обычных Entity классов только тем, что нельзя создать объект этого +класса. Имена абстрактных классов могут использоваться в запросах. + +Абстрактные Entity классы используются в наследовании, когда их потомки +наследуют поля абстрактного класса: + +```java +@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; + ............. +} +``` + [к оглавлению](#jdbc) ## Как наследуется Entity? -+ Может наследоваться и от других Entity классов, и от не Entity классов. ++ Может наследоваться и от других Entity классов, и от не Entity классов. Состояние (поля) не Entity суперкласса не является персистентным, то есть не хранится в БД и не обрабатывается провайдером (Hibernate), поэтому любое такое состояние (поля), унаследованное Entity классом, также не будет отображаться в БД. Не Entity суперклассы не могут участвовать в операциях EntityManager или Query. Любые маппинги или аннотации отношений в не Entity суперклассах игнорируются. + Не Entity классы так же могут наследоваться от Entity. + Может быть абстрактным, при этом он сохраняет все свойства Entity, за исключением того что его нельзя непосредственно инициализировать. @@ -438,7 +618,76 @@ Plain Old Java Object - простой Java-объект, не унаследо [к оглавлению](#jdbc) ## Что такое встраиваемый (Embeddable) класс? -Встраиваемый (Embeddable) класс это класс который не используется сам по себе, только как часть одного или нескольких Entity классов. Entity класс могут содержать как одиночные встраиваемые классы, так и коллекции таких классов. Также такие классы могут быть использованы как ключи или значения map. Во время выполнения каждый встраиваемый класс принадлежит только одному объекту Entity класса и не может быть использован для передачи данных между объектами Entity классов (то есть такой класс не является общей структурой данных для разных объектов). В целом, такой класс служит для того чтобы выносить определение общих атрибутов для нескольких Entity, можно считать что JPA просто встраивает в Entity вместо объекта такого класса те атрибуты, которые он содержит. +Встраиваемый (Embeddable) класс это класс который не используется сам по себе, только как часть одного или нескольких Entity классов. + +Например, у нас может быть встраиваемый класс ClassA, который представляет собой композицию строкового и числового значений, и эти два поля будут добавлены в класс EntityA: +```java +@Entity +public class EntityA { + @Id + @GeneratedValue + private int id; + @Embedded + private ClassA classARef; + ............. +} +@Embeddable +public class ClassA { + private String myStr; + private int myInt; + ............. +} +``` +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate2.png) + + +Entity класс могут содержать как одиночные встраиваемые классы, так и коллекции таких классов. Также такие классы могут быть использованы как ключи или значения map. Во время выполнения каждый встраиваемый класс принадлежит только одному объекту Entity класса и не может быть использован для передачи данных между объектами Entity классов (то есть такой класс не является общей структурой данных для разных объектов). В целом, такой класс служит для того чтобы выносить определение общих атрибутов для нескольких Entity, можно считать что JPA просто встраивает в Entity вместо объекта такого класса те атрибуты, которые он содержит. +То есть, если класс Person с полями name и age встроен и в класс Driver, и в класс Baker, то у обоих последних классов появятся оба поля из класса Person. Но если у объекта Driver эти поля будут иметь значения “Иван” и “35”, то эти же поля у объекта Baker могут иметь совершенно иные значения, никак не связанные с объектом Driver. + +__Особенности встраиваемых классов__ +1) все поля встраиваемого класса, даже коллекции, станут полями класса, в +который происходит встраивание; +2) встраиваемые классы могут быть встроены в одну и ту же сущность несколько +раз, нужно только поменять имена полей; +3) экземпляры встраиваемых классов, в отличие от экземпляров сущностей, не +имеют собственного персистентного состояния, вместо этого они существуют +только как часть состояния объекта, которому они принадлежат; +4) встраиваемые классы могут использовать в качестве полей: +➢ базовые типы; +➢ коллекции базовых типов (с аннотацией @ElementCollection); +➢ другие встраиваемые классы; +➢ коллекции других встраиваемых классов (с аннотацией +@ElementCollection); +➢ сущности; +➢ коллекции сущностей; +5) сущность может использовать в качестве полей одиночные встраиваемые +классы и коллекции встраиваемых классов; +6) встраиваемые классы могут использоваться в качестве ключей и значений Map. + +Так как мы можем встраивать классы в неограниченное количество других классов, то у каждого класса, содержащего встраиваемый класс, мы можем изменить названия полей из встраиваемого класса. Например, у класса Driver поля из встраиваемого класса Person будут изменены с name на driver_name и с age на driver_age + +```java +@Embeddable +public class Person { + private String 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, но не обязаны это делать. + +Можно использовать для денормализации БД (ускорений запросов к БД) [к оглавлению](#jdbc) @@ -456,7 +705,61 @@ Plain Old Java Object - простой Java-объект, не унаследо 1. Такие классы должны удовлетворять тем же правилам что Entity классы, за исключением того что они не обязаны содержать первичный ключ и быть отмечены аннотацией Entity (см. вопрос 10), 2. Embeddable класс должен быть отмечен аннотацией Embeddable или описан в XML файле конфигурации JPA. +## Что такое Mapped Superclass? +Mapped Superclass это класс от которого наследуются Entity, он может содержать аннотации JPA, однако сам такой класс не является Entity, ему не обязательно выполнять все требования установленные для Entity (например, он может не содержать первичного ключа). Такой класс не может использоваться в операциях EntityManager или Query. Такой класс должен быть отмечен аннотацией MappedSuperclass или соответственно описан в xml файле. + +__Особенности__ +‒ Должен быть помечен аннотацией @MappedSuperclass или описан в xml файле. +‒ Не может использоваться в операциях EntityManager или Query, вместо этого нужно использовать классы-наследники. +‒ Не может состоять в отношениях с другими сущностями (в сущности нельзя создать поле с типом сопоставленного суперкласса). +‒ Может быть абстрактным. +‒ Не имеет своей таблицы в БД. + +Для того, чтобы использовать Mapped Superclass, достаточно унаследовать его в классах-потомках: +```java +@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; + ............. +} +``` +В указанном примере кода в БД будут таблицы FULLTIMEEMPLOYEE и PARTTIMEEMPLOYEE, но таблицы EMPLOYEE не будет: + +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate3.png) + +Это похоже на стратегию наследования “Таблица для каждого конкретного класса сущностей”, но в модели данных нет объединения таблиц или наследования. Также тут нет таблицы для Mapped Superclass. Наследование существует только в объектной модели. + +Основным недостатком использования сопоставленного суперкласса является то, что полиморфные запросы невозможны, то есть мы не можем загрузить всех наследников Mapped Superclass. + + +## Mapped Superclass vs. Embeddable class +__Сходства:__ +1) не являются сущностями и могут иметь все аннотации, кроме @Entity; +2) не имеют своих таблиц в БД; +3) не могут использоваться в операциях EntityManager или Query. + +__Различия:__ +1) MappedSuperclass - наследование, Embeddable class - композиция (экземпляр «части» может входить только в одно целое (или никуда не входить)); +2) поля из Mapped Superclass могут быть у сущности в одном экземпляре, полей из Embeddable class может быть сколько угодно (встроив в сущность Embeddable class несколько раз и поменяв имена полей); +3) в сущности нельзя создать поле с типом сопоставленного суперкласса, а с Embeddable можно и нужно. + [к оглавлению](#jdbc) + ## Основные классы и интерфейсы JPA __EntityManagerFactory__ – фабричный класс EntityManager. Он создает и управляет несколькими экземплярами EntityManager. Создание EntityManagerFactory довольно дорогая операция, поэтому обычно её создают один раз и на всё приложение. @@ -480,15 +783,19 @@ __Методы операций над Entity:__ + refresh (обновление данных) + detach (удаление из управление JPA) + lock (блокирование Enity от изменений в других thread). + __Методы получение данных:__ + find (поиск и получение Entity) + createQuery, createNamedQuery, createNativeQuery + contains + createNamedStoredProcedureQuery, createStoredProcedureQuery + __Получение других сущностей JPA:__ + getTransaction, getEntityManagerFactory, getCriteriaBuilder, getMetamodel, getDelegate + __Работа с EntityGraph:__ + createEntityGraph, getEntityGraph + __Общие операции над EntityManager или всеми Entities:__ + close, isOpen, getProperties, setProperty, clear. @@ -606,30 +913,30 @@ NEW ———— persist(entity) —————> MANAGED <————— cr ## Влияние операций EntityManager на Entity объекты различный жизненных циклов? __Persist__
-1) Если статус Entity new, то он меняется на managed и объект будет сохранен в базу при commit'е транзакции или в результате flush операций, -2) Если статус уже managed, операция игнорируется, однако зависимые Entity могут поменять статус на managed, если у них есть аннотации каскадных изменений, -3) Если статус removed, то он меняется на managed, -4) Если статус detached, будет выкинут exception сразу или на этапе commit'а транзакции, +1) New -> меняется на managed и объект будет сохранен в базу при commit'е транзакции или в результате flush операций, +2) Managed -> операция игнорируется, однако зависимые Entity могут поменять статус на managed, если у них есть аннотации каскадных изменений, +3) Removed -> то он меняется на managed, +4) Detached -> будет выкинут exception сразу или на этапе commit'а транзакции, __Remove__
-1) Если статус Entity new, операция игнорируется, однако зависимые Entity могут поменять статус на removed, если у них есть аннотации каскадных изменений и они имели статус managed, -2) Если статус managed, то статус меняется на removed и запись объект в базе данных будет удалена при commit'е транзакции (так же произойдут операции remove для всех каскадно зависимых объектов), -3) Если статус removed, то операция игнорируется, -4) Если статус detached, будет выкинут exception сразу или на этапе commit'а транзакции, +1) New -> операция игнорируется, однако зависимые Entity могут поменять статус на removed, если у них есть аннотации каскадных изменений и они имели статус managed, +2) Managed -> статус меняется на removed и запись объект в базе данных будет удалена при commit'е транзакции (так же произойдут операции remove для всех каскадно зависимых объектов), +3) Removed -> операция игнорируется, +4) Detached -> будет выкинут exception сразу или на этапе commit'а транзакции, __Merge__
-1) Если статус detached, то либо данные будет скопированы в существующей managed entity с тем же первичным ключом, либо создан новый managed в который скопируются данные, -1) Если статус Entity new, то будет создана новый managed entity, в который будут скопированы данные прошлого объекта, -2) Если статус managed, операция игнорируется, однако операция merge сработает на каскадно зависимые Entity, если их статус не managed, -3) Если статус removed, будет выкинут exception сразу или на этапе commit'а транзакции, +1) Detached -> то либо данные будет скопированы в существующей managed entity с тем же первичным ключом, либо создан новый managed в который скопируются данные, +1) New -> будет создана новый managed entity, в который будут скопированы данные прошлого объекта, +2) Managed -> операция игнорируется, однако операция merge сработает на каскадно зависимые Entity, если их статус не managed, +3) Removed -> будет выкинут exception сразу или на этапе commit'а транзакции, __Refresh__
-1) Если статус Entity managed, то в результате операции будут востановлены все изменения из базы данных данного Entity, так же произойдет refresh всех каскадно зависимых объектов, +1) Managed -> в результате операции будут востановлены все изменения из базы данных данного Entity, так же произойдет refresh всех каскадно зависимых объектов, 2) Если статус new, removed или detached, будет выкинут exception, __Detach__
-1) Если статус Entity managed или removed, то в результате операции статус Entity (и всех каскадно-зависимых объектов) станет detached. -2) Если статус new или detached, то операция игнорируется, +1) Managed или Removed -> в результате операции статус Entity (и всех каскадно-зависимых объектов) станет detached. +2) New или Detached -> операция игнорируется, ## Аннотации JPA @Access — аннотация используется для указания типа доступа связанного класса сущности, сопоставленного супер класса или встраиваемого класса или атрибута сущности. @@ -1028,15 +1335,105 @@ evict() используется для удаления конкретного __Кеш 2-го уровня__ — кеш второго уровня привязан к объекту-фабрике сессий (Session Factory object). Что как бы подразумевает, что видимость этого кеша гораздо шире кеша первого уровня. Чтение из кеша второго уровня происходит только в том случае, если нужный объект не был найден в кеше первого уровня. По умолчанию кеш второго уровня отключен. Для включения необходимо добавить следующие строки в Вашем конфигурационном файле JPA (persistence.xml): -`` +Будут ли в нашем приложении кэшироваться сущности и связанные с ними состояния, определяется значением элемента shared-cache-mode файла persistence.xml (или в свойстве javax.persistence.sharedCache.mode конфигурационного файла). Если в файле для элемента shared-cache-mode установлено значение: + +- ENABLE_SELECTIVE (дефолтное и рекомендуемое значение): только сущности с аннотацией @Cacheable (равносильно значению по умолчанию @Cacheable(value=true)) будут сохраняться в кэше второго уровня. +- DISABLE_SELECTIVE: все сущности будут сохраняться в кэше второго уровня, за исключением сущностей, помеченных аннотацией @Cacheable(value=false)как некэшируемые. +- ALL: сущности всегда кэшируются, даже если они помечены как некэшируемые. +- NONE: ни одна сущность не кэшируется, даже если помечена как кэшируемая. При данной опции имеет смысл вообще отключить кэш второго уровня. +- UNSPECIFIED: применяются значения по умолчанию для кэша второго уровня, определенные Hibernate. Это эквивалентно тому, что вообще не используется shared-cache-mode, так как Hibernate не включает кэш второго уровня, если используется режим UNSPECIFIED. -`//или в более старых версиях` +На самом деле, хибернейт сам не реализует кеширование как таковое. А лишь предоставляет структуру для его реализации, поэтому подключить можно любую реализацию, которая соответствует спецификации нашего ORM фреймворка. Из популярных реализаций можна выделить следующие: EHCache, OSCache, SwarmCache. -`//` +Для Hibernate требуется только реализация интерфейса org.hibernate.cache.spi.RegionFactory, который инкапсулирует все детали, относящиеся к конкретным провайдерам. По сути, RegionFactory действует как мост между Hibernate и поставщиками кэша. В примерах будем использовать Ehcache. Что нужно сделать: -`` +- добавить мавен-зависимость кэш-провайдера нужной версии: +```java + +org.hibernate +hibernate-ehcache +5.2.2.Final + +``` -На самом деле, хибернейт сам не реализует кеширование как таковое. А лишь предоставляет структуру для его реализации, поэтому подключить можно любую реализацию, которая соответствует спецификации нашего ORM фреймворка. Из популярных реализаций можна выделить следующие: EHCache, OSCache, SwarmCache. +- включить кэш второго уровня и определить конкретного провайдера: +```java +hibernate.cache.use_second_level_cache=true +hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory +``` + +- установить у нужных сущностей JPA-аннотацию @Cacheable, обозначающую, что сущность нужно кэшировать, и Hibernate-аннотацию @Cache, настраивающую детали кэширования, у которой в качестве параметра указать стратегию параллельного доступа (о которой говорится далее), например так: +```java +@Entity +@Table(name = "shared_doc") +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +public class SharedDoc{ +private Set users; +} +``` + +- не обязательно устанавливать у сущностей JPA-аннотацию @Cacheable, если работаем с Hibernate напрямую, не через JPA. + +- чтобы кэш не “съел” всю доступную память, можно, например, ограничивать количество каждого типа сущностей, хранимых в кэше: +```java + + + +``` + +__Стратегия параллельного доступа к объектам__ + +Проблема заключается в том, что кэш второго уровня доступен из нескольких сессий сразу и несколько потоков программы могут одновременно в разных транзакциях работать с одним и тем же объектом. Следовательно надо как-то обеспечивать их одинаковым представлением этого объекта. В Hibernate существует четыре стратегии одновременного доступа к объектам в кэше: +- READ_ONLY: Используется только для сущностей, которые никогда не изменяются (будет выброшено исключение, если попытаться обновить такую сущность). Очень просто и производительно. Подходит для некоторых статических данных, которые не меняются. +- +- NONSTRICT_READ_WRITE: Кэш обновляется после совершения транзакции, которая изменила данные в БД и закоммитила их. Таким образом, строгая согласованность не гарантируется, и существует небольшое временное окно между обновлением данных в БД и обновлением тех же данных в кэше, во время которого параллельная транзакция может получить из кэша устаревшие данные. + +- READ_WRITE: Эта стратегия гарантирует строгую согласованность, которую она достигает, используя «мягкие» блокировки: когда обновляется кэшированная сущность, на нее накладывается мягкая блокировка, которая снимается после коммита транзакции. Все параллельные транзакции, которые пытаются получить доступ к записям в кэше с наложенной мягкой блокировкой, не смогут их прочитать или записать и отправят запрос в БД. Ehcache использует эту стратегию по умолчанию. + +- TRANSACTIONAL: полноценное разделение транзакций. Каждая сессия и каждая транзакция видят объекты, как если бы только они с ним работали последовательно одна транзакция за другой. Плата за это — блокировки и потеря производительности. + +__Представление объектов в кэше__ + +Еще одна важная деталь про кэш второго уровня о которой стоило бы упомянуть — Hibernate не хранит сами объекты Ваших классов. Он хранит информацию в виде массивов строк, чисел и т.д. Что очень разумно, учитывая сколько лишней памяти занимает каждый объект. Идентификатор объекта выступает указателем на эту информацию. Концептуально это нечто вроде Map, в которой id объекта — ключ, а массивы данных — значения полей. Приблизительно это можно представить себе так: + +1 -> { "Pupkin", 1, null , {1,2,5} } + +Помимо вышесказанного, следует помнить — зависимости Вашего класса по умолчанию также не кэшируются. Например, рассмотрим класс SharedDoc, который кэшируется: +```java +@Entity +@Table(name = "shared_doc") +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +public class SharedDoc{ +private Set users; +} +``` + +В примере выше при выборке сущности SharedDoc из кэша, коллекция users будет доставаться из БД, а не из кэша второго уровня. Если мы хотим также кэшировать и зависимости, то над полями тоже нужно разместить аннотации @Cacheable и @Cache: +```java +@Entity +@Table(name = "shared_doc") +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +public class SharedDoc{ +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +private Set users; +} +``` + +Однако, при кэшировании коллекций, содержащих другие сущности, будут закэшированы только их первичные ключи. Если это коллекция базовых типов, то будут храниться сами значения базовых типов. + +__@Cache__ + +Это аннотация Hibernate, настраивающая тонкости кэширования объекта в кэше второго уровня Hibernate. @Cache принимает три параметра: + +- include - имеет по умолчанию значение all и означающий кэширование всего объекта. Второе возможное значение - non-lazy, запрещает кэширование лениво загружаемых объектов. Кэш первого уровня не обращает внимания на эту директиву и всегда кэширует лениво загружаемые объекты. + +- region - позволяет задать имя региона кэша для хранения сущности. Регион можно представить как разные области кэша, имеющие разные настройки на уровне реализации кэша. Например, можно было бы создать в конфигурации ehcache два региона, один с краткосрочным хранением объектов, другой с долгосрочным и отправлять часто изменяющиеся объекты в первый регион, а все остальные - во второй. Ehcache по умолчанию создает регион для каждой сущности с именем класса этой сущности, соответственно в этом регионе хранятся только эти сущности. К примеру, экземпляры Foo хранятся в Ehcache в кэше с именем “com.baeldung.hibernate.cache.model.Foo”. + +- usage - задаёт стратегию одновременного доступа к объектам. Кэш второго уровня создается в области фабрики EntityManagerFactory и доступен для использования во всех EntityManager, которые создаются с использованием этой конкретной фабрики. Это также означает, что после закрытия фабрики весь кэш, связанный с ним, умирает, а менеджер кэша также закрывается. @@ -1052,19 +1449,6 @@ __Кеш 2-го уровня__ — кеш второго уровня привя @Cacheable это аннотация JPA и позволяет объекту быть закэшированным. Hibernate поддерживает эту аннотацию в том же ключе. @Cache это аннотация Hibernate, настраивающая тонкости кэширования объекта в кэше второго уровня Hibernate. Аннотации @Cacheable достаточно, чтобы объект начал кэшироваться с настройками по умолчанию. При этом @Cache использованная без @Cacheable не разрешит кэширование объекта. -@Cache принимает три параметра: -+ include, имеющий по умолчанию значение all и означающий кэширование всего объекта. Второе возможное значение, non-lazy, запрещает кэширование лениво загружаемых объектов. Кэш первого уровня не обращает внимания на эту директиву и всегда кэширует лениво загружаемые объекты. -+ region позволяет задать имя региона кэша для хранения сущности. Регион можно представить как разные кэши или разные части кэша, имеющие разные настройки на уровне реализации кэша. Например, я мог бы создать в конфигурации ehcache два региона, один с краткосрочным хранением объектов, другой с долгосрочным и отправлять часто изменяющиеся объекты в первый регион, а все остальные во второй. -+ usage задаёт стратегию одновременного доступа к объектам. - -Помимо всего этого, вероятней всего, Вам также понадобится отдельно настроить и саму реализацию кеша. В случае с EHCache это нужно сделать в файле ehcache.xml. Ну и в завершение еще нужно указать самому хибернейту, что именно кешировать. К счастью, это очень легко можно сделать с помощью аннотаций, например так: @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) - -Еще одна важная деталь про кеш второго уровня про которую стоило бы упомянуть — хибернейт не хранит сами объекты Ваших классов. Он хранит информацию в виде массивов строк, чисел и т. д. И идентификатор объекта выступает указателем на эту информацию. Концептуально это нечто вроде Map, в которой id объекта — ключ, а массивы данных — значение. Приблизительно можно представить себе это так: - -`1 -> { "Pupkin", 1, null , {1,2,5} }` - -При этом зависимости Вашего класса по умолчанию также не кешируются. Они будут подгружаться из БД, если их отдельно ен закешировать. - Для работы с кэшем второго уровня (second level cache) в JPA описан Cache интерфейс, содержащий большое количество методов по управлению кэшем второго уровня (second level cache), если он поддерживается провайдером JPA, конечно. Объект данного интерфейса можно получить с помощью метода getCache у EntityManagerFactory. + Сохранение или обновление элемента: save(), update(), saveOrUpdate() @@ -1090,6 +1474,25 @@ __Кеш запросов__ — QueryCache, Кеш запросов похож Кроме того, вам также необходимо активировать кэширование для конкретного запроса, для которого вы хотите кэшировать результаты, вызывая `setCacheable(true)` +или через подсказку в запросе setHint("org.hibernate.cacheable", true): +```java +entityManager.createQuery("select f from Foo f") +.setHint("org.hibernate.cacheable", true) +.getResultList(); +``` + +У кэша запросов есть и своя цена — Hibernate будет вынужден отслеживать сущности закешированные с определённым запросом и выкидывать запрос из кэша, если кто-то поменяет значение сущности. То есть для кэша запросов стратегия параллельного доступа всегда read-only. + +## Для чего нужна аннотация @Cacheable? +@Cacheable - аннотация JPA, используется для указания того, должна ли сущность храниться в кэше второго уровня, в случае, если в файле persistence.xml (или в свойстве javax.persistence.sharedCache.mode конфигурационного файла) для элемента shared-cache-mode установлено одно из значений: +- ENABLE_SELECTIVE: только сущности с аннотацией @Cacheable (равносильно значению по умолчанию @Cacheable(value=true)) будут сохраняться в кэше второго уровня. +- DISABLE_SELECTIVE: все сущности будут сохраняться в кэше второго уровня, за исключением сущностей, помеченных аннотацией @Cacheable(value=false)как некэшируемые. +- ALL: сущности всегда кэшируются, даже если они помечены как некэшируемые. +- NONE: ни одна сущность не кэшируется, даже если помечена как кэшируемая. При данной опции имеет смысл вообще отключить кэш второго уровня. +- UNSPECIFIED: применяются значения по умолчанию для кэша второго уровня, определенные Hibernate. Это эквивалентно тому, что вообще не используется shared-cache-mode, так как Hibernate не включает кэш второго уровня, если используется режим UNSPECIFIED. + +Аннотация @Cacheable размещается над классом сущности. Её действие распространяется на эту сущность и её наследников, если они не определили другое поведение + ## Hibernate proxy (lazy load). Hibernate использует прокси объект для поддержки отложенной загрузки. Обычно при загрузке данных из таблицы Hibernate не загружает все отображенные (замаппинные) объекты. Как только вы ссылаетесь на дочерний объект или ищите объект с помощью геттера, если связанная сущность не находиться в кэше сессии, то прокси код перейдет к базе данных для загрузки связанной сущности. Для этого используется javassist, чтобы эффективно и динамически создавать реализации подклассов ваших entity объектов. @@ -1158,17 +1561,160 @@ public Owner read(Long id) { + Nonstrict-read-write — аналогичен read-write, но изменения объектов могут запаздывать и транзакции могут видеть старые версии объектов. Рекомендуется использовать в случаях, когда одновременное обновление объектов маловероятно и не может привести к проблемам. + Transactional — полноценное разделение транзакций. Каждая сессия и каждая транзакция видят объекты, как если бы только они с ним работали последовательно одна транзакция за другой. Плата за это — блокировки и потеря производительности. -## Что такое Mapped Superclass? -Mapped Superclass это класс от которого наследуются Entity, он может содержать аннотации JPA, однако сам такой класс не является Entity, ему не обязательно выполнять все требования установленные для Entity (например, он может не содержать первичного ключа). Такой класс не может использоваться в операциях EntityManager или Query. Такой класс должен быть отмечен аннотацией MappedSuperclass или соответственно описан в xml файле. - ## Какие три типа стратегии наследования мапинга (Inheritance Mapping Strategies) описаны в JPA? -В JPA описаны три стратегии наследования мапинга (Inheritance Mapping Strategies), то есть как JPA будет работать с классами-наследниками Entity: +Стратегии наследования нужны для того, чтобы дать понять провайдеру(Hibernate) как ему отображать в БД сущности-наследники. Для этого нам нужно декорировать родительский класс аннотацией @Inheritance и указать один из типов отображения: SINGLE_TABLE, TABLE_PER_CLASS, JOINED. + +1. SINGLE_TABLE. Одна таблица на всю иерархию классов. +2. TABLE_PER_CLASS. Таблица для каждого конкретного класса сущностей. +3. JOINED. Стратегия «соединения», при которой поля или свойства, специфичные + для подклассов, отображаются в таблицах этих подклассов, а поля или свойства + родительского класса отображаются в таблице родительского класса. + +1) __одна таблица на всю иерархию наследования (a single table per class hierarchy)__ — Является стратегией по умолчанию и используется, когда аннотация @Inheritance не указана в родительском классе или когда она указана без конкретной стратегии. все enity, со всеми наследниками записываются в одну таблицу, для идентификации типа entity определяется специальная колонка “discriminator column”. Например, если есть entity Animals c классами-потомками Cats и Dogs, при такой стратегии все entity записываются в таблицу Animals, но при это имеют дополнительную колонку animalType в которую соответственно пишется значение «cat» или «dog».Минусом является то что в общей таблице, будут созданы все поля уникальные для каждого из классов-потомков, которые будет пусты для всех других классов-потомков. Например, в таблице animals окажется и скорость лазанья по дереву от cats и может ли пес приносить тапки от dogs, которые будут всегда иметь null для dog и cat соответственно. -1) __одна таблица на всю иерархию наследования (a single table per class hierarchy)__ — все enity, со всеми наследниками записываются в одну таблицу, для идентификации типа entity определяется специальная колонка “discriminator column”. Например, если есть entity Animals c классами-потомками Cats и Dogs, при такой стратегии все entity записываются в таблицу Animals, но при это имеют дополнительную колонку animalType в которую соответственно пишется значение «cat» или «dog».Минусом является то что в общей таблице, будут созданы все поля уникальные для каждого из классов-потомков, которые будет пусты для всех других классов-потомков. Например, в таблице animals окажется и скорость лазанья по дереву от cats и может ли пес приносить тапки от dogs, которые будут всегда иметь null для dog и cat соответственно. +Еще пример, если есть entity Employee c классами-потомками FullTimeEmployee и PartTimeEmployee, то при такой стратегии все FullTimeEmployee и PartTimeEmployee записываются в таблицу Employee, и при этом в таблице появляется дополнительная колонка с именем DTYPE, в которой будут записаны значения, определяющие принадлежность к классу. По умолчанию эти значения формируются из имён классов, в нашем случае - либо «FullTimeEmployee» либо «PartTimeEmployee». Но мы можем их поменять в аннотации у каждого класса-наследника:@DiscriminatorValue("F") + +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate4.png) + +Если мы хотим поменять имя колонки, то мы должны указать её новое имя в параметре аннотации у класса-родителя: @DiscriminatorColumn(name=EMP_TYPE). +```java +@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; +} +``` +Эта стратегия обеспечивает хорошую поддержку полиморфных отношений +между сущностями и запросами, которые охватывают всю иерархию классов +сущностей +```java +-- 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'} +``` +Минусом стратегии является невозможность применения ограничения NOT NULL для тех колонок таблицы, которые характерны только для классов-наследников. 2) __объединяющая стратегия (joined subclass strategy)__ — в этой стратегии каждый класс enity сохраняет данные в свою таблицу, но только уникальные колонки (не унаследованные от классов-предков) и первичный ключ, а все унаследованные колонки записываются в таблицы класса-предка, дополнительно устанавливается связь (relationships) между этими таблицами, например в случае классов Animals (см.выше), будут три таблицы animals, cats, dogs, причем в cats будет записана только ключ и скорость лазанья, в dogs — ключ и умеет ли пес приносить палку, а в animals все остальные данные cats и dogs c ссылкой на соответствующие таблицы. Минусом тут являются потери производительности от объединения таблиц (join) для любых операций. -3) __одна таблица для каждого класса (table per concrete class strategy)__ — тут все просто каждый отдельный класс-наследник имеет свою таблицу, т.е. для cats и dogs (см.выше) все данные будут записываться просто в таблицы cats и dogs как если бы они вообще не имели общего суперкласса. Минусом является плохая поддержка полиморфизма (polymorphic relationships) и то что для выборки всех классов иерархии потребуются большое количество отдельных sql запросов или использование UNION запроса. +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate5.png) + +Столбец первичного ключа в таблице подкласса служит внешним ключом первичного ключа таблицы суперкласса. Также в таблице родительского класса добавляется столбец DiscriminatorColumn с DiscriminatorValue для определения типа наследника. +```java +@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] +``` + +Эта стратегия обеспечивает хорошую поддержку полиморфных отношений, но требует выполнения одной или нескольких операций соединения таблиц при создании экземпляров подклассов сущностей. В глубоких иерархиях классов это может привести к недопустимому снижению производительности. Точно так же запросы, которые покрывают всю иерархию классов, требуют операций соединения между таблицами подклассов, что приводит к снижению производительности: +```java +-- Loading entities -- +List entityAList = em.createQuery("Select t from Employee t") +.getResultList(); // Hibernate makes joins to assemble entities +FullTimeEmployee{id=1, name='Sara', salary=100000} +PartTimeEmployee{id=2, name='Robert', hourlyRate='60'} +``` + +3) __одна таблица для каждого класса (table per concrete class strategy)__ — каждый отдельный класс-наследник имеет свою таблицу, т.е. для cats и dogs (см.выше) все данные будут записываться просто в таблицы cats и dogs как если бы они вообще не имели общего суперкласса. Минусом является плохая поддержка полиморфизма (polymorphic relationships) и то что для выборки всех классов иерархии потребуются большое количество отдельных sql запросов или использование UNION запроса. + +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate6.png) + +```java +@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 entityAList = em.createQuery("Select t from Employee t") +.getResultList(); // Hibernate makes additional sql- or union-queries to get +entities +PartTimeEmployee{id=2, name='Robert', hourlyRate='60'} +FullTimeEmployee{id=1, name='Sara', salary=100000} +``` + +Минусом является плохая поддержка полиморфизма (polymorphic relationships) и то, что для выборки всех классов иерархии потребуется большое количество отдельных sql-запросов для каждой таблицы-наследника или использование UNIONзапроса для соединения таблиц всех наследников в одну таблицу. Также недостатком этой стратегии является повторение одних и тех же атрибутов в таблицах. + +При TABLE PER CLASS не работает стратегия генератора первичных ключей IDENTITY, поскольку может быть несколько объектов подкласса, имеющих один и тот же идентификатор, и запрос базового класса приведет к получению объектов с одним и тем же идентификатором (даже если они принадлежат разным типам). ## Стратегии загрузки объектов в Hibernate? __Join fetching:__ hibernate получает ассоциированные объекты и коллекции одним SELECT используя OUTER JOIN @@ -1182,9 +1728,249 @@ __Batch fetching:__ оптимизированная стратегия вида ## Для чего нужна аннотация Basic? Basic — указывает на простейший тип маппинга данных на колонку таблицы базы данных. Также в параметрах аннотации можно указать fetch стратегию доступа к полю и является ли это поле обязательным или нет. +В широком смысле Hibernate разделяет типы на две группы: +1. Типы значений (Value types). +2. Типы сущностей (Entity types). + +__Типы сущностей__ +Сущности из-за своего уникального идентификатора существуют независимо от других объектов, тогда как типы значений нет. Экземпляры сущностей соответствуют строкам в таблице базы данных и различаются между собой благодаря уникальным идентификаторам. Например, две сущности могут иметь абсолютно одинаковые значения полей, но имея разные идентификаторы (первичные ключи) они будут считаться разными, в отличие от POJO, которые при наличии абсолютно одинаковых значений полей будут считаться равными (equals вернет true). Из-за требования к наличию уникального идентификатора, сущности существуют независимо и определяют свой собственный жизненный цикл. + +__Типы значений__ +Это данные, которые не определяют свой собственный жизненный цикл. По сути, они принадлежат сущности (entity), которая определяет их жизненный цикл. С другой стороны, всё состояние объекта полностью состоит из типов значений. В свою очередь, типы значений подразделяются на три подкатегории: + +1. Базовые типы (Basic types). +2. Встраиваемые типы (Embeddable types). +3. Типы коллекций (Collection types) + + __Базовый тип значений__ +Соответствует одному столбцу в БД. Hibernate предоставляет ряд встроенных базовых типов, которые соответствуют естественным отображениям, рекомендованным спецификациями JDBC. + + Аннотация @Basic может быть применена к полю любого из следующих типов: +- Примитивы и их обертки. +- java.lang.String +- java.math.BigInteger +- java.math.BigDecimal +- java.util.Date +- java.util.Calendar +- java.sql.Date +- java.sql.Time +- java.sql.Timestamp +- byte[] or Byte[] +- char[] or Character[] +- enums +- любые другие типы, которые реализуют Serializable. + +Строго говоря, базовый тип в Hibernate обозначается аннотацией javax.persistence.Basic. Вообще, аннотацию @Basic можно не ставить, как это и происходит по умолчанию. + +Аннотация @Basic определяет 2 атрибута: +1. optional - boolean (по умолчанию true) - определяет, может ли значение поля или свойства быть null. Игнорируется для примитивных типов. Но если тип поля не примитивного типа, то при попытке сохранения сущности будет выброшено исключение. + +2. fetch - FetchType (по умолчанию EAGER) - определяет, должен ли этот атрибут извлекаться незамедлительно (EAGER) или лениво (LAZY). Однако, это необязательное требование JPA, и провайдерам разрешено незамедлительно загружать данные, даже для которых установлена ленивая загрузка. + +Без аннотации @Basic при получении сущности из БД по умолчанию её поля базового типа загружаются принудительно (EAGER) и значения этих полей могут быть null + +## Для чего нужна аннотация Column? +Аннотация @Column сопоставляет поле класса столбцу таблицы, а её атрибуты определяют поведение в этом столбце, используется для генерации схемы базы данных + +@Basic vs @Column: +1. Атрибуты @Basic применяются к сущностям JPA, тогда как атрибуты @Column применяются к столбцам базы данных. +2. @Basic имеет атрибут optional, который говорит о том, может ли поле объекта быть null или нет; с другой стороны атрибут nullable аннотации @Column указывает, может ли соответствующий столбец в таблице быть null. +3. Мы можем использовать @Basic, чтобы указать, что поле должно быть загружено лениво. +4. Аннотация @Column позволяет нам указать имя столбца в таблице и ряд других свойств: + a. insertable/updatable - можно ли добавлять/изменять данные в колонке, по умолчанию true; + b. length - длина, для строковых типов данных, по умолчанию 255 + ## Для чего нужна аннотация Access? Она определяет тип доступа (access type) для класса entity, суперкласса, embeddable или отдельных атрибутов, то есть как JPA будет обращаться к атрибутам entity, как к полям класса (FIELD) или как к свойствам класса (PROPERTY), имеющие гетеры (getter) и сетеры (setter). +Hibernate или другой провайдер должен каким-то образом получать доступ к полям сущности. Например, при сохранении сущности в базу данных Hibernate должен получить доступ к состоянию сущности, то есть прочитать значения полей сущности, чтобы записать их в соответствующие ячейки таблицы. Аналогично при получении данных из БД и формировании из них объекта сущности, Hibernate должен создать этот самый объект сущности (для этого ему и нужен public или protected конструктор без параметров), а затем записать в поля этого объекта значения, полученные из ячеек БД, тем самым сформировав состояние сущности. + +Для чтения и записи этих полей Hibernate использует два подхода: +1. Field access (доступ по полям). При таком способе аннотации маппинга (Id, Column, OneToMany, … ) размещаются над полями, и Hibernate напрямую работает с полями сущности, читая и записывая их. +2. Property access (доступ по свойствам). При таком способе аннотации размещаются над методами-геттерами, но никак не над сеттерами. Hibernate использует их и сеттеры для чтения и записи полей сущности. Но есть требование - у сущности с property access названия методов должны соответствовать требованиям JavaBeans. Например, если у сущности Customer есть поле с именем firstName, то у этой сущности должны быть определены методы getFirstName и setFirstName для чтения и записи поля firstName. + +Совокупность полей и методов (свойств) сущности называется атрибутами. + +Эти два подхода неявно определяют тип доступа к состоянию конкретной сущности - либо доступ по полям либо доступ по свойствам. Но при неявном определении типа доступа JPA требует, чтобы у всех сущностей в иерархии был единый тип доступа. + +По умолчанию тип доступа определяется местом, в котором находится аннотация @Id. Если она будет над полем - это будет AccessType.FIELD, если над геттером - это AccessType.PROPERTY. + +Чтобы явно определить тип доступа у сущности, нужно использовать аннотацию @Access, которая может быть указана у сущности, Mapped Superclass и Embeddable class, а также над полями или методами. + +Аннотация @Access позволяет в иерархии сущностей с одним единым типом доступа безболезненно определить для одной или нескольких сущностей другой тип доступа. То есть в иерархии, где у всех тип доступа, например, field access, можно у какой-нибудь сущности указать тип доступа property access и это не нарушит работу Hibernate. + +Если у сущности объявлена аннотация @Access(AccessType.FIELD): +- значит аннотации маппинга нужно размещать над полями; +- есть возможность у любых атрибутов сущности поменять тип доступа на property access, разместив аннотацию @Access(AccessType.PROPERTY) над соответствующими геттерами; +- разместив аннотации маппинга над методами, не имеющими @Access(AccessType.PROPERTY), получим неопределенное поведение. + +Если у сущности объявлена аннотация @Access(AccessType.PROPERTY): +- значит аннотации маппинга нужно размещать над геттерами; +- есть возможность у любых атрибутов сущности поменять тип доступа на field access, разместив аннотацию @Access(AccessType.FIELD) над соответствующими полями; +- разместив аннотации маппинга над полями, не имеющими @Access(AccessType.FIELD), получим неопределенное поведение. + +Поля, унаследованные от суперкласса, имеют тип доступа этого суперкласса, а не дочерней сущности, даже если они не совпадают. + +Когда у одной сущности определены разные типы доступа, то нужно использовать аннотацию @Transient для избежания дублирования маппинга. + +## Для чего нужны аннотации @Embedded и @Embeddable? +__@Embeddable__ +Аннотация JPA, размещается над классом для указания того, что класс является встраиваемым в другие классы и будет внедрен другими сущностями, то есть поля этого встраиваемого класса будут добавляться к полям других сущностей и будут представлять столбцы в таблице этой сущности. Так, во встраиваемый класс мы можем выделить общие поля для разных сущностей не создавая для него таблицу. Встраиваемый класс сам не является сущностью. +```java +@Embeddable +public class ContactPerson { +private String firstName; +private String lastName; +private String phone; +// standard getters, setters +} +``` + +__@Embedded__ +Аннотация JPA, используется для размещения над полем в классе-сущности для указания того, что мы внедряем встраиваемый класс. +```java +@Entity +public class Company { +@Id +@GeneratedValue +private Integer id; +private String name; +private String address; +private String phone; +@Embedded +private ContactPerson contactPerson; +// standard getters, setters +} +``` + +##Для чего нужны аннотации @JoinColumn, @JoinColumns и @JoinTable? +__@JoinColumn__ + +Аннотация @JoinColumn используется для указания столбца FOREIGN KEY, используемого при установлении связей между сущностями или коллекциями. Мы помним, что только сущность-владелец связи может иметь внешние ключи от другой сущности (владеемой). Однако, мы можем указать аннотацию @JoinColumn как во владеющей таблице, так и во владеемой, но столбец с внешними ключами всё равно появится во владеющей таблице. Особенности использования: + +- @OneToOne: означает, что появится столбец addressId в таблице сущностивладельца связи Office, который будет содержать внешний ключ, ссылающийся на первичный ключ владеемой сущности Address. +```java +@Entity +public class Office { +@OneToOne(fetch = FetchType.LAZY) +@JoinColumn(name = "addressId") +private Address address; +} +``` + +- @OneToMany/@ManyToOne: в данном случае мы можем использовать параметр mappedBy для того, чтобы столбец с внешними ключами находился на владеющей стороне ManyToOne - то есть в таблице Email: +```java +@Entity +public class Employee { + +@Id +private Long id; + +@OneToMany(fetch = FetchType.LAZY, mappedBy = "employee") +private List emails; +} +@Entity +public class Email { + +@ManyToOne(fetch = FetchType.LAZY) +@JoinColumn(name = "employee_id") +private Employee employee; +} +``` +В приведенном выше примере таблица Email (владелец связи) имеет столбец employee_id, в котором хранится значение идентификатора и внешний ключ к таблице Employee. + +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate8.png) +- Если бы мы не указали mappedBy, то была бы создана сводная (третья)таблица с первичными ключами из двух основных таблиц. + +__@JoinColumns__ + +Аннотация @JoinColumns используется для группировки нескольких аннотаций @JoinColumn, которые используются при установлении связей между сущностями или коллекциями, у которых составной первичный ключ и требуется несколько колонок для указания внешнего ключа. + +В каждой аннотации @JoinColumn должны быть указаны элементы name и referencedColumnName: +```java +@ManyToOne +@JoinColumns({ +@JoinColumn(name="ADDR_ID", referencedColumnName="ID"), +@JoinColumn(name="ADDR_ZIP", referencedColumnName="ZIP") +}) +public Address getAddress() { return address; } +``` + +__@JoinTable__ + +Аннотация @JoinTable используется для указания связывающей (сводной, третьей) таблицы между двумя другими таблицами. + +## Для чего нужны аннотации @OrderBy и @OrderColumn + +__@OrderBy__ + +Аннотация @OrderBy указывает порядок, в соответствии с которым должны располагаться элементы коллекций сущностей, базовых или встраиваемых типов при их извлечении из БД. Эта аннотация может использоваться с аннотациями @ElementCollection, @OneToMany, @ManyToMany. + +При использовании с коллекциями базовых типов, которые имеют аннотацию @ElementCollection, элементы этой коллекции будут отсортированы в натуральном порядке, по значению базовых типов: +```java +@ElementCollection +@OrderBy +private List phoneNumbers; +``` + +В данном примере коллекция строк phoneNumbers, будет отсортирована в натуральном порядке, по значениям базового типа String. + +Если это коллекция встраиваемых типов (@Embeddable), то используя точку(".") мы можем сослаться на атрибут внутри встроенного атрибута. Например, следующий код отсортируют адреса по названиям стран в обратном порядке: +```java +@Embeddable +public class Address { +... +@Embedded +private City city +.... +} +@ElementCollection +@OrderBy("city.country DESC") +private List
addresses; +``` + +Если это коллекция сущностей, то у аннотации @OrderBy можно указать имя поля сущности, по которому сортировать эти самые сущности: +```java +@Entity +public class Task { +.... +@OneToOne +private Employee supervisor; +... +} +@ManyToMany +@OrderBy("supervisor") +private List tasks +``` + +Если мы не укажем у @OrderBy параметр, то сущности будут упорядочены по первичному ключу. + +В случае с сущностями доступ к полю по точке (".") не работает. Попытка использовать вложенное свойство, например @OrderBy ("supervisor.name") повлечет Runtime Exceprtion. + +__@OrderColumn__ + +Аннотация @OrderColumn создает столбец в таблице, который используется для поддержания постоянного порядка в списке, но этот столбец не считается частью состояния сущности или встраиваемого класса. + +Hibernate отвечает за поддержание порядка как в базе данных при помощи столбца, так и при получении сущностей и элементов из БД. Hibernate отвечает за обновление порядка при записи в базу данных, чтобы отразить любое добавление, удаление или иное изменение порядка, влияющее на список в таблице. + +Аннотация @OrderColumn может использоваться с аннотациями @ElementCollection, @OneToMany, @ManyToMany - указывается на стороне отношения, ссылающегося на коллекцию, которая должна быть упорядочена. В примере ниже Hibernate добавил в таблицу Employee_PhoneNumbers третий столбец PHONENUMBERS_ORDER, который и является результатом работы @OrderColumn: +```java +'Show Columns from Employee_PhoneNumbers' +[EMPLOYEE_ID, BIGINT(19), NO, PRI, NULL] +[PHONENUMBERS, VARCHAR(255), YES, , NULL] +[PHONENUMBERS_ORDER, INTEGER(10), NO, PRI, NULL] +'Select * FROM Employee_PhoneNumbers' +[1, 111-111,111, 0] +[1, 222-222,222, 1] +[2, 333-333-333, 0] +[2, 444-444-444, 1] +[2, 666-666-666, 2] +[3, 555-555-555, 0] +``` + +__@OrderBy vs @OrderColumn__ +Порядок, указанный в @OrderBy, применяется только в рантайме при выполнении запроса к БД, То есть в контексте персистентности, в то время как при использовании @OrderColumn, порядок сохраняется в отдельном столбце таблицы и поддерживается при каждой вставке/обновлении/удалении элементов. + ## Для чего нужны callback методы в JPA? К каким сущностям применяются аннотации callback методов? Перечислите семь callback методов (или, что тоже самое, аннотаций callback методов) Callback методы служат для вызова при определенных событиях Entity (то есть добавить обработку например удаления Entity методами JPA), могут быть добавлены к entity классу, к mapped superclass, или к callback listener классу, заданному аннотацией EntityListeners (см предыдущий вопрос). Существует семь callback методов (и аннотаций с теми же именами): 1) PrePersist @@ -1196,11 +1982,6 @@ Callback методы служат для вызова при определен 7) PostLoad ## Какие видов блокировок (lock) описаны в спецификации JPA? -В оптимистичных блокировках при коммите в базу данных производится сравнивание значения поля, помеченного как version, на момент получения данных и на данный момент. Если оно изменилось, то есть какая-то другая транзакция опередила нашу и успела изменить данные, то в таком случае наша транзакция выбрасывает ошибку, и необходимо заново запускать ее. - -В пессимистичных же блокировка накладывается сразу же перед предполагаемой модификацией данных на все строки, которые такая модификация предположительно затрагивает. - -LockModeType задает стратегию блокирования. У JPA есть шесть видов блокировок, перечислим их в порядке увеличения надежности (от самого ненадежного и быстрого, до самого надежного и медленного): 1) NONE — без блокировки @@ -1210,6 +1991,70 @@ LockModeType задает стратегию блокирования. 5) PESSIMISTIC_WRITE — пессимистичная блокировка на запись (и чтение), 6) PESSIMISTIC_FORCE_INCREMENT — пессимистичная блокировка на запись (и чтение) с принудительным увеличением поля версионности. +__Оптимистичное блокирование__ предполагает, что параллельно выполняющиеся транзакции редко обращаются к одним и тем же данным и позволяет им спокойно и свободно выполнять любые чтения и обновления данных. Но при окончании транзакции производится проверка, изменились ли данные в ходе выполнения данной транзакции и, если да, транзакция обрывается и выбрасывается исключение. Оптимистичное блокирование в JPA реализовано путём внедрения в сущность специального поля версии: +```java +@Entity +public class Company extends AbstractIdentifiableObject { +@Version +private long version; +@Getter +@Setter +private String name; +@Getter +@Setter +@ManyToMany(mappedBy = "workingPlaces") +private Collection workers; +} +``` + +Поле, аннотирование @Version, может быть целочисленным или временнЫм. При завершении транзакции, если сущность была оптимистично заблокирована, будет проверено, не изменилось ли значение @Version кем-либо ещё, после того как данные были прочитаны, и, если изменилось, будет выкинуто OptimisticLockException. Использование этого поля позволяет отказаться от блокировок на уровне базы данных и сделать всё на уровне JPA, улучшая уровень конкурентности. + +JPA поддерживает два типа оптимистичной блокировки: + +- LockModeType.OPTIMISTIC — блокировка на чтение, которая работает, как описано выше: если при завершении транзакции кто-то извне изменит поле @Version, то транзакция автоматически будет откачена и будет выброшено OptimisticLockException. +- LockModeType.OPTIMISTIC_FORCE_INCREMENT — блокировка на запись. Ведёт себя как и блокировка на чтение, но при этом увеличивает значение поля @Version. + +Обе блокировки ставятся путём вызова метода lock() у EntityManager, в который передаётся сущность, требующая блокировки и уровень блокировки: +```java +EntityManager em = entityManagerFactory.createEntityManager(); +em.lock(company1, LockModeType.OPTIMISTIC); +em.lock(company2, LockModeType.OPTIMISTIC_FORCE_INCREMENT); +``` +Блокировка будет автоматически снята при завершении транзакции, снять её до этого вручную невозможно. + +__Пессимистичное блокирование__ напротив, ориентирован на транзакции, которые постоянно или достаточно часто конкурируют за одни и те же данные и поэтому блокирует доступ к данным превентивно, в тот момент когда читает их. Другие транзакции останавливаются, когда пытаются обратиться к заблокированным данным и ждут снятия блокировки (или кидают исключение). Пессимистичное блокирование выполняется на уровне базы и поэтому не требует вмешательств в код сущности. Так же, как и в случае с оптимистичным блокированием, поддерживаются блокировки чтения и записи: + +- LockModeType.PESSIMISTIC_READ — данные блокируются в момент чтения и это гарантирует, что никто в ходе выполнения транзакции не сможет их изменить. Остальные транзакции, тем не менее, смогут параллельно читать эти данные. Использование этой блокировки может вызывать долгое ожидание блокировки или даже выкидывание PessimisticLockException. +- LockModeType.PESSIMISTIC_WRITE — данные блокируются в момент записи и никто с момента захвата блокировки не может в них писать и не может их читать до окончания транзакции, владеющей блокировкой. Использование этой блокировки может вызывать долгое ожидание блокировки. + +- Кроме того, для сущностей с полем, аннотированным @Version, существует +третий вариант пессимистичной блокировки: __LockModeType.PESSIMISTIC_FORCE_INCREMENT__ — ведёт себя как LockModeType.PESSIMISTIC_WRITE, но в конце транзакции увеличивает значение поля @Version, даже если фактически сущность не изменилась. Накладываются пессимистичные блокировки так же как и оптимистичные, вызовом метода lock(): +```java +em.lock(company1, LockModeType.PESSIMISTIC_READ); +em.lock(company2, LockModeType.PESSIMISTIC_WRITE); +em.lock(company3, LockModeType.PESSIMISTIC_FORCE_INCREMENT); +``` + +Снимаются они тоже автоматически, по завершению транзакции. + +Следующие публичные методы EntityManager-а могут использоваться для +наложения блокировок: + +```java +void lock(Object entity, LockModeType lockMode) +void lock(Object entity, LockModeType lockMode, Map +properties) + T find(Class entityClass, Object primaryKey, LockModeType +lockMode) + T find(Class entityClass, Object primaryKey, LockModeType +lockMode, Map properties) +void refresh(Object entity, LockModeType lockMode) +void refresh(Object entity, LockModeType lockMode, Map +properties) +``` + +Помимо вышеуказанных методов, в Query API также есть методы для определения блокировок + ## Как можно изменить настройки fetch стратегии любых атрибутов Entity для отдельных запросов (query) или методов поиска (find), то если у Enity есть атрибут с fetchType = LAZY, но для конкретного запроса его требуется сделать EAGER или наоборот? Для этого существует EntityGraph API, используется он так: с помощью аннотации NamedEntityGraph для Entity, создаются именованные EntityGraph объекты, которые содержат список атрибутов у которых нужно поменять fetchType на EAGER, а потом данное имя указывается в hits запросов или метода find. В результате fetchType атрибутов Entity меняется, но только для этого запроса. Существует две стандартных property для указания EntityGraph в hit: ```java @@ -1271,8 +2116,78 @@ assertEquals(3, books.size()); + LOCK : передает в Hibernate native LOCK действие; + REPLICATE : передает в Hibernate native REPLICATE действие. +__Удаление сирот в отношениях (Orphan Removal)__ + +Представим, что у нас есть класс Customer, у которого есть коллекция Order: +```java +@Entity +public class Customer { + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List orders = new ArrayList<>(); + // other mappings, getters and setters +} +``` +Пусть у нас есть один объект Customer - родитель, в коллекции которого есть 4 объекта Order - дети, и мы установили атрибут orphanRemoval = true над этой коллекцией. В нашей базе данных в таблице Customer будет одна строка, а в таблице Order будет четыре строки. Также в таблице Order будет колонка с внешними ключами на таблицу Customer. В каждой из четырех ячеек этой колонки будут ссылки на один и тот же первичный ключ объекта Customer. + +Например, мы удалим из коллекции orders один объект Order - любой из четырех, в результате чего у объекта Customer останется три объекта Order: +```java +Customer customer = entityManager.find(Customer.class, 1L); +Order order = customer.getOrders().get(0); +customer.getOrders().remove(order); +flushAndClear(); +``` + +После запуска метода flushAndClear() - обновления объекта Customer отправятся в базу данных, и произойдет следующее: + +1. Hibernate заметит, что у объекта Customer уже не 4, а 3 связанных дочерних объекта Order; +2. в связи с этим Hibernate найдёт в таблице Order строку с удаленным объектом из коллекции Order; +3. очистит в этой строке ячейку с внешним ключом на Customer; +4. после чего удалит саму эту строку, как осиротевшую (более не ссылающуюся на родителя). + +Если не будет атрибута orphanRemoval = true, то пункт 4 не выполнится, и в таблице Order останется сущность Order, не связанная ни с одной сущностью Customer, то есть её ячейка с внешним ключом будет пустой. Такая сущность будет считаться осиротевшей. + ## Как определить владельца связи? -Для того, чтобы объявить сторону, которая не несет ответственности за отношения, используется атрибут mappedBy. Он ссылается на имя свойства связи на стороне владельца. +Существуют следующие четыре типа связей между сущностями: +1. OneToOne - когда один экземпляр Entity может быть связан не больше чем с одним экземпляром другого Entity. +2. OneToMany - когда один экземпляр Entity может быть связан с несколькими экземплярами других Entity. +3. ManyToOne - обратная связь для OneToMany. Несколько экземпляров Entity могут быть связаны с одним экземпляром другого Entity. +4. ManyToMany - экземпляры Entity могут быть связаны с несколькими экземплярами друг друга. + +__mappedBy__ + Владеемая сторона в двунаправленных отношениях должна ссылаться на владеющую сторону используя элемент mappedBy аннотаций @OneToOne, @OneToMany, или @ManyToMany. Элемент mappedBy определяет поле в объекте, который является владельцем отношения. Если применить атрибут mappedBy на одной стороне связи, то Hibernate не станет создавать сводную таблицу: +```java +@Entity +@Table(name="CART") +public class Cart { + //... + @OneToMany(mappedBy="cart") + private Set items; + // getters and setters +} +@Entity +@Table(name="ITEMS") +public class Items { + //... + @ManyToOne + @JoinColumn(name="cart_id", nullable=false) + private Cart cart; + public Items() {} + // getters and setters +} +``` +В данном примере таблица класса Items является владеющей стороной и будет иметь колонку с внешними ключами на таблицу Cart. Таблица класса Cart будет владеемой. + +__@ManyToOne__ +Сторона many в отношениях many-to-one всегда является владельцем отношений и не может определять элемент mappedBy (такого параметра у аннотации @ManyToOne просто нет). + +__@OneToOne__ +Для двунаправленных отношений one-to-one, сторона-владелец это та сторона, чья таблица имеет столбец с внешним ключом на другую таблицу. Если не указан параметр mappedBy, то колонки с айдишниками появляются у каждой таблицы. + +__@ManyToMany__ +Для двунаправленных отношений many-to-many, любая сторона может быть стороной-владельцем. + +__Запросы и направление отношений__ +Язык запросов Java Persistence и запросы API Criteria часто перемещаются между отношениями. Направление отношений определяет, может ли запрос перемещаться от одной сущности к другой. Например в двунаправленных отношениях запрос может перемещаться как от первой сущности ко второй, так и обратно. В однонаправленных отношениях запрос может перемещаться только в одну сторону - от владеющей сущности к владеемой [к оглавлению](#jdbc) @@ -1288,16 +2203,277 @@ assertEquals(3, books.size()); [к оглавлению](#jdbc) ## FetchType стратегии по-умолчанию? -Исторически Hibernate по умолчанию использует режим EAGER загрузки в отношении ManyToOne и OneToOne, а во всех остальных случаях — LAZY. Рекомендуется использовать LAZY во всех случаях. Указать в запросе делать join вместо нескольких select-ов всегда возможно, а обратно — отключить EAGER для определённых случаев — нет. +В JPA описаны два типа fetch-стратегии: +1. LAZY — данные поля сущности будут загружены только во время первого обращения к этому полю. +2. EAGER — данные поля будут загружены немедленно вместе с сущностью. + +FetchType.EAGER: Hibernate должен сразу загрузить соответствующее аннотированное поле или свойство. Это поведение по умолчанию для полей, аннотированных @Basic, @ManyToOne и @OneToOne (все что быстро). + +FetchType.LAZY: Hibernate может загружать данные не сразу, а при первом обращении к ним, но так как это необязательное требование, то Hibernate имеет право изменить это поведение и загружать их сразу. Это поведение по умолчанию для полей, аннотированных @OneToMany, @ManyToMany и @ElementCollection (все что медленно) . + +Раньше у Hibernate все поля были LAZY, но в последних версиях - всё как в JPA +[к оглавлению](#jdbc) + +## Enum значения в БД? +__По порядковым номерам EnumType.ORDINAL__ +Если мы сохраняем в БД сущность, у которой есть поле-перечисление (Enum), то в таблице этой сущности создаётся колонка для значений этого перечисления и по умолчанию в ячейки сохраняется порядковый номер этого перечисления (ordinal). +```java +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. Если мы добавим новое +значение в середину или просто изменим порядок перечисления, мы сломаем +существующую модель данных. Такие проблемы могут быть трудно уловимыми, и нам +придется обновлять все записи базы данных. + +__По именам EnumType.STRING__ +@Enumerated(EnumType.STRING) - означает, что в базе будут храниться имена Enum. С @Enumerated(EnumType.STRING) мы можем безопасно добавлять новые значения перечисления или изменять порядок перечисления. Однако переименование значения enum все равно нарушит работу базы данных. Кроме того, даже несмотря на то, что это представление данных гораздо более читаемо по сравнению с параметром @Enumerated(EnumType.ORDINAL), оно потребляет намного больше места, чем необходимо. Это может оказаться серьезной проблемой, когда нам нужно иметь дело с большим объемом данных. + +__@PostLoad и @PrePersist__ +Другой вариант - использование стандартных методов обратного вызова из JPA. Мы можем смапить наши перечисления в БД и обратно в методах с аннотациями @PostLoad и @PrePersist. + +Идея состоит в том, чтобы в сущности иметь не только поле с Enum, но и вспомогательное поле. Поле с Enum аннотируем @Transient, а в БД будет храниться значение из вспомогательного поля. Создадим Enum с полем priority. содержащем числовое значение приоритета: +```java +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, нам нужно добавить два атрибута и реализовать методы обратного вызова: +```java +@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. + +__Converter__ +В JPA с версии 2.1 можно использовать Converter для конвертации Enum’а в некое его значение для сохранения в БД и получения из БД. Все, что нам нужно сделать, это создать новый класс, который реализует javax.persistence.AttributeConverter и аннотировать его с помощью @Converter. +```java +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 { + + @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) -## Data и Enum значения в БД? -+ @Temporal(value=TemporalType.DATE) для Data до Java 8 версии. После 8 версии дополнительных аннотаций не требуется. +## Как мапятся даты (до Java 8 и после)? +При работе с датами рекомендуется установить определенный часовой пояс для драйвера JDBC. Таким образом, наше приложение будет независимым от текущего часового пояса системы. + +Другой способ - настроить свойство hibernate.jdbc.time_zone в файле свойств Hibernate, который используется для создания фабрики сессий. Таким образом, мы можем указать часовой пояс один раз для всего приложения. -+ Для Enum нужно указать аннотацию @Enumerated, которая принимает параметр типа EnumType: -1. EnumType.STRING – это значит, что в базе будет хранится имя этого enum. То есть если мы зададим role = RoleEnum.ADMIN, то в БД в поле role будет хранится значение ADMIN. -2. EnumType.ORDINAL – это значит, что в базе будет хранится ID этого enum. ID – это место расположение в списке перечисления начиная с 0. Например если значение enum равно ADMIN, то в базе будет хранится число 2, а если будет ANONYMOUS, то в базе будет хранится 0. +__java.sql__ +Hibernate позволяет отображать различные классы даты/времени из Java в таблицах баз данных. Стандарт SQL определяет три типа даты/времени: +1. DATE - Представляет календарную дату путем хранения лет, месяцев и дней. Эквивалентом JDBC является java.sql.Date. +2. TIME - Представляет время дня и хранит часы, минуты и секунды. Эквивалентом JDBC является java.sql.Time. +3. TIMESTAMP - Хранит как DATE, так и TIME плюс наносекунды. Эквивалентом JDBC является java.sql.Timestamp. Поскольку эти типы соответствуют SQL, их сопоставление относительно простое. Мы можем использовать аннотацию @Basic или @Column: +```java +@Entity +public class TemporalValues { +@Basic +private java.sql.Date sqlDate; +@Basic +private java.sql.Time sqlTime; +@Basic +private java.sql.Timestamp sqlTimestamp; +} +``` + Затем мы могли бы установить соответствующие значения следующим образом: + +```java +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 + +__java.util__ +Точность представления времени составляет одну миллисекунду. Для большинства практических задач этого более чем достаточно, но иногда хочется иметь точность повыше. + +Поскольку классы в данном API изменяемые (не immutable), использовать их в многопоточной среде нужно с осторожностью. В частности java.util.Date можно признать «эффективно» потоко-безопасным, если вы не вызываете у него устаревшие методы. + +__java.util.Date__ +Тип java.util.Date содержит информацию о дате и времени с точностью до миллисекунд. Но так как классы из этого пакета не имели прямого соответствия типам данных SQL, приходилось использовать над полями java.util.Date аннотацию @Temporal, чтобы дать понять SQL, с каким конкретно типом данных она работает. Для этого у аннотации @Temporal нужно было указать параметр TemporalType, который принимал одно из трёх значений: DATE, TIME или TIMESTAMP, что позволяло указать базе данных с какими конкретными типами данных она работает. +```java +@Basic +@Temporal(TemporalType.DATE) +private java.util.Date utilDate; +@Basic +@Temporal(TemporalType.TIME) +private java.util.Date utilTime; +@Basic +@Temporal(TemporalType.TIMESTAMP) +private java.util.Date utilTimestamp; +``` +Тип java.util.Date имеет точность до миллисекунд, и недостаточно точен для обработки SQL-значения Timestamp, который имеет точность вплоть до наносекунд. Поэтому, когда мы извлекаем сущность из базы данных, неудивительно, что в этом поле мы находим экземпляр java.sql.Timestamp, даже если изначально мы сохранили java.util.Date. Но это не страшно, так как Timestamp наследуется от Date. + +__java.util.Calendar__ +Как и в случае java.util.Date, тип java.util.Calendar может быть сопоставлен с различными типами SQL, поэтому мы должны указать их с помощью @Temporal. Разница лишь в том, что Hibernate не поддерживает отображение (маппинг) Calendar на TIME: +```java +@Basic +@Temporal(TemporalType.DATE) +private java.util.Calendar calendarDate; +@Basic +@Temporal(TemporalType.TIMESTAMP) +private_ java.util.Calendar calendarTimestamp +``` + +__java.time__ +Начиная с Java 8, доступен новый API даты и времени для работы с временными значениями. Этот API-интерфейс устраняет многие проблемы классов java.util.Date и java.util.Calendar. Все классы в новом API неизменяемые (immutable) и, как следствие, потоко-безопасные. Точность представления времени составляет одну наносекунду, что в миллион раз точнее чем в пакете java.util. Типы данных из пакета java.time напрямую отображаются (маппятся) на соответствующие типы SQL. Поэтому нет необходимости явно указывать аннотацию @Temporal: +1. LocalDate соответствует DATE. +2. LocalTime и OffsetTime соответствуют TIME. +3. Instant, LocalDateTime, OffsetDateTime и ZonedDateTime соответствуют + TIMESTAMP. + +Это означает, что мы можем пометить эти поля только аннотацией @Basic (или @Column), например: + +```java +@Basic +private java.time.LocalDate localDate; +@Basic +private java.time.LocalTime localTime; +@Basic +private java.time.OffsetTime offsetTime; +@Basic +private java.time.Instant instant; +@Basic +private java.time.LocalDateTime localDateTime; +@Basic +private java.time.OffsetDateTime offsetDateTime; +@Basic +private java.time.ZonedDateTime zonedDateTime; +``` + +Каждый временной класс в пакете java.time имеет статический метод parse() для анализа предоставленного значения типа String с использованием соответствующего формата. Итак, вот как мы можем установить значения полей сущности: +```java +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]")); +``` + +[к оглавлению](#jdbc) ## PersistenceContext Экземпляр EntityManager связан с persistence context. Контекст сохранения-это кэш первого уровня, в котором все сущности извлекаются из базы данных или сохраняются в ней. Он находится между нашим приложением и постоянным хранилищем. Набор экземпляров сущности, в котором для любого персистентного идентификатора сущности существует уникальный экземпляр сущности. В persistence context управляются экземпляры сущностей и их жизненный цикл. API EntityManager используется для создания и удаления постоянных экземпляров сущностей, поиска сущностей по их первичному ключу и выполнения запросов к сущностям. @@ -1332,7 +2508,143 @@ EntityGraph (граф сущностей) - механизм динамичес List findAll(@Nullable Specification specification) ``` -Для сложных зависимостей в запросах можно использовать NamedEntityGraph, но его нужно явно объявить в самой Entity\ +Для сложных зависимостей в запросах можно использовать NamedEntityGraph, но его нужно явно объявить в самой Entity + +Основная цель JPA Entity Graph - улучшить производительность в рантайме при загрузке базовых полей сущности и связанных сущностей и коллекций. + +Вкратце, Hibernate загружает весь граф в одном SELECT-запросе, то есть все +указанные связи от нужной нам сущности: +```java +@Entity +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String reply; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + private Post post; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + private User user; +//... +} +@Entity +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + private String email; +//... +} +@NamedEntityGraph( + name = "post-entity-graph-with-comment-users", + attributeNodes = { + @NamedAttributeNode("subject"), + @NamedAttributeNode("user"), + @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"), + }, + subgraphs = { + @NamedSubgraph( + name = "comments-subgraph", + attributeNodes = { + @NamedAttributeNode("user") + } + ) + } +) +@Entity +public class Post { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String subject; + @OneToMany(mappedBy = "post") + private List comments = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + private User user; + +//... +} +``` + +В данном примере мы создали EntityGraph и при его помощи хотим, чтобы при загрузке сущности Post загружались поля subject, user, comments. Также мы захотели, чтобы у каждой сущности comments загружалось поле user, для чего мы использовали subgraphs. + +EntityGraph можно определить и без помощи аннотаций, а используя entityManager из JPA API: +```java +EntityGraph entityGraph = entityManager.createEntityGraph(Post.class); +entityGraph.addAttributeNodes("subject"); +entityGraph.addAttributeNodes("user"); +entityGraph.addSubgraph("comments").addAttributeNodes("user"); +``` + +JPA определяет два свойства или подсказки, с помощью которых Hibernate может выбирать стратегию извлечения графа сущностей во время выполнения: +- fetchgraph - из базы данных извлекаются только указанные в графе атрибуты. Поскольку мы используем Hibernate, мы можем заметить, что в отличие от спецификаций JPA, атрибуты, статически настроенные как EAGER, также загружаются. + +- loadgraph - в дополнение к указанным в графе атрибутам, также извлекаются атрибуты, статически настроенные как EAGER. В любом случае, первичный ключ и версия, если таковые имеются, всегда загружаются. + +Загрузить EntityGraph можем тремя способами: +1. Используя перегруженный метод find(), который принимает Map с настройкам EntityGraph: +```java + EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph"); + Map properties = new HashMap<>(); + properties.put("javax.persistence.fetchgraph", entityGraph); + Post post = entityManager.find(Post.class, id, properties); +``` + + +2. Используя JPQL и передав подсказку (hint): +```java +EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-withcomment-users"); + Post post = entityManager.createQuery("select p from Post p where p.id = :id", + Post.class) + .setParameter("id", id) + .setHint("javax.persistence.fetchgraph", entityGraph) + .getSingleResult(); +``` + +3. С помощью Criteria API: +```java +EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-withcomment-users"); + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Post.class); + Root root = criteriaQuery.from(Post.class); + criteriaQuery.where(criteriaBuilder.equal(root.get("id"), id)); + TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); + typedQuery.setHint("javax.persistence.loadgraph", entityGraph); + Post post = typedQuery.getSingleResult(); +``` + + Все они дают следующий результат: +```java +select + post0_.id as id1_1_0_, + post0_.subject as subject2_1_0_, + post0_.user_id as user_id3_1_0_, + comments1_.post_id as post_id3_0_1_, + comments1_.id as id1_0_1_, + comments1_.id as id1_0_2_, + comments1_.post_id as post_id3_0_2_, + comments1_.reply as reply2_0_2_, + comments1_.user_id as user_id4_0_2_, + user2_.id as id1_2_3_, + user2_.email as email2_2_3_, + user2_.name as name3_2_3_ + from + Post post0_ + left outer join + Comment comments1_ + on post0_.id=comments1_.post_id + left outer join + User user2_ + on post0_.user_id=user2_.id + where + post0_.id=? +``` + + В каждом из них стратегия графа (fetchgraph, loadgraph) указана как подсказка. В первом примере мы использовали Map, в то время как в двух последующих примерах мы использовали метод setHint() https://www.baeldung.com/jpa-entity-graph https://thorben-janssen.com/jpa-21-entity-graph-part-1-named-entity/ @@ -1346,10 +2658,45 @@ __JOIN FETCH__ __FetchMode.SUBSELECT__ -Использовать FetchMode.SUBSELECT и ленивую инициализацию. В этом случае, будет произведено два запроса: первый сделает выборку основной сущности, второй запрос отправится в базу после того, как мы обратимся к ленивому полю. Запрос будет один на получение всех связанных объектов. Данное решение не работает с нативными запросами и с полями, помеченными аннотацией @ManyToOne +Это Аннотация Hibernate, в JPA её нет. Можно использовать только с коллекциями. Будет сделан один sql-запрос для получения корневых сущностей и, если в контексте персистентности будет обращение к ленивым полям-коллекциям, то выполнится еще один запрос для получения связанных коллекций __EntityGraph__ -Использовать entityGraph. Не самое изящное решение для n+1 проблемы. Графы в основном нужны, когда требуется загрузить действительно большой детальный граф, т.е. когда нам нужно получить очень много связанной информации из базы, и такой большой запрос следует оптимизировать. Для n+1 не самое компактное решение, но тоже работает, однако такое решение не подойдет при использовании нативных запросов. + +Не самое изящное решение для n+1 проблемы. Графы в основном нужны, когда требуется загрузить действительно большой детальный граф, т.е. когда нам нужно получить очень много связанной информации из базы, и такой большой запрос следует оптимизировать. Для n+1 не самое компактное решение, но тоже работает, однако такое решение не подойдет при использовании нативных запросов. + +__Batch fetching__ + +Это Аннотация Hibernate, в JPA её нет. Указывается над классом сущности или над полем коллекции с ленивой загрузкой. Будет сделан один sql-запрос для получения корневых сущностей и, если в контексте персистентности будет обращение к ленивым полям-коллекциям, то выполнится еще один запрос для получения связанных коллекций. Изменим пример: +```java +@OneToMany(mappedBy = "customer") +@Fetch(value = FetchMode.SELECT) +@BatchSize(size=5) +private Set orders = new HashSet<>(); +``` + +Например, мы знаем, что в персистентный контекст загружено 12 сущностей Customer, у которых по одному полю-коллекции orders, но так как это @OneToMany, то у них ленивая загрузка по умолчанию и они не загружены в контекст персистентности из БД. При первом обращении к какому-нибудь полю orders, нам бы хотелось, чтобы для всех 12 сущностей Customer были загружены их 12 коллекций Order, по одной для каждой. Но так как у нас @BatchSize(size=5), то Hibernate сделает 3 запроса: в первом и втором получит по пять коллекций, а в третьем получит две коллекции. + +Если мы знаем примерное количество коллекций, которые будут использоваться в любом месте приложения, то можно использовать @BatchSize и указать нужное количество. + +Также аннотация @BatchSize может быть указана у класса. Рассмотрим пример, где у нас есть сущность Order, у которой есть поле типа Product(не коллекция). Мы выгрузили в контекст персистентности 27 объектов Order. При обращении к полям Product у объектов Order будет инициализировано до 10 ленивых прокси сущностей Product одновременно: +```java +@Entity +class Order { +@OneToOne(fetch = FetchType.LAZY) +private Product product; +... +} +@Entity +@BatchSize(size=10) +class Product { +... +} +``` + +Хотя использовать @BatchSize лучше, чем столкнуться с проблемой запроса N+1, в большинстве случаев гораздо лучшей альтернативой является использование DTO или JOIN FETCH, поскольку они позволяют получать все необходимые данные одним запросом. + +__HibernateSpecificMapping, SqlResultSetMapping__ Для нативных запросов рекомендуется использовать именно их. + ## JOIN FETCH vs Join При запросе `JOIN emp.department dep` возвращается только сам запрощенный объект. При запросе `JOIN FETCH emp.department dep` возвращается объект и связанные с ним сущности, в одном запросе. @@ -1375,14 +2722,167 @@ OrderBy упорядычевает результат запроса (от ме + Первый метод использования составного ключа включает класс ключа целиком в класс сущности: @EmbeddedId указывает на поле составного первичного ключа, а @Embeddable объявляет класс составным ключом. + Второй вариант использования оставляет поля первичного ключа непосредственно в классе сущности, а класс составного ключа служит лишь для поддержки: @IdClass(Passport.PassportKey.class) +__@IdClass__ +Допустим, у нас есть таблица с именем Account, и она имеет два столбца - accountNumber и accountType, которые формируют составной ключ. Чтобы обозначить оба этих поля как части составного ключа мы должны создать класс, например, AccountId с этими полями: +```java +public class AccountId implements Serializable { +private String accountNumber; +private String accountType; +// default constructor +public AccountId(String accountNumber, String accountType) { +this.accountNumber = accountNumber; +this.accountType = accountType; +} +// equals() and hashCode() +} +``` +Затем нам нужно аннотировать сущность Account аннотацией @IdClass. Мы также должны объявить поля из класса AccountId в сущности Account с такими же именами и аннотировать их с помощью @Id: +```java +@Entity +@IdClass(AccountId.class) +public class Account { +@Id +private String accountNumber; +@Id +private String accountType; +// other fields, getters and setters +} +``` +__@EmbeddedId__ + +Является альтернативой аннотации @IdClass. Рассмотрим другой пример, в котором мы должны сохранить некоторую информацию о книге с заголовком и языком в качестве полей первичного ключа. В этом случае класс первичного ключа, BookId, должен быть аннотирован @Embeddable: +```java +@Embeddable +public class BookId implements Serializable { +private String title; +private String language; +// default constructor +public BookId(String title, String language) { +this.title = title; +this.language = language; +} +// getters, equals() and hashCode() methods +} +//Затем нам нужно встроить этот класс в сущность Book, используя @EmbeddedId: +@Entity +public class Book { +@EmbeddedId +private BookId bookId; +// constructors, other fields, getters and setters +} + +``` +__@IdClass vs @EmbeddedId__ + +- с @IdClass нам пришлось указывать столбцы дважды - в AccountId и в Account. Но с @EmbeddedId мы этого не сделали; +- JPQL-запросы с @IdClass проще. С @EmbeddedId, чтобы получить доступ к полю, нам нужно из сущности обратиться к встраиваемому классу и потом к его полю: +```java +SELECT account.accountNumber FROM Account account // с @IdClass +SELECT book.bookId.title FROM Book book // с @EmbeddedId +``` +- @EmbeddedId более подробна, чем @IdClass, поскольку мы можем получить доступ ко всему объекту первичного ключа, используя метод доступа к полю в классе-сущности. Это также дает четкое представление о полях, которые являются частью составного ключа, поскольку все они агрегированы в классе, который доступен только через метод доступа к полям; +- @IdClass может быть предпочтительным выбором по сравнению с @EmbeddedId в ситуациях, когда класс составного первичного ключа поступает из другого модуля или устаревшего кода, а также когда мы не можем его изменить, например, чтобы установить аннотацию @EmbeddedId. Для таких сценариев, где мы не можем изменить класс составного ключа, аннотация @IdClass является единственным выходом; +- если мы собираемся получить доступ к частям составного ключа по отдельности, мы можем использовать @IdClass, но в тех местах, где мы часто используем полный идентификатор в качестве объекта, @EmbeddedId предпочтительнее. + [к оглавлению](#jdbc) ## GeneratedValue Автоматически генерирует значение первичного ключа. Используется 4 стратегии (strategy = GenerationType.IDENTITY), Если мы не указываем значение явно, типом генерации по умолчанию является AUTO. -+ AUTO - значения определяется на основе типа атрибута первичного ключа. Выбирает стратегию генерации на основе конкретного диалекта базы данных. Для большинства популярных баз данных он выбирает GenerationType.SEQUENCE. -+ IDENTITY - тип генерации основан на IdentityGenerator , который ожидает значения, сгенерированные столбцом identity в базе данных. является самым простым в использовании, но не самый лучший с точки зрения производительности. Он опирается на автоматически увеличивающийся столбец базы данных и позволяет базе данных генерировать новое значение при каждой операции вставки. Hibernate требует значения первичного ключа для каждого управляемого объекта и поэтому должен немедленно выполнить оператор вставки. Это предотвращает использование различных методов оптимизации, таких как пакетная обработка JDBC. (Идентити делает инсерт до персиста). -+ SEQUENCE - генератор использует последовательности, если они поддерживаются нашей базой данных, и переключается на генерацию таблиц, если они не поддерживаются. Чтобы настроить имя последовательности, мы можем использовать аннотацию @ GenericGenerator со стратегией SequenceStyleGenerator, использует последовательность базы данных для генерации уникальных значений. Для получения следующего значения из последовательности базы данных требуются дополнительные операторы select. Но это не влияет на производительность для большинства приложений. (Секвенс делает селект, чтобы сгенерить id). -+ TABLE - использует базовую таблицу базы данных, которая содержит сегменты значений генерации идентификатора. Используется редко. Он моделирует последовательность, сохраняя и обновляя ее текущее значение в таблице базы данных, что требует использования пессимистических блокировок, которые помещают все транзакции в последовательный порядок. Это замедляет работу вашего приложения + +__AUTO__ + +значения определяется на основе типа атрибута первичного ключа. Выбирает стратегию генерации на основе конкретного диалекта базы данных. Для большинства популярных баз данных он выбирает GenerationType.SEQUENCE. +__IDENTITY__ + +Указывает, что для генерации значения первичного ключа будет использоваться столбец IDENTITY, имеющийся в базе данных. Значения в столбце автоматически увеличиваются, что позволяет базе данных генерировать новое значение при каждой операции вставки. С точки зрения базы данных это очень эффективно, поскольку столбцы с автоинкрементом хорошо оптимизированы и не требуют каких-либо дополнительных операторов. Процесс инкремента (получения следующего) первичного ключа происходит вне текущей выполняемой транзакции, поэтому откат транзакции может в конечном итоге обнулить уже присвоенные значения (могут возникнуть пропуски значений). + +Если мы используем Hibernate, то использование IDENTITY имеет существенный nедостаток. Так как Hibernate нужен первичный ключ для работы с managed-объектом в persistence context, а мы не можем узнать значение первичного ключа до выполнения инструкции INSERT, то Hibernate должен немедленно выполнить оператор INSERT, чтобы получить этот самый первичный ключ, сгенерированный БД. Только после этого у Hibernate будет возможность работать с сущностью в контексте персистентности, после чего выполнить операцию persist. Но Hibernate, в соответствии со своей идеологией, использует стратегию “транзакционная запись-после” (transactional writebehind), согласно которой он пытается максимально отложить сброс данных в БД из контекста персистентности, чтобы не делать много обращений к БД. Так как поведение при IDENTITY противоречит идеологии и стратегии “транзакционная запись-после”, Hibernate отключает пакетные вставки (batching inserts) для объектов, использующих генератор IDENTITY. Однако, пакетные обновления и удаления (batching updates и batching deletes) всё же поддерживаются. + +IDENTITY является самым простым в использовании типом генерации, но не самым лучшим с точки зрения производительности. Как уже упоминалось, стратегия генератора первичных ключей IDENTITY не работает при TABLE PER CLASS, поскольку может быть несколько объектов подкласса, имеющих один и тот же идентификатор, и запрос базового класса приведет к получению объектов с одним и тем же идентификатором (даже если они принадлежат разным типам + +__SEQUENCE__ + + Указывает, что для получения значений первичного ключа Hibernate должен использовать имеющиеся в базе данных механизмы генерации последовательных значений (Sequence). Но если наша БД не поддерживает тип SEQUENCE, то Hibernate автоматически переключится на тип TABLE. + +SEQUENCE - это объект базы данных, который генерирует инкрементные целые числа при каждом последующем запросе. SEQUENCE намного более гибкий, чем IDENTITY, потому что: + - SEQUENCE не содержит таблиц, и одну и ту же последовательность можно назначить нескольким столбцам или таблицам; + - SEQUENCE может предварительно распределять значения для улучшения производительности; + - SEQUENCE может определять шаг инкремента, что позволяет нам воспользоваться «объединенным» алгоритмом Hilo; + - SEQUENCE не ограничивает пакетные вставки JDBC в Hibernate; + - SEQUENCE не ограничивает модели наследования Hibernate. + +При SEQUENCE для получения следующего значения из последовательности базы данных требуются дополнительные операторы select, но это не влияет на производительность для большинства приложений. И если нашему приложению необходимо сохранить огромное количество новых сущностей, мы можем использовать некоторые специфичные для Hibernate оптимизации, чтобы уменьшить количество операторов. + + Для работы с этой стратегией Hibernate использует свой класс SequenceStyleGenerator. SEQUENCE - это тип генерации, рекомендуемый документацией Hibernate. + +Самый простой способ - просто задать безымянную генерацию последовательности: +```java +@Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue( + strategy = GenerationType.SEQUENCE // Имя последовательности не + // определено, поэтому Hibernate будет использовать последовательность + // hibernate_sequence для всех сущностей + ) + private Long id; + @Column(name = "product_name") + private String name; + //Getters and setters are omitted for brevity + } +``` + + Для всех сущностей с безымянной последовательностью Hibernate будет использовать одну и ту же hibernate_sequence, из которой будет брать для них айдишники. + +Используя аннотацию @SequenceGenerator, мы можем указать конкретное имя последовательности для таблицы, а также иные параметры. + +Также мы можем настроить под себя несколько разных последовательностей(SEQUENCE-генераторов), указав, например, имя последовательности и начальное значение: +```java +@Entity + public class User { + @Id + @GeneratedValue(generator = "sequence-generator") + @GenericGenerator( + name = "sequence-generator", + strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", + parameters = { + @Parameter(name = "sequence_name", value = "user_sequence"), + @Parameter(name = "initial_value", value = "4"), + @Parameter(name = "increment_size", value = "1") + } + ) + private long userId; + // ... + } +``` + + В этом примере мы установили имя последовательности и начальное значение, что означает, что генерация первичного ключа начнется с 4. Для каждой последовательности сгенерированные значения являются уникальными. + +Так, мы можем назначать разные последовательности разным сущностям и они будут брать айдишники из этой последовательности. В зависимости от требований приложения, можно иметь один генератор на всё приложение, по генератору на каждую сущность или несколько генераторов, которыми пользуются несколько сущностей. + +Например, у нас есть 10 сущностей, для трех из них мы создадим последовательность с именем first_sequence, из которой они будут брать айдишники. Для пяти других сущностей создадим последовательность с именем second_sequence, из которой они будут брать свои айдишники. А для оставшихся двух сущностей можем задать безымянную последовательность, и в этом случае айдишники для них будут браться по умолчанию из hibernate_sequence. + +__TABLE__ + +В настоящее время GenerationType.TABLE используется редко. Hibernate должен получать первичные ключи для сущностей из специальной создаваемой для этих целей таблицы, способной содержать несколько именованных сегментов значений для любого количества сущностей. Основная идея заключается в том, что данная таблица(например, hibernate_sequence) может содержать несколько сегментов со значениями идентификаторов для разных сущностей. Это требует использования пессимистических блокировок, которые помещают все транзакции по получению идентификаторов в очередь. Разумеется, это замедляет работу приложения. + +Третья стратегия, GenerationType.TABLE, не зависит от поддержки конкретной базой данных и хранит счётчики значений в отдельной таблице. С одной стороны это более гибкое и настраиваемое решение, с другой стороны более медленное и требующее большей настройки. Вначале требуется создать (вручную!) и проинициализировать (!) таблицу для значений ключей. Затем создать генератор и связать его со идентификатором: + +Используя аннотацию @TableGenerator мы можем настроить этот тип генерации: +```java +@Entity +public class Department { +@Id +@GeneratedValue(strategy = GenerationType.TABLE, +generator = "table-generator") +@TableGenerator(name = "table-generator", +table = "dep_ids", +pkColumnName = "seq_id", +valueColumnName = "seq_value") +private long depId; +// ... +} +``` Если мы не хотим использовать какие-либо из готовых стратегий, мы можем определить наш собственный генератор, реализуя интерфейс IdentifierGenerator . @@ -1400,11 +2900,46 @@ GenerationType.SEQUENCE - id инкрементится на стороне хи [к оглавлению](#jdbc) -## ElementCollection +## ElementCollection - Как сохранять в БД коллекции базовых типов? ElementCollection - стандартная аннотация JPA, означает, что коллекция не является совокупностью объектов, а представляет собой набор простых типов (строки и т.д.) или набор встраиваемых элементов (класс, аннотированный с помощью @Embeddable). Это также означает, что элементы полностью принадлежат содержащим объектам: они изменяются, когда объект изменяется, удаляется при удалении объекта и т.д. Они не могут иметь свой собственный жизненный цикл. +@ElementCollection указывается в классе сущности над полем коллекции базовых или встраиваемых типов. Все записи коллекции хранятся в отдельной таблице, то есть в итоге получаем две таблицы: одну для сущности, вторую для коллекции элементов. Конфигурация для таблицы коллекции элементов указывается с помощью аннотации @CollectionTable, которая используется для указания имени таблицы коллекции и JoinColumn, который ссылается на первичную таблицу. + +![Image alt](https://github.com/Shell26/Java-Developer/raw/master/img/Hibernate7.png) +```java +@Entity +public class Customer { + +@Id + @GeneratedValue + private int id; + private String name; + + @ElementCollection + private List phoneNumbers; + ............. +} + +``` +Аннотация @ElementCollection похожа на отношение @OneToMany, за исключением того, что целью являются базовые и встраиваемые типы, а не сущности. Можно использовать аннотации @AttributeOverrides и @AttributeOverride для настройки отображения в таблице полей базовых или встраиваемых типов. + +Коллекции могут иметь тип java.util.Map, которые состоят из ключа и значения. Для этого типа коллекций применяются следующие правила: +1. Ключ или значение Map может быть базовым типом языка программирования + Java, встраиваемым классом или сущностью. +2. Если значение Map является встраиваемым классом или базовым типом, + используйте аннотацию @ElementCollection. Пример. +3. Если значение Map является сущностью, используйте аннотацию @OneToMany + или @ManyToMany. Пример. +4. Использовать тип Map только на одной стороне двунаправленной связи. + +Аннотация @MapKeyColumn позволяет настроить столбец «ключ» в таблице Map. Аннотация @Column позволяет настроить столбец «значение» в таблице Map. + +Использование коллекций элементов имеет один большой недостаток:элементы коллекции не имеют идентификатора, и Hibernate не может обращаться индивидуально к каждому элементу коллекции. Когда мы добавляем новый объект в коллекцию или удаляем из коллекции существующий элемент, Hibernate удаляет все строки из таблицы элементов и вставляет новые строки по одной для каждого элемента в коллекции. То есть при добавлении одного элемента в коллекцио Hibernate не добавит одну строку в таблицу коллекции, а очистит её и заполнит по новой всеми элементами. + +Поэтому коллекции элементов следует использовать только для очень маленьких коллекций, чтобы Hibernate не выполнял слишком много операторов SQL. Во всех других случаях рекомендуется использовать коллекции сущностей с @OneToMany. + [к оглавлению](#jdbc) ## Метод unWrap()