Hibernate - DmitryGontarenko/usefultricks GitHub Wiki

About

JPA (Java Persistence API) – спецификация, которая предоставляет возможность сохранять в удобном виде Java-объекты в базе данных.
Спецификация является лишь описание Java API. Грубо говоря, в ней указано, какими средставами мы должны быть обеспечены (какие интерйсы мы должны реализовать), что бы реализовать концепцию ORM.
Саму реализацию таких средств спецификация не описывает. Это дает возможность использовать для одной спецификации разные реализации.
Существует несколько реализаций этой спецификации, одна из самых популярных использует это Hibernate.

ORM (Object-Relational Mapping, объектно-реляционное отображение) - это технология программирования для преобразования данных между реляционными базами данных и объекто-ориентированными языками программирования.

Hibernate - реализация JPA, предназначенная для решения задач объектно-реляционного отображения (ORM). Hibernate можно использоват как для создания таблиц с нуля, так и для работы с уже готовыми таблицами. Библиотека решает задачу связи классов Java с таблицами базы данных, предоставляет средства для автоматической генерации, обновления набора таблиц, построения запросов, обработки полученных, автоматизирует генерацию SQL-запросов. Hibernate использует SQL-подобный языка HQL.

Set Up

Устанавливам Maven зависимости:

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.9.Final</version>
        </dependency>

Configuration Object

Configuration Object - это первый объект Hibernate, который создается в любом Hibernate-приложении. Обычно создается только один раз во время инициализации приложения. Он представляет файл конфигурации или свойств, требуемый Hibernate.
Объект конфигурации предоставляет два ключевых компонента:

  • Database Connection - соединение с базой данных устанавливается через один или несколько конфигурационных файлов - hibernate.properties и hibernate.cfg.xml;
  • Class Mapping Setup - этот компонент создает связь между классами Java и таблицами базы данных.
Configuration configuration = new Configuration(); // создания объекта Configuration
configuration.configure(); // чтение конфигурации из файлов hibernate.cfg.xml и hibernate.properties

Экземпляр класса Configuration задуман как объект времени конфигурации (configuration-time). Он создается во время конфигурации и уничтожается как только построен экземпляр класса SessionFactory.

SessionFactory Object

SessionFactory Object - после того, как экземпляр класса Configuration "прочитает" все конфигурации, приложение получит фабрику SessionFactory для создания объекта Session.
SessionFactory является потокобезопасным объектом и используется всеми потоками приложения. На каждую базу данных с использованием отдельного файла конфигурации потребуется один объект SessionFactory.

SessionFactory sessionFactory = configuration.buildSessionFactory();

Session Object

Session Object - сессия используется для получения физического соединения с базой данных. Объект Session является легковесным и предназначен для реализации каждый раз, когда необходимо взаимодействие с базой данных.
Объекты сеанса не должны оставаться открытыми в течение длительного времени, потому что они обычно не являются потокобезопасными, и их следует создавать и уничтожать по мере необходимости.

Session session = sessionFactory.openSession();

Transaction Object

Transaction Object - транзакция представляет собой объект для работы с базой данных, и большинство СУБД поддерживает функциональность транзакций.

Transaction transaction = session.beginTransaction();
session.save(car); // какое-либо взаимодействие с БД, например запись объекта Car в соответствующую таблицу
transaction.commit();

Код целиком:

    public void saveCar(Car car) {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        Session session = sessionFactory.openSession();

        Transaction transaction = session.beginTransaction();
        session.save(car);
        transaction.commit();
    }

Код целиком (более правильная версия):

    public void saveCar(Car car) {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        Session session = sessionFactory.openSession();

        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();
            session.save(car);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null)
                transaction.rollback(); // откатываем изменения в случае ошибки
        } finally {
            session.close(); // закрываем сессию
        }
    }

Query Object

Query Object - объекты запросов используют SQL или язык запросов Hibernate (HQL) для извлечения данных из базы данных и создания объектов. Экземпляр Query используется для привязки параметров запроса, ограничения количества результатов, возвращаемых запросом, и, наконец, для выполнения запроса.

Configuration

Hibernate требуется знать - где найти информацию о маппинге, которая определяет, как Java классы связаны с таблицами базы данных. Hibernate также требует конфигурацию, которая содержит набора параметров, связанных с базой данных.
Вся такая информация обычно предоставляется в виде стандартного файла свойств Java с именем hibernate.properties или в виде файла XML с именем hibernate.cfg.xml.

Рассмотрим создания Hibernate конфигурации и основных параметров с помощью XML в файле hibernate.cfg.xml.
В примере используется база данных H2:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
        <property name="connection.url">jdbc:h2:mem:testdb</property>
        <property name="connection.driver_class">org.h2.Driver</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"></property>

      <!-- List of XML mapping files -->
      <mapping resource = "Car.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Тэг <mapping/> содержит в себе ссылку на файл с маппингом.
Маппинг классов будет рассмотрен ниже.

Persistent Class

Классы Java, чьи объекты или экземпляры будут храниться в таблицах базы данных, называются постоянными классами в Hibernate. Hibernate работает лучше всего, если эти классы следуют некоторым простым правилам, также известным как модель программирования Plain Old Java Object (POJO).
Это значит, что Java класс должен иметь конструктор без параметров, приватные поля с геттерами и сеттарами. А также все классы должны иметь идентификатор, для наиболее удобного идентифицирования объекта в базе данных.

Mapping using files

Объектно-реляционное отображение можно определить в XML файле <classname>.hbm.xml, ссылка на который будет указана в файле конфигураций hibernate.cfg.xml.

Для начала, создадим POJO класс:

@Data
public class Car {
    private Long id;
    private String name;
}

Данный класс соответствует таблице CAR в БД:

create table CAR (
    id int,
    name varchar(255)
);

Теперь создадим файл Car.hbm.xml с маппинг конфигурацией:

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name = "com.home.hibernate.model.Car" table = "CAR">
        <id name = "id" type = "long" column = "id"/>
        <property name = "name" column = "name" type = "string"/>
    </class>
</hibernate-mapping>

Таким образом, мы получили конфигурацию, в которой объект класса Car полностью сопостовим с таблицой CAR из базы данных.

Examples 1.
После создания таблицы CAR, класса Car, а также конфигурационных файлов hibernate.cfg.xml и Car.hbm.xml - рассмотрим несколько методов для работы с БД на реальном примере:

public class Controller {
    private static SessionFactory sessionFactory;

    public void addAndDelete() {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        add(new Car(3L, "BMW")); // добавляем запись в БД
        delete(1L); // удаляем записи из БД
    }

    /**
     * Добавляет запись в БД
     * @param car объект для добавления
     */
    private void add(Car car) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();
            session.save(car);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null)
                transaction.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }
    }

    /**
     * Удаляет запись из БД по ID
     * @param id идентификатор для удаления
     */
    private void delete(Long id) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();
            Car car = session.get(Car.class, id);
            session.delete(car);
            transaction.commit();
        } catch (HibernateException e) {
            if (transaction != null)
                transaction.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
}

Mapping using annotations

В примерах ранее было показано, как Hibernate использует XML-файл конфигурации для маппинга данных из POJO-классов в таблицы базы данных и наоборот.
Hibernate-аннотации - это удобный способ маппинга POJO и таблиц, без использования XML-файлов.

Examples 2.
Создадим таблицу для хранения информации о сотрудниках:

create table DEVELOPER (
    id int primary key auto_increment not null,
    first_name varchar(50),
    last_name varchar(50),
    salary int
);

А теперь создадим класс Developer, который будет сопоставим с созданный таблицей:

@Data // lombok аннотация
@NoArgsConstructor // lombok аннотация
@Entity
@Table(name = "DEVELOPER")
public class Developer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "salary")
    private int salary;

    public Developer(String firstName, String lastName, int salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.salary = salary;
    }
}

А тепрепь используем данную сущность в методе для добавления записи в БД:

public class DeveloperController {
    private static SessionFactory sessionFactory;

    public void add() {
        sessionFactory = new Configuration()
                .configure()
                .addAnnotatedClass(Developer.class) // добавляем нужный класс
                .buildSessionFactory();
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();
            session.save(new Developer("Sarah", "Li", 120000));
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null)
                transaction.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
}

Важно отметить, что в том случае, когда мы используем аннотируемый класс, а не XML-файл конфигураций - при создании фабрики необходимо указывать класс с помощью метода .addAnnotatedClass(Class.class). В остальном же все аналогично ситуации, когда мы используем XML для маппинга.

HQL

Hibernate Query Language (HQL) - это объектно-ориентированный язык запросов, похожий на SQL, но вместо работы с таблицами и столбцами HQL работает с постоянными объектами и их свойствами. HQL-запросы преобразуются Hibernate в обычные SQL-запросы, которые, в свою очередь, выполняют действия с базой данных.

Рассмотрим несколько пример HQL, но для начала создадим сущность Developer для тестирования.
Таблица в базе данных:

create table DEVELOPER (
    id int primary key auto_increment not null,
    name varchar(50),
    salary varchar(50)
);

insert into DEVELOPER (id, name, salary) values (1, 'John', 75000);
insert into DEVELOPER (id, name, salary) values (2, 'Katy', 99000);
insert into DEVELOPER (id, name, salary) values (3, 'Sarah', 69000);

POJO класс:

@Entity
@Table(name = "DEVELOPER")
public class Developer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "name")
    private String name;

    @Column(name = "salary")
    private int salary;

    // constructors, getters, setters...
}

Все команды будут выполнены в рамках транзакции. Код создания сессии и транзакций не будет указан в целях экономии места, его примеры можно найти в начале.

FROM

            Query<Developer> query = session.createQuery("FROM Developer", Developer.class);
            List<Developer> results = query.list();

            for (Developer result : results) {
                System.out.println(result.getName()); // John, Katy, Sarah
            }

Важно отметить, что в параметрах запроса (метод createQuery()) мы указываем наименования класса Developer, а не таблицы DEVELOPER.
Таким образом, в параметры запроса можно передавать полный путь до класса:

Query<Developer> query = session.createQuery("from com.home.hibernate.model.Developer");

SELECT

В HQL можно использовать алиасы, как с ключевым словом AS, так и без него.

            Query<String> query = session.createQuery("SELECT D.name FROM Developer D", String.class);
            List<String> results = query.list();

            for (String result : results) {
                System.out.println(result); // John, Katy, Sarah
            }

WHERE

            Query<Developer> query = session.createQuery("FROM Developer D WHERE D.id = 3", Developer.class);
            List<Developer> results = query.list();

            System.out.println(results); // [Developer{id=3, name='Sarah', salary=69000}]

ORDER BY

           Query<Developer> query = session
                    .createQuery("FROM Developer D WHERE D.salary > 70000 " +
                            "ORDER BY D.id DESC", Developer.class);
            List<Developer> results = query.list();

            for (Developer result : results) {
                System.out.println(result.getName()); // Katy. John
                System.out.println(result.getSalary()); // 99000, 75000
            }

GROUP BY

            Query<Object[]> query = session
                    .createQuery("SELECT name, MAX(salary) FROM Developer GROUP BY name", Object[].class);
            List<Object[]> results = query.list();

            for (Object[] result : results) {
                System.out.println(result[0] + " " + result[1]);
            }

Using Named Parameters

Hibernate поддерживает именованные параметры в своих HQL-запросах. Это позволяет передавать значения в HQL запрос в качестве параметра.

            Query<Developer> query = session.createQuery("FROM Developer WHERE id = :developer_id", Developer.class);
            query.setParameter("developer_id", 1);
            List<Developer> results = query.list();

            System.out.println(results); // [Developer{id=1, name='John', salary=75000}]

UPDATE

Для обновления значений необходимо вызвать метод executeUpdate().

            Query query = session
                    .createQuery("UPDATE Developer SET salary = :dev_salary WHERE id = :dev_id");
            query.setParameter("dev_id", 1);
            query.setParameter("dev_salary", 100_000);
            int result = query.executeUpdate();

            System.out.println("Rows affected = " + result); // Rows affected = 1

DELETE

Для удаления значений необходимо вызвать метод executeUpdate().

            Query query = session.createQuery("DELETE FROM Developer WHERE id = 2");
            int result = query.executeUpdate();

            System.out.println("Rows affected = " + result); // Rows affected = 1

INSERT

HQL поддерживает вставку значений только как результат другого запроса - INSERT INTO ... SELECT ... .
Для вставки значений необходимо вызвать метод executeUpdate().

            Query query = session
                    .createQuery("INSERT INTO Developer(name, salary) " +
                    "SELECT name, salary FROM BackupDeveloper");
            int result = query.executeUpdate();

            System.out.println("Rows affected = " + result); // Rows affected = 1

Aggregate Methods

HQL поддерживает следующие агрегатные функции: avg(),count(),max(),min(),sum().
Поддерживается также и ключевое слово distinct, которое учитывает только уникальные значения.

Следующий код выведет нам число уникальных имен в таблице Developer:

            Query<Long> query = session
                    .createQuery("SELECT count(distinct name) FROM Developer", Long.class);
            List<Long> results = query.list();

            System.out.println(results); // 3

Named Queries

Hibernate имеет поддержку именованых запросов. Такие запросы создаются для каждой модели в отдельности и вызываются по имени.
Рассмотрим на примере. Для нашего POJO класса добавим аннотацию @NamedQueries, которая будет содержать в себе перечень запросов:

@Entity
@Table(name = "DEVELOPER")
@NamedQueries({
        @NamedQuery(name = "Developer.findAll", query = "FROM Developer"),
        @NamedQuery(name = "Developer.findById", query = "SELECT d FROM Developer d WHERE d.id = :id")
})
public class Developer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "name")
    private String name;

    @Column(name = "salary")
    private int salary;

    // constructors, getters, setters...
}

А теперь воспользуемся этими запросами:

            Query<Developer> findAll = session.createNamedQuery("Developer.findAll", Developer.class);
            List<Developer> developers = findAll.list();

            Query<Developer> findById = session.createNamedQuery("Developer.findById", Developer.class)
                    .setParameter("id", 1);
            Developer developer = findById.getSingleResult();

Таким образом, мы получили коллекцию из объектов Developer для запроса findAll и один объект Developer для запроса findById.

Pagination using Query

Существует возможность изменить нумерацию результирующей коллекции, получаемой значения из запроса.
Для этого существуют два метода:

  1. setFirstResult() - этот метод принимает целое число, которое будет указывать первый элемент коллекции. По умолчанию нумерация в коллекции начинается с 0-ого элемента, с помощью этого метода можно установить любое значение начала коллекции.
  2. setMaxResults() - с помощью этого метода можно ограничить число получаемых результатов из запроса.

Рассмотрим этим методы на примере:

            Query<Developer> query = session.createQuery("FROM Developer", Developer.class);
            query.setFirstResult(1); // счетчик коллекции смещается на 1
            query.setMaxResults(1); // максимальный результат - 1
            List<Developer> results = query.list();

            System.out.println(results.size()); // 1
⚠️ **GitHub.com Fallback** ⚠️