1. Введение.

1.1. Что такое JDBC?

JDBC (Java Database Connectivity) – это технология, которая обеспечивает доступ Java API к реляционным базам данных. Благодаря этому, наши Java-приложения могут выполнять SQL-запросы и взаимодействовать с базами данных (далее – БД), которые поддерживают SQL.

JDBC является крайне гибкой и позволяет нам писать приложения, которые не зависят от конкретной платформы и могут взаимодействовать с различными СУБД без каких либо изменение в программном коде.

Какие плюсы даёт нам JDBC:

  • Простая и понятная обработка SQL-запросов

  • Крайне удобна для небольших приложений

  • Простой и понятный синтаксис

Какие минусы JDBC:

  • Сложно использовать и поддерживать в больших проектах

  • Большое количество кода

  • Сложно реализовывать концепцию MVC

1.2. Что такое ORM (Object Relational Mapping)?

1.2.1. Проблема

Когда создается приложение на Java и необходимо сделать так, чтобы приложение получило доступ к информации, которая хранится в БД, нужно понимать крайне важную деталь:

Существует огромная разница между объектной и реляционной моделью

СУБД даёт нам информацию в табличном формате, в то время, как Java даёт нам информацию в виде некоего графа объектов.

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

Класс Developer.java
public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private String salary;

    public Developer() {
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public String getSalary() {
        return salary;
    }

    public void setSalary(String salary) {
        this.salary = salary;
    }
}

И есть таблица в БД, которая также представляет разработчика:

Таблица developer:
CREATE TABLE developer (
    developer_id INT NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(50) DEFAULT NULL,
    last_name VARCHAR(50) DEFAULT NULL,
    specialty VARCHAR(50) DEFAULT NULL,
    SALARY INT DEFAULT NULL,
    PRIMARY KEY (developer_id)
);

Допустим, что после того, как создан и java-класс и таблица в БД, необходимо изменить БД, тогда сразу же возникает проблема. К тому же, когда записываются или читаются данные в/из БД, тогда есть 5 проблем, которые связаны с разницей между объектно-ориентированной моделью и реляционной моделью:

  1. Наследование
    В реляционной модели нет никакого понятия, похожего на наследование, которое является одним из ключевых принципов ООП.

  2. Идентификация
    Для БД есть только одна сущность, по которому объект может быть идентифицирован – это Первичный Ключ (Primary Key). В то время как в Java есть такие вещи, как (entity1 == entity2) и (object1.equals(object2)).

  3. Ассоциации
    В Java используются ссылки на объекты для ассоциации, а в реляционной модели – Внешний Ключ (Foreign Key).

  4. Доступ
    В Java и в реляционной БД абсолютно разные способы получения доступа к объекту.

  5. Инкапсуляция
    Крайне часто, при разработке приложений, придется столкнуться с тем, что объектно-ориентированная модель имеет больше классов, чем таблиц в БД.

Отсюда возникает проблема: как сделать так, чтобы Java приложение получало доступ к БД и могло корректно интерпретировать эту информацию. Другими словами, нам нужно создать связь между Объектом и реляционной сущностью, иначе говоря Объектно-Реляционное-Связывание или же – ORM (Object-Relational Mapping).

1.2.2. Решение

ORM – это техника программирования, которая служит для того, чтобы обеспечивать преобразование данных при их обмене между реляционной базой данных и Java (ну или других языков программирования).

Так какие же преимущества нам даёт ORM в сравнение с JDBC?

  • Позволяет бизнес-методам обращаться не к БД, а Java-классам

  • Ускоряет разработку приложения

  • Основан на JDBC

  • Отделяет SQL-запросы от объектно-ориентированной модели

  • Позволяет не думать о реализации БД

  • Сущности основаны на бизнес-задачах, а не на структуре БД

  • Управление транзакциями

ORM состоит из:

  • API, который реализует базовые операции (СОЗДАНИЕ, ЧТЕНИЕ, ИЗМЕНЕНИЕ, УДАЛЕНИЕ) объектов-моделей.

  • Средства настройки метаданных связывания

  • Техники взаимодействия с транзакциями, которая позволяет реализовать такие функции, как dirty checking, lazy association fetching и т.д.

А самыми распространёнными ORM фреймворком являются:

  • Hibernate

  • Java Object-Oriented Querying (jOOQ)

  • MyBatis

  • EclipseLink

  • TopLink

2. Архитектура.

Hibernate – это ORM фреймворк для Java с открытым исходным кодом. Эта технология является крайне мощной и имеет высокие показатели производительности.

Hibernate создаёт связь между таблицами в базе данных (далее – БД) и Java-классами и наоборот. Это избавляет разработчиков от огромного количества лишней, рутинной работы, в которой крайне легко допустить ошибку и крайне трудно потом её найти.

Схематично это можно изобразить следующим образом:

Java-Hibernate-Database

Какие же преимущества даёт нам использование Hibernate?

  • Обеспечивает простой API для записи и получения Java-объектов в/из БД.

  • Минимизирует доступ к БД, используя стратегии fetching.

  • Не требует сервера приложения.

  • Позволяет не работать с типами данных языка SQL, а иметь дело с привычными типами данных Java.

  • Заботится о создании связей между Java-классами и таблицами БД с помощью XML-файлов не внося изменения в программный код.

  • Если необходимо изменить БД, то достаточно лишь внести изменения в XML-файлы.

  • Hibernate поддерживает все основные СУБД: MySQL, Oracle, PostgreSQL, Microsoft SQL Server Database, HSQL, DB2.

  • Hibernate также может работать в связке с такими технологиями, как Maven и Jakarta EE.

2.1. Архитектура

Приложение, которе использует Hibernate (в крайне поверхностном представлении) имеет такую архитектуру:

Hibernate-Architecture

Если рассмотреть строение самого Hibernate более подробно, что этот же рисунок будет выглядеть следующим образом:

Hibernate-Architecture-Detailed

Hibernate поддерживает такие API, как:

  • JDBC обеспечивает простейший уровень абстракции функциональности для реляционных БД.

  • JTA позволяют Hibernate использовать серверы приложений Jakarta EE.

  • JNDI позволяют Hibernate использовать серверы приложений Jakarta EE.

2.1.1. Transaction

Этот объект представляет собой рабочую единицу работы с БД. В Hibernate транзакции обрабатываются менеджером транзакций.

2.1.2. SessionFactory

Самый важный и самый тяжёлый объект (обычно создаётся в единственном экземпляре, при запуске приложения). Необходима как минимум одна SessionFactory для каждой БД, каждый из которых конфигурируется отдельным конфигурационным файлом.

2.1.3. Session

Сессия используется для получения физического соединения с БД. Обычно, сессия создаётся при необходимости, а после этого закрывается. Это связано с тем, что эти объекты крайне легковесны. Чтобы понять, что это такое, можно сказать, что создание, чтение, изменение и удаление объектов происходит через объект Session.

2.1.4. Query

Этот объект использует HQL или SQL для чтения/записи данных из/в БД. Экземпляр запроса используется для связывания параметров запроса, ограничения количества результатов, которые будут возвращены и для выполнения запроса.

2.1.5. Configuration

Этот объект используется для создания объекта SessionFactory и конфигурирует сам Hibernate с помощью конфигурационного XML-файла, который объясняет, как обрабатывать объект Session.

2.1.6. Criteria

Используется для создания и выполнения объектно-ориентированных запросов для получения объектов.

3. ORM (на примере Hibernate)

3.1. Основные понятия JPA и ORM

Java Persistence API (JPA) — спецификация, предоставляет возможность сохранять в удобном виде объекты в базе данных. JPA описывает систему управления сохранением объектов в таблицы реляционных баз данных в удобном виде. JPA реализует концепцию ORM.

ORMObject-Relational Mapping (объектно-реляционное отображение). Это технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования. Если упростить, то ORM это связь объектов в Java и записей в базе данных. Самой популярной на данный момент такой системой является Hibernate. Так же к ним можно отнести такие программы как EclipseLink, Toplink, их ещё называют JPA-провайдерами.

Непосредственно JPA представлен в пакете javax.persistence.

3.2. DataSource

DataSource — используется для создания соединения с базой данных. Это альтернатива DriverManager используемого в JDBC. В документации сказано, что DataSource использовать предпочтительнее. В частности, одним из преимуществ DataSource является возможность создания пула соединений Database Connection Pool (DBCP).

Существует много библиотек, которые выступают как DataSource. Например, такие, как:

  • Hikari – это реализация DataSource, которая обеспечивает механизм пула соединений. По сравнению с другими реализациями, он обещает быть легким и более производительным. Является самой популярной библиотекой для пула соединений.

  • Tomcat jdbc pool - это реализация DataSource из пакета tomcat-jdbc. Является достаточно производительной.

  • DBCP и C3P0 - устаревшие реализации DataSource, являются однопоточными и очень непроизводительными.

3.3. Entity

Entity - это легковесный хранимый объект бизнес логики (persistent domain object), является основной программной сущностью.

Главная аннотация @Entity позволяет объектам класса быть связанными с базой данных. Чтобы класс мог быть сущностью, к нему предъявляются следующие требования:

  • Entity класс должен быть отмечен аннотацией Entity или описан в XML файле конфигурации JPA.

  • Entity класс должен содержать public или protected конструктор без аргументов (он также может иметь конструкторы с аргументами).

  • Entity класс должен быть классом верхнего уровня.

  • Entity класс не может быть enum или интерфейсом.

  • Entity класс не может быть финальным классом.

  • Entity класс не может содержать final поля или методы, если они участвуют в mapping.

  • Если объект Entity класса будет передаваться по значению как отдельный объект, например через удаленный интерфейс он так же должен реализовывать Serializable интерфейс.

  • Поля Entity класс должны быть напрямую доступны только методам самого Entity класса и не должны быть напрямую доступны другим классам, использующим этот Entity.

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

3.4. Типы ассоциаций между Entity

Существуют следующие типы ассоциаций:

  • @OneToOne - используется тогда, когда сущность одной таблицы связанная с одной сущностью другой таблицы.

  • @OneToMany - используется тогда, когда сущность одной таблицы связанная с несколькими сущностями другой таблицы.

  • @ManyToOne - используется тогда, когда сущности одной таблицы связанны с одной сущностью другой таблицы.

  • @ManyToMany - используется тогда, когда сущности одной таблицы связанны с несколькими сущностями другой таблицы.

3.4.1. @OneToOne

Для того чтобы связать сущности отношением один-к-одному, используется аннотация @OneToOne. В целом, может быть 3 варианта ее использования:

  • Связанные сущности используют одно и то же значение первичного ключа.

  • Внешний ключ определяется полем одной из сущностей (это поле в базе данных должно быть уникальным для имитации отношения один-к-одному).

  • Используется таблица для хранения ссылки между двумя сущностями (ограничение уникальности должно быть установлено на каждом из полей для того, чтобы соответствовать кратности один-к-одному).

Связанные сущности используют одно и то же значение первичного ключа
Сущность User
@Entity
@Table(name = "users")
public class User {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // Поля общие для всех пользователей
}
Сущность Partner
@Entity
@Table(name = "partners")
public class Partner {
    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private User user;

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // Поля данных партнеров системы
}

Аннотация @PrimaryKeyJoinColumn указывает на то, что первичный ключ сущности Partner используется в качестве внешнего ключа для связи с сущностью User.

Связь один-к-одному с использованием явного внешнего ключа
Сущность User
@Entity
@Table(name = "users")
public class User {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="passport_id")
    private Passport passport;
}

Связь в базе данных между таблицами users и passports осуществляется посредством поля passport_id в таблице users. Связанное поле в User объявлено с помощью аннотации @JoinColumn, ее параметр обозначает поле в базе данных, которое будет использоваться для создания связи.

Связь один-к-одному может быть двунаправленной. В двунаправленных отношениях одна из сторон (и только одна) должна быть владельцем и нести ответственность за обновление связанных полей. В случае когда владельцем выступает сущность User. Для того чтобы объявить сторону, которая не несет ответственности за отношения, используется атрибут mappedBy. Он ссылается на имя свойства связи на стороне владельца (passport).

Сущность Passport
@Entity
@Table(name = "passports")
public class Passport {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(mappedBy = "passport")
    private User user;
}

Двунаправленное отношение не создает дополнительного внешнего ключа. Фактически, двунаправленная связь никак не влияет на то, как таблицы связаны друг с другом в базе данных. Просто она позволяет работать с сущностями в обоих направлениях, все также используя единственный внешний ключ. В случае, если на стороне владельца нет связанного поля @JoinColumn, то выполнятся следующие умолчания: в таблице владельца будет создано поле для связи, имя которого собирается из имени связи на стороне владельца, нижнего подчеркивания и имени уникального ключа на зависящей стороне.

Преимуществом однонаправленной связи, является то, что ею легче управлять, потому что необходимо поддерживать только одну сторону. Преимущество же двунаправленной связи заключается в возможности доступа между связанными сущностями в обоих направлениях. Но обычно это приводит к формированию лишних запросов к базе данных, поэтому использовать двунаправленные связи необходимо осторожно.

Связь один-к-одному с использованием таблицы отношений
Сущность User
@Entity
@Table(name = "users")
public class User {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinTable(name = "user_passport",
        joinColumns = @JoinColumn(name="user_id"),
        inverseJoinColumns = @JoinColumn(name="passport_id")
    )

    private Passport passport;
}
Сущность Passport
@Entity
@Table(name = "passports")
public class Passport {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(mappedBy = "passport")
    private User user;
}

В базе данных таблица users связана с passports с помощью таблицы отношений user_passport. Эта таблица содержит внешний ключ user_id, указывающий на таблицу users и внешний ключ passport_id, указывающий на passports. @JoinTable позволяет избежать создания отдельной сущности для таблицы отношений user_passport, и непосредственно связать сущности User и Password между собой. Связь может быть двунаправленной точно так же, как в случае с использованием явного внешнего ключа.

3.4.2. @OneToMany и @ManyToOne

@OneToMany — случай, когда у одного автора может быть несколько книг.

Сущность Author
@Data
@Entity
@Table(name = "author")
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

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

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

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "book_id", unique = true, nullable = false)
    private Set<Book> books;
}

Оно уже является сетом, так как у автора может быть несколько книг. @OneToMany говорит о типе отношения. FetchType.LAZY говорит, что не нужно подгружать весь список книг, если это не указанно в запросе.

В классе Book устанавливаем обратную связь @ManyToOne:

Сущность Book
@Data
@Entity
@Table(name = "book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "print_year", nullable = false)
    private int printYear;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "author_id", nullable = false)
    private Author author;
}

3.4.3. @ManyToMany

Такая зависимость реализовывается через создание дополнительной таблицы. Допустим ситуацию, когда у нескольких книг может быть несколько авторов, а у авторов – несколько книг.

Сущность Author
@Data
@ToString(exclude = "books")
@Entity
@Table(name = "author")
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

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

    @Column(name = "second_name", nullable = false)
    private String secondName;

    @ManyToMany
    @JoinTable(
            name = "author_book_link",
            joinColumns = @JoinColumn(name = "author_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id")
    )
    private Set<Book> books;
}

Для связи сущностей создаётся таблица author_book_link.

@JoinTable — будет связывать атрибут с дополнительной таблицей author_book_link. В ней указываются два атрибута, которые будут указывать на primary keys двух сущностей.

Сущность Book
@Data
@ToString(exclude = "authors")
@Entity
@Table(name = "book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "print_year", nullable = false)
    private int printYear;

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "books")
    private Set<Author> authors;
}

3.5. Стратегии генерации первичного ключа

Один из главных требований к Entity является наличие первичного ключа. В JPA на этот случай предусмотрены механизмы автоматической генерации значений суррогатных ключей, которые включаются аннотацией @GeneratedValue.

JPA поддерживает четыре стратегии генерации ключа:

  • GenerationType.IDENTITY

  • GenerationType.SEQUENCE

  • GenerationType.TABLE

  • GenerationType.AUTO

3.5.1. Стратегия GenerationType.IDENTITY

Такая стратегия работает с базами, у которых есть специальные IDENTITY поля, например с MySQL или DB2. В таких базах данных возможно создавать первичный ключ с автоматическим инкрементом.

Создание таблицы с первичным ключом
CREATE TABLE journal (
    id BIGINT PRIMARY KEY AUTO_INCREMENT
);

3.5.2. Стратегия GenerationType.SEQUENCE

Такая стратегия использует встроенный в базы данных, такие как PostgreSQL или Oracle, механизм генерации последовательных значений. Использование этого генератора требует как создания отдельной sequence в базе данных:

Создание таблицы с первичным ключом
CREATE TABLE journal (
    id BIGINT PRIMARY KEY
);
Создание последовательности
CREATE SEQUENCE book_sequence START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;

Так и задания имени этой последовательности в описании ключа:

Создание последовательности
private class ExampleEntity {
    @Id
    @SequenceGenerator(name = "bookSequence", sequenceName = "book_sequence")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bookSequence")
    @Column(name = "id", nullable = false, updatable = false)
    private Long rowId;
}

3.5.3. Стратегия GenerationType.TABLE

Такая стратегия не зависит от поддержки конкретной базой данных и хранит счётчики значений в отдельной таблице. С одной стороны это более гибкое и настраиваемое решение, с другой стороны более медленное и требующее большей настройки. Вначале требуется создать и проинициализировать таблицу для значений ключей:

Создание таблицы для сохранения ключей
CREATE TABLE sequence_store (
    sequence_name VARCHAR(255) PRIMARY KEY,
    sequence_value BIGINT NOT NULL
);
Entity реализующая стратегию GenerationType.TABLE
private class ExampleEntity {
    @Id
    @TableGenerator(
            name = "seqStore", table = "sequence_store",
            pkColumnName = "sequence_name", pkColumnValue = "journal.id.pk",
            valueColumnName = "sequence_value", initialValue = 1, allocationSize = 1
    )
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "seqStore" )
    @Column(name = "id", nullable = false, updatable = false)
    private Long rowId;
}

3.5.4. Стратегия GenerationType.AUTO

Позволяет автоматически выбрать стратегию в соответствии с используемой базой данных.

3.6. Fetch strategies

В JPA описаны два типа Fetch strategies:

  • LAZY — данные поля будут загружены только во время первого обращения к этому полю.

  • EAGER — данные поля будут загружены сразу, при инициализации корневой сущности.

Каждый тип связи имеет свою Fetch strategy` по умолчанию:

  • @OneToMany: LAZY

  • @ManyToOne: EAGER

  • @ManyToMany: LAZY

  • @OneToOne: EAGER

В JPA есть два типа загрузки FetchType: EAGER and LAZY. FetchType.EAGER загрузка заставляет ORM загружать связанные сущности и коллекции сразу, вместе с корневой сущностью. FetchType.LAZY загрузка означает, что ORM загрузит сущность или коллекцию отложено, при первом обращении к ней из кода.

FetchType в JPA говорит когда связанная сущность или коллекция будет загружена. По умолчанию JPA провайдер загружает связанные коллекции (отношения один-ко-многим и многие-ко-многим) отложено. В большинстве случаев отложенная загрузка — оптимальный вариант. Нет смысла инициализировать все связанные коллекции, если к ним не будет обращений.

3.6.1. Каскадные типы

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

Table 1. Каскадные операции
Параметр Описание

CascadeType.PERSIST

При сохранении экземпляра сущности с помощью метода persist() любой связанный экземпляр сущности также перейдёт в хранимое состояние.

CascadeType.REMOVE

При удалении экземпляра сущности с помощью метода remove() любой связанный экземпляр сущности также будет удален.

CascadeType.DETACH

При отсоединении экземпляра сущности от контекста хранения с помощью detach() любой ассоциированный экземпляр сущности также будет отсоединен.

CascadeType.MERGE

При слиянии временной или отсоединенной сущности с persistence context с помощью merge() для любого связанного временного или отсоединенного экземпляра сущности также будет выполнено слияние.

CascadeType.REFRESH

При изменении экземпляра сущности с помощью refresh() любой связанный экземпляр сущности также будет изменен.

CascadeType.ALL

Сокращенная запись для применения всех способов каскадирования.

3.7. Состояния сущности

Сущности могут находиться в следующих состояниях:

  • transient — объект создан, но при этом ещё не имеет сгенерированных первичных ключей и пока ещё не сохранен в базе данных.

  • managed — объект создан, управляется JPA, имеет сгенерированные первичные ключи.

  • detached — объект был создан, но не управляется (или больше не управляется) JPA.

  • removed — объект создан, управляется JPA, но будет удален после завершения транзакции.

Рассмотрим как операция persist() на Entity объекты каждого из четырех статусов:

  • Если статус у сущности transient, то он меняется на managed, и объект будет сохранен в базу при завершении транзакции или в результате flush() операций.

  • Если статус уже managed, операция игнорируется, однако зависимые Entity могут поменять статус на managed, если у них есть аннотации каскадных изменений.

  • Если статус removed, то он меняется на managed.

  • Если статус detached, будет выкинут Exception сразу или на этапе завершении транзакции.

Рассмотрим как операция remove() на Entity объекты каждого из четырех статусов:

  • Если статус transient, операция игнорируется, однако зависимые Entity могут поменять статус на removed, если у них есть аннотации каскадных изменений и они имели статус managed.

  • Если статус managed, то статус меняется на removed и запись объект в базе данных будет удалена при завершении транзакции (так же произойдут операции remove для всех каскадно зависимых объектов).

  • Если статус removed, то операция игнорируется.

  • Если статус detached, будет выкинут Exception сразу или на этапе завершения транзакции.

Рассмотрим как операция merge() на Entity объекты каждого из четырех статусов:

  • Если статус у сущности detached, то либо данные будет скопированы в существующей managed entity с тем же первичным ключом, либо создан новый managed в который скопируются данные.

  • Если статус transient, то будет создана новый managed entity, в который будут скопированы данные прошлого объекта.

  • Если статус managed, операция игнорируется, однако операция merge() сработает на каскадно зависимые Entity, если их статус не managed.

  • Если статус removed, будет выкинута ошибка сразу или на этапе завершения транзакции.

Рассмотрим как операция refresh() на Entity объекты каждого из четырех статусов:

  • Если статус у сущности managed, то в результате операции будут восстановлены все изменения из базы данных данного Entity, так же произойдет refresh() всех каскадно зависимых объектов.

  • Если статус transient, removed или detached, будет выброшен Exception.

Рассмотрим как операция detach() на Entity объекты каждого из четырех статусов:

  • Если статус у сущности managed или removed, то в результате операции статус Entity (и всех каскадно-зависимых объектов) станет detached.

  • Если статус transient или `detached, то операция игнорируется.

3.8. Удаление сирот

Рассмотрим настройку orphanRemoval, которая касается удаления элементов из коллекции. У нас это будет удаление комментария из списка комментариев топика.

Сущность Comment
@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String text;

    @ManyToOne(fetch = FetchType.LAZY)
    private Topic topic;

    // getters/setters/constructors
}
Сущность Topic
@Entity
public class Topic {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String title;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments=new ArrayList<>();

    public void addComment(Comment comment) {
        comments.add(comment);
        comment.setTopic(this);
    }

    public void removeComment(Comment comment) {
        comments.remove(comment);
        comment.setTopic(null);
    }

    // getters/setters/constructors
}

Следует обратить внимание на метод removeComment(), он удаляет комментарий из коллекции и устанавливает его полю topic значение null.

Чтобы понять смысл настройки orphanRemoval, надо представить, что теоретически может подразумеваться под удалением комментария из списка комментариев топика. Очевидно это означает, что у данного топика больше нет комментария.

Но остается ли он вообще в базе, то есть можно ли его вывести в общем списке комментариев всех топиков? Или же удаляется из базы? За эти два варианта и отвечает orphanRemoval.

3.8.1. orphanRemoval равен true

Если orphanRemoval равен true, то при удалении комментария из списка комментариев топика, он удаляется из базы. Проверим это в тесте:

Метод для тестирования работы orphanRemoval = true
private class RemoveTests {
    @Test
    @DisplayName("если orphanRemoval=true, то при удалении комментария из топика он удаляется из базы")
    public void givenOrphanRemovalTrue_whenRemoveCommentFromTopic_thenItRemovedFromDatabase() {
       Topic topic = topicRepository.getById(-1L);
       topic.removeComment(topic.getComments().get(0));
       Assertions.assertEquals(2, commentRepository.count());
    }
}

Генерируются следующие команды:

select topic0_.id as id1_1_0_, comments1_.id as id1_0_1_,
       topic0_.title as title2_1_0_,
       comments1_.text as text2_0_1_, comments1_.topic_id as topic_id3_0_1_,
       comments1_.topic_id as topic_id3_0_0__, comments1_.id as id1_0_0__
from topic topic0_ inner join comment comments1_
on topic0_.id=comments1_.topic_id
where topic0_.id=?

delete from comment where id=?

Можно заметить оператор delete, он и удаляет комментарий из базы.

3.8.2. orphanRemoval равен false

Если orphanRemoval равен false, то при удалении комментария из списка, в базе комментарий остается. Его внешний ключ обнуляется, и больше комментарий не ссылается на Topic.

Проверим это:

Метод для тестирования работы orphanRemoval = false
private class RemoveTests {
    @Test
    @DisplayName("если orphanRemoval = false, то при удалении комментария из топика остается в базе")
    public void givenOrphanRemovalFalse_whenRemoveCommentFromTopic_thenItRemovedFromDatabase() {
        Topic topic = topicRepository.getById(-1L);
        topic.removeComment(topic.getComments().get(0));
        Assertions.assertEquals(3, commentRepository.count());
    }
}

Генерируются следующие команды:

select topic0_.id as id1_1_0_, comments1_.id as id1_0_1_,
       topic0_.title as title2_1_0_, comments1_.text as text2_0_1_,
       comments1_.topic_id as topic_id3_0_1_, comments1_.topic_id as topic_id3_0_0__,
       comments1_.id as id1_0_0__
from topic topic0_ inner join comment comments1_
on topic0_.id=comments1_.topic_id
where topic0_.id=?

update comment set text=?, topic_id=? where id=?

Здесь происходит обновление таблицы comment: столбцу topic_id присваивается значение NULL. Комментарий остается в базе, просто ни на какой Topic он больше не ссылается. В свою очередь оператор delete отсутствует.

4. Конфигурирование.

Для корректной работы, мы должны передать Hibernate подробную информацию, которая связывает наши Java-классы с таблицами в базе данных (далее – БД). Мы, также, должны указать значения определённых свойств Hibernate.

Обычно, вся эта информация помещена в отдельный файл, либо XML-файл – hibernate.cfg.xml, либо – hibernate.properties.

В этой статье мы рассмотрим конфигурирование приложение с помощью XML-файла hibernate.cfg.xml.

Для начала рассмотрим ключевые свойства, которые должны быть настроены в типичном приложении:

  • hibernate.dialect указывает Hibernate диалект БД. Hibernate, в свою очередь, генерирует необходимые SQL-запросы (например, org.hibernate.dialect.MySQLDialect, если мы используем MySQL).

  • hibernate.connection-driver_class указывает класс JDBC драйвера.

  • hibernate.connection.url указывает URL (ссылку) необходимой нам БД (например, jdbc:mysql://localhost:3306/database).

  • hibernate.connection.username указывает имя пользователя БД (например, root).

  • hibernate.connection.password указывает пароль к БД (например, password).

  • hibernate.connection.pool_size ограничивает количество соединений, которые находятся в пуле соединений Hibernate.

  • hibernate.connection.autocommit указывает режим autocommit для JDBC-соединения.

Давайте рассмотрим пример конфигурационного XML-файла.

Исходные данные:

  • Тип БД: MySQL

  • Имя базы данных: database

  • Имя пользователя: root

  • Пароль: password

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
   <session-factory>
       <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
       <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
       <!-- Assume test is the database name -->
       <property name="hibernate.connection.url">jdbc:mysql://localhost/database</property>
       <property name="hibernate.connection.username">root</property>
       <property name="hibernate.connection.password">password</property>
    </session-factory>
</hibernate-configuration>

5. Сессии.

Session (сессия) используется для получения физического соединения с базой данных (далее – БД). Благодаря тому, что сессия является легковесны объектом, его создают (т.е. открывают сессию) каждый раз, когда возникает необходимость, а потом, когда необходимо, уничтожают (т.е. закрывают сессию). Мы создаём, читаем, редактируем и удаляем объекты с помощью сессий.

Мы стараемся создавать сессии при необходимости, а затем уничтожать их из-за того, что ни не являются потоко-безопасными и не должны быть открыты в течение длительного времени.

Экземпляр класса может находиться в одном из трёх состояний:

  • transient
    Это новый экземпляр устойчивого класса, который не привязан к сессии и ещё не представлен в БД. Он не имеет значения, по которому может быть идентифицирован.

  • persistent
    Мы можем создать переходный экземпляр класса, связав его с сессией. Устойчивый экземпляр класса представлен в БД, а значение идентификатора связано с сессией.

  • detached
    После того, как сессия закрыта, экземпляр класса становится отдельным, независимым экземпляром класса.

В примитивном виде, транзакция выглядит примерно таким образом:

public class Program {
    public static void main(String[] args){
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();

            /**
            * Here we make some work.
            * */

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

В этом примере, в случае исключения, происходит откат (rollback).

В интерфейсе Session определены 23 метода, которые мы можем использовать:

  • Transaction beginTransaction() начинает транзакцию и возвращает объект Transaction.

  • void cancelQuery() отменяет выполнение текущего запроса.

  • void clear() полностью очищает сессию

  • Connection close() заканчивает сессию, освобождает JDBC-соединение и выполняет очистку.

  • Criteria createCriteria(String entityName) создание нового экземпляра Criteria для объекта с указанным именем.

  • Criteria createCriteria(Class persistentClass) создание нового экземпляра Criteria для указанного класса.

  • Serializable getIdentifier(Object object) возвращает идентификатор данной сущности, как сущности, связанной с данной сессией.

  • void update(String entityName, Object object) обновляет экземпляр с идентификатором, указанном в аргументе.

  • void update(Object object) обновляет экземпляр с идентификатором, указанном в аргументе.

  • void saveOrUpdate(Object object) сохраняет или обновляет указанный экземпляр.

  • Serializable save(Object object) сохраняет экземпляр, предварительно назначив сгенерированный идентификатор.

  • boolean isOpen() проверяет открыта ли сессия.

  • boolean isDirty() проверят, есть ли в данной сессии какие-либо изменения, которые должны быть синхронизованы с базой данных (далее – БД).

  • boolean isConnected() проверяет, подключена ли сессия в данный момент.

  • Transaction getTransaction() получает связанную с этой сессией транзакцию.

  • void refresh(Object object) обновляет состояние экземпляра из БД.

  • SessionFactory getSessionFactory() возвращает фабрику сессий (SessionFactory), которая создала данную сессию.

  • Session get(String entityName, Serializable id) возвращает сохранённый экземпляр с указанными именем сущности и идентификатором. Если таких сохранённых экземпляров нет – возвращает null.

  • void delete(String entityName, Object object) удаляет сохранённый экземпляр из БД.

  • void delete(Object object) удаляет сохранённый экземпляр из БД.

  • SQLQuery createSQLQuery(String queryString) создаёт новый экземпляр SQL-запроса (SQLQuery) для данной SQL-строки.

  • Query createQuery(String queryString) создаёт новый экземпляр запроса (Query) для данной HQL-строки.

  • Query createFilter(Object collection, String queryString) создаёт новый экземпляр запроса (Query) для данной коллекции и фильтра-строки.

6. Сохраняемые классы.

Ключевая функция Hibernate заключается в том, что можно взять значения из Java-класса и сохранить их в таблице базы данных (далее – БД). С помощью конфигурационных файлов указывают Hibernate как извлечь данные из класса и соединить с определённым столбцами в таблице БД.

Если нужно, чтобы экземпляры (объекты) Java-класса в будущем сохранялись в таблице БД, то их называют “сохраняемые классы” (persistent class). Для того чтобы сделать работу с Hibernate максимально удобной и эффективной, следует использовать программную модель Простых Старых Java Объектов (Plain Old Java ObjectPOJO).

Существуют определённые требования к POJO классам. Вот самые главные из них:

  • Все классы должны иметь ID для простой идентификации объектов в БД и в Hibernate. Это поле класса соединяется с первичным ключом (primary key) таблицы БД.

  • Все POJO – классы должны иметь конструктор по умолчанию (т.е. без параметров).

  • Все поля POJO – классов должны иметь модификатор доступа private иметь набор Getters и Setters в стиле JavaBean.

  • POJO – классы не должны содержать бизнес-логику.

Классы POJO называют для того, чтобы подчеркнуть тот факт, что эти объекты являются экземплярами обычных Java-классов.

Developer.java
package net.proselyte.hibernate.pojo;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(int id, String firstName, String lastName, String specialty, int experience) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }

    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", specialty='" + specialty + '\'' +
                ", experience=" + experience +
                '}';
    }
}

7. Соединяющие файлы.

Чаще всего, когда имеется дело с ORM фреймворком, связи между объектами и таблицами в базе данных (далее – БД) указываются в XML – файле.
POJO – класс Developer.java

package net.proselyte.hibernate.pojo;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(int id, String firstName, String lastName, String specialty, int experience) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", specialty='" + specialty + '\'' +
                ", experience=" + experience +
                '}';
    }
}

Теперь создадим таблицу в БД.

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS (
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

На данный момент есть две независимых друг от друга сущности: POJO – класс Developer.java и таблица в БД HIBERNATE_DEVELOPERS. Для того чтобы связать их друг с другом и получить возможность сохранять значения полей класса, необходимо объяснить, как именно это делать Hibernate framework. Чтобы это сделать, создаём конфигурационной XML – файл Developer.hbm.xml :

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "https://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="net.proselyte.hibernate.pojo.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer's details.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="first_name" type="string"/>
        <property name="lastName" column="last_name" type="string"/>
        <property name="specialty" column="last_name" type="string"/>
        <property name="experience" column="salary" type="int"/>
    </class>
</hibernate-mapping>

На данный момент нельзя сказать, что приложение готово, но есть часть необходимых конфигураций:

  • <hibernate-mapping>
    Это ключевой тег, который должен быть в каждом XML – файле для связывания (mapping). Внутри этого тега конфигурируем связи.

  • <class>
    Тег <class> используется для того, чтобы указать связь между POJO – классов и таблицей в БД. Имя класса указывается с помощью свойства name, имя таблицы в БД – с помощью свойства table.

  • <meta>
    Опциональный (необязательный) тег, внутри которого можно добавить описание класса.

  • <id>
    Тег <id> связывает уникальный идентификатор ID в POJO – классе и первичный ключ (primary key) в таблице БД. Свойство name соединяет поле класса со свойством column, которое указывает нам колонку в таблице БД. Свойство type определяет тип связывания (mapping) и используется для конвертации типа данных Java в тип данных SQL.

  • <generator>
    Этот тег внутри тега <id> используется для того, что генерировать первичные ключи автоматически. Если указать это свойство native, как в примере, приведённом выше, то Hibernate сам выберет алгоритм (identity, hilo, sequence) в зависимости от возможностей БД.

  • <property>
    Этот тег используется для того, чтобы связать (map) конкретное поле POJO – класса с конкретной колонкой в таблице БД. Свойство name указывает поле в классе, в то время как свойство column указывает на колонку в таблице БД. Свойство type указывает тип связывания (mapping) и конвертирует тип данных Java в тип данных SQL.

Существуют также и другие теги, которые могут быть использованы в конфигурационном XML – файле и не упоминались до этого времени.

8. Пример простого приложения.

8.1. Шаг 1. Создать POJO – класс Developer.java

Developer.java
package net.proselyte.hibernate.example;

public class Developer {
    protected int id;
    protected String firstName;
    protected String lastName;
    protected String specialty;
    protected int experience;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(int id, String firstName, String lastName, String specialty, int experience) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", specialty='" + specialty + '\'' +
                ", experience=" + experience +
                '}';
    }
}

8.2. Шаг 2. Создание таблицы в базе данных (далее – БД).

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

8.3. Шаг 3. создание конфигурационного файла hibernate.cfg.xml

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume ИМЯ ВАШЕЙ БД is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БАЗЫ_ДАННЫХ</property>
        <property name="hibernate.connection.username">ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Четвёртый шаг – создание конфигурационного XML – файла Developer.hbm.xml

Developer.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="net.proselyte.hibernate.example.model.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer's details.
        </meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>
</hibernate-mapping>

И финальный шаг – создание основного класса приложения DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.example;

import net.proselyte.hibernate.example.model.Developer;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.List;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding developer's records to the DB");
        /**
         *  Adding developer's records to the database (DB)
         */
        developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2);
        developerRunner.addDeveloper("Some", "Developer", "C++ Developer", 2);
        developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4);

        System.out.println("List of developers");
        /**
         * List developers
         */
        List developers = developerRunner.listDevelopers();
        for (Developer developer : developers) {
            System.out.println(developer);
        }
        System.out.println("===================================");
        System.out.println("Removing Some Developer and updating Proselyte");
        /**
         * Update and Remove developers
         */
        developerRunner.updateDeveloper(10, 3);
        developerRunner.removeDeveloper(11);

        System.out.println("Final list of developers");
        /**
         * List developers
         */
        developers = developerRunner.listDevelopers();
        for (Developer developer : developers) {
            System.out.println(developer);
        }
        System.out.println("===================================");

    }

    public void addDeveloper(String firstName, String lastName, String specialty, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        session.save(developer);
        transaction.commit();
        session.close();
    }

    public List listDevelopers() {
        Session session = this.sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();

        transaction.commit();
        session.close();
        return developers;
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = this.sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = this.sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если все настройки были сделаны правильно, то получаем следующий результат работы программы:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7538 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.example.DeveloperRunner
Feb 18, 2016 2:30:00 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 18, 2016 2:30:00 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 18, 2016 2:30:00 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 18, 2016 2:30:00 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 18, 2016 2:30:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 18, 2016 2:30:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 18, 2016 2:30:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 18, 2016 2:30:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 18, 2016 2:30:02 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Thu Feb 18 14:30:02 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 18, 2016 2:30:02 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding developer's records to the DB
List of developers
Feb 18, 2016 2:30:03 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 10
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Developer:
id: 11
First Name: Some
Last Name: Developer
Specialty: C++ Developer
Experience: 2

Developer:
id: 12
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

===================================
Removing Some Developer and updating Proselyte
Final list of developers
Developer:
id: 10
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Developer:
id: 12
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

===================================

9. Виды связей. List Mapping.

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

List связывается (mapped) с помощью элемента <list> и инициализируется с помощью java.util.ArrayList.

9.1. Шаг 1. Создаём 2 таблицы в базе данных (далее – БД)

CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

HIBERNATE_PROJECTS

create table HIBERNATE_PROJECTS (
   id INT NOT NULL auto_increment,
   PROJECT_NAME VARCHAR(50) default NULL,
   COMPANY VARCHAR(50) default NULL,
   idx INT default NULL,
   DEVELOPER_ID INT default NULL,
   PRIMARY KEY (id)
);

9.2. Шаг 2. Создаём POJO-классы

Developer.java

package net.proselyte.hibernate.mappings.list;

import java.util.List;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private List projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public List getProjects() {
        return projects;
    }

    public void setProjects(List projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}

Project.java

package net.proselyte.hibernate.mappings.list;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

9.3. Шаг 3. Создаём конфигурационные файлы

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>

        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost/ИМЯ ВАШЕЙ БД
        </property>
        <property name="hibernate.connection.username">
            ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ
        </property>
        <property name="hibernate.connection.password">
            ВАШ ПАРОЛЬ
        </property>

        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Developer.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="net.proselyte.hibernate.mappings.list.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer's details.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <list name="projects" cascade="all">
            <key column="DEVELOPER_ID"/>
            <list-index column="idx"/>
            <one-to-many class="net.proselyte.hibernate.mappings.list.Project"/>
        </list>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.list.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">
            This class contains project's records.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

9.4. Шаг 4. Создаём гласс DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.mappings.list;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.ArrayList;
import java.util.List;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the set of projects.");

        ArrayList projects1 = new ArrayList();
        projects1.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.add(new Project("SkybleLib", "SkybleSoft"));

        ArrayList projects2 = new ArrayList();
        projects2.add(new Project("Some Project", "Some Company"));
        projects2.add(new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        Integer developerId2 = developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, ArrayList projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            List projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

9.5. Результат работы программы:

/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7532 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.list.DeveloperRunner
Feb 20, 2016 6:22:36 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 20, 2016 6:22:36 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 20, 2016 6:22:36 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 20, 2016 6:22:36 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 20, 2016 6:22:38 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 20, 2016 6:22:38 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 20, 2016 6:22:38 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 20, 2016 6:22:38 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 20, 2016 6:22:38 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Sat Feb 20 18:22:38 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 20, 2016 6:22:39 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the set of projects.
Adding developer's records to the DB
List of developers
Feb 20, 2016 6:22:40 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 63
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 13
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 14
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 64
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 15
Project Name: Some Project
Company Name: Some Company

Project:
id: 16
Project Name: One more Project
Company Name: One more Company


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 63
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 13
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 14
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 64
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 15
Project Name: Some Project
Company Name: Some Company

Project:
id: 16
Project Name: One more Project
Company Name: One more Company


================

===================================
Feb 20, 2016 6:22:40 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

В этой статье рассмотрены основы связывания (mapping) с использованием коллекции List.

10. Виды связей. Collection Mapping

Коллекция Java, которая хранит элементы без какой-либо последовательности и позволяет хранение одинаковых элементов, называется Bag.

Collection связывается (mapped) с помощью элемента <bag> и инициализируется с помощью java.util.ArrayList.

Пример:

10.1. Шаг 1. Создадим две табилцы в базе данных (далее – БД)

CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

HIBERNATE_PROJECTS

create table HIBERNATE_PROJECTS (
   id INT NOT NULL auto_increment,
   PROJECT_NAME VARCHAR(50) default NULL,
   COMPANY VARCHAR(50) default NULL,
   DEVELOPER_ID INT default NULL,
   PRIMARY KEY (id)
);

10.2. Шаг 2. Создадим POJO – классы

Developer.java

package net.proselyte.hibernate.mappings.list;

import java.util.Collection;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Collection projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Collection getProjects() {
        return projects;
    }

    public void setProjects(Collection projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}

Project.java

package net.proselyte.hibernate.mappings.list;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

10.3. Шаг 3. Создадим конфигурационные файлы

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>

        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost/ИМЯ ВАШЕЙ БАЗЫ ДАННЫХ
        </property>
        <property name="hibernate.connection.username">
            ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ
        </property>
        <property name="hibernate.connection.password">
            ВАШ ПАРОЛЬ
        </property>

        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Deveoper.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="net.proselyte.hibernate.mappings.list.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer's details.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <bag name="projects" cascade="all">
            <key column="DEVELOPER_ID"/>
            <one-to-many class="net.proselyte.hibernate.mappings.list.Project"/>
        </bag>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.list.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">
            This class contains project's records.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

10.4. Шаг 4. Создадим класс DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.mappings.list;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.ArrayList;
import java.util.Collection;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the collection of projects.");

        ArrayList projects1 = new ArrayList();
        projects1.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.add(new Project("SkybleLib", "SkybleSoft"));

        ArrayList projects2 = new ArrayList();
        projects2.add(new Project("Some Project", "Some Company"));
        projects2.add(new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        Integer developerId2 = developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, ArrayList projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Collection developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            Collection projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

10.5. Результат работы программы:

/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7533 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.list.DeveloperRunner
Feb 20, 2016 7:45:08 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 20, 2016 7:45:08 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 20, 2016 7:45:08 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 20, 2016 7:45:09 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 20, 2016 7:45:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 20, 2016 7:45:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 20, 2016 7:45:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 20, 2016 7:45:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 20, 2016 7:45:10 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Sat Feb 20 19:45:10 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 20, 2016 7:45:11 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the collection of projects.
Adding developer's records to the DB
List of developers
Feb 20, 2016 7:45:11 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 67
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 5
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 6
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 68
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 7
Project Name: Some Project
Company Name: Some Company

Project:
id: 8
Project Name: One more Project
Company Name: One more Company


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 67
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 5
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 6
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 68
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 7
Project Name: Some Project
Company Name: Some Company

Project:
id: 8
Project Name: One more Project
Company Name: One more Company


================

===================================
Feb 20, 2016 7:45:11 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

В этой статье рассмотрен пример связывания с использованием Collection.

11. Виды связей. Set Mapping.

Set (множество) – это коллекция, которая не содержит повторяющихся элементов (только уникальные). Для обеспечения уникальности значений, объекты, содержащиеся в Set, должны реализовывать метода equals() и hashCode(), используя которые, Java-приложение сможет определить являются 2 элемента одинаковыми или нет.

Set связывается (mapped) с помощью элемента <set> и инициализируется с помощью java.util.HashSet.

Другими словами, используем Set, если в коллекции будут храниться только уникальные элементы.

Для понимания, как это реализовывается на практике, рассмотрим пример небольшого приложения.

11.1. Шаг 1. Создадим две таблицы в нашей БД:

HIBERNATE_DEVELOPERS

CREATE TABLE HIBERNATE_DEVELOPERS (
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

HIBERNATE_PROJECTS

CREATE TABLE HIBERNATE_PROJECTS (
   id INT NOT NULL AUTO_INCREMENT,
   PROJECT_NAME VARCHAR(50) default NULL,
   COMPANY VARCHAR(50) default NULL,
   DEVELOPER_ID INT default NULL,
   PRIMARY KEY (id)
);

Будем использовать отношение one-to-many между таблицами HIBERNATE_DEVELOPERS и HIBERNATE_PROJECTS.

11.2. Шаг 2. Создаём класс Developer.java

Developer.java
package net.proselyte.hibernate.mappings.set;

import java.util.Set;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Set projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Set getProjects() {
        return projects;
    }

    public void setProjects(Set projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}

11.3. Шаг 3. Создаём класс Project.java

Project.java
package net.proselyte.hibernate.mappings.set;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    /**
     * Methods equals and hashCode for comparing objects
     */

    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (!this.getClass().equals(object.getClass())) {
            return false;
        }

        Project object2 = (Project) object;
        if ((this.id == object2.getId()) && (this.projectName == object2.getProjectName()) && (this.companyName == object2.getCompanyName())) {
            return true;
        }
        return false;
    }

    public int hasCode() {
        int code = 0;
        code = (id + projectName + companyName).hashCode();
        return code;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

11.4. Шаг 4. Создаём конфигурационный файл hibernate.cfg.xml

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume ИМЯ ВАШЕЙ БД is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/PROSELYTE_TUTORIAL</property>
        <property name="hibernate.connection.username">ИМЯ ВАШЕЙ БАЗЫ ДАННЫХ</property>
        <property name="hibernate.connection.password">ВАШ ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

11.5. Шаг 5. Создаём конфигурационный файл Developer.hbm.xml

Developer.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="net.proselyte.hibernate.mappings.set.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains the developer's detail.</meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <set name="projects" cascade="all">
            <key column="DEVELOPER_ID"/>
            <one-to-many class="net.proselyte.hibernate.mappings.set.Project"/>
        </set>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.set.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">This class contains the projects records.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

11.6. Шаг 6. Создаём и запускаем класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.mappings.set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the set of projects.");
        HashSet projects1 = new HashSet();
        projects1.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.add(new Project("SkybleLib", "SkybleSoft"));

        HashSet projects2 = new HashSet();
        projects2.add(new Project("Some Project", "Some Company"));
        projects2.add(new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(31, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public void addDeveloper(String firstName, String lastName, String specialty, int experience, Set projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        session.save(developer);
        transaction.commit();
        session.close();
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            Set projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано верно, то в результате получим следующее:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7545 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.set.DeveloperRunner
Feb 18, 2016 9:33:06 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 18, 2016 9:33:06 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 18, 2016 9:33:06 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 18, 2016 9:33:06 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Thu Feb 18 21:33:08 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 18, 2016 9:33:08 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the set of projects.
Adding developer's records to the DB
List of developers
Feb 18, 2016 9:33:09 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 29
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 33
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 34
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 30
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 35
Project Name: Some Project
Company Name: Some Company

Project:
id: 36
Project Name: One more Project
Company Name: One more Company


================

Developer:
id: 31
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 37
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 38
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 32
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 40
Project Name: One more Project
Company Name: One more Company

Project:
id: 39
Project Name: Some Project
Company Name: Some Company


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 29
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 33
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 34
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 30
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 35
Project Name: Some Project
Company Name: Some Company

Project:
id: 36
Project Name: One more Project
Company Name: One more Company


================

Developer:
id: 31
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 37
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 38
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 32
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 39
Project Name: Some Project
Company Name: Some Company

Project:
id: 40
Project Name: One more Project
Company Name: One more Company


================

===================================
Feb 18, 2016 9:33:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

12. Виды связей. SortedSet Mapping.

SortedSet – это коллекция, которая содержит только уникальные элементы и эти элементы упорядочены в естественном порядке или с помощью специального класса-компаратора.

SortedSet связывается (mapped) с помощью элемента <set> в таблице связей и инициализируется с помощью java.util.TreeSet. Свойство sort определяет, будет ли происходить сортировка в натуральном порядке или с помощью класса-компаратора, созданного разработчиком. Если мы выбираем натуральный порядок, то элементы будут упорядочены в восходящем порядке.

Другими словами, мы используем SortedSet, если в коллекции будут храниться только уникальные элементы и нам необходимо их определённым образом упорядочить.

Для понимания, как это реализовывается на практике, рассмотрим пример небольшого приложения.

12.1. Шаг 1. Создадим две таблицы в нашей БД:

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);
HIBERNATE_PROJECTS
create table HIBERNATE_PROJECTS (
   id INT NOT NULL AUTO_INCREMENT,
   PROJECT_NAME VARCHAR(50) default NULL,
   COMPANY VARCHAR(50) default NULL,
   DEVELOPER_ID INT default NULL,
   PRIMARY KEY (id)
);

Мы будем использовать отношение one-to-many между таблицами HIBERNATE_DEVELOPERS и HIBERNATE_PROJECTS.

12.2. Шаг 2. Создаём класс Developer.java

Developer.java
package net.proselyte.hibernate.mappings.sortedset;

import java.util.SortedSet;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private SortedSet projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public SortedSet getProjects() {
        return projects;
    }

    public void setProjects(SortedSet projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}

12.3. Шаг 3. Создём класс Project.java

Project.java
package net.proselyte.hibernate.mappings.sortedset;

public class Project implements Comparable {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public int compareTo(Project that) {
        final int BEFORE = -1;
        final int AFTER = 1;

        if (that == null) {
            return BEFORE;
        }

        Comparable thisProject = this.getProjectName();
        Comparable thatProject = that.getProjectName();

        if (thisProject == null) {
            return AFTER;
        } else if (thatProject == null) {
            return BEFORE;
        } else {
            return thisProject.compareTo(thatProject);
        }
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

12.4. Шаг 4. Создаём класс-компаратор ProjectsComparator.java

ProjectsComparator.java
package net.proselyte.hibernate.mappings.sortedset;

import java.util.Comparator;

public class ProjectsComparator implements Comparator {
    @Override
    public int compare(Project o1, Project o2) {
        final int BEFORE = -1;
        final int AFTER = 1;

        if (o2 == null) {
            return BEFORE * -1;
        }

        Comparable thisProject = o1.getProjectName();
        Comparable thatProject = o2.getProjectName();

        if (thisProject == null) {
            return AFTER * 1;
        } else if (thatProject == null) {
            return BEFORE * -1;
        } else {
            return thisProject.compareTo(thatProject) * -1;
        }
    }
}

12.5. Шаг 5. Создаём конфигурационный файл hibernate.cfg.xml

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ ВАШЕЙ БАЗЫ ДАННЫХ</property>
        <property name="hibernate.connection.username">ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

12.6. Шаг 6. Создаём конфигурационный файл Developer.hbm.xml

Developer.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="net.proselyte.hibernate.mappings.sortedset.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains the developer's detail.</meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <set name="projects" cascade="all" sort="net.proselyte.hibernate.mappings.sortedset.ProjectsComparator">
            <key column="DEVELOPER_ID"/>
            <one-to-many class="net.proselyte.hibernate.mappings.sortedset.Project"/>
        </set>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.sortedset.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">This class contains the projects records.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

12.7. Шаг 7. Создаём и запускаем класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.mappings.sortedset;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.HashSet;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the set of projects.");
        TreeSet projects1 = new TreeSet();
        projects1.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.add(new Project("SkybleLib", "SkybleSoft"));

        TreeSet projects2 = new TreeSet();
        projects2.add(new Project("Some Project", "Some Company"));
        projects2.add(new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(35, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public void addDeveloper(String firstName, String lastName, String specialty, int experience, SortedSet projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        session.save(developer);
        transaction.commit();
        session.close();
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            SortedSet projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано верно, то в результате мы получим следующее:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7532 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.sortedset.DeveloperRunner
Feb 18, 2016 10:01:13 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 18, 2016 10:01:13 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 18, 2016 10:01:13 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 18, 2016 10:01:13 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 18, 2016 10:01:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 18, 2016 10:01:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 18, 2016 10:01:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 18, 2016 10:01:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 18, 2016 10:01:15 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Thu Feb 18 22:01:15 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 18, 2016 10:01:15 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the set of projects.
Adding developer's records to the DB
List of developers
Feb 18, 2016 10:01:16 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 35
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 46
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 45
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 36
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 48
Project Name: Some Project
Company Name: Some Company

Project:
id: 47
Project Name: One more Project
Company Name: One more Company


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 35
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 46
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 45
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 36
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 48
Project Name: Some Project
Company Name: Some Company

Project:
id: 47
Project Name: One more Project
Company Name: One more Company


================

===================================
Feb 18, 2016 10:01:16 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

13. Виды связей. Map Mapping.

Коллекция Java, хранящая пары “ключ – значение” и в которой допускается хранение только уникальных элементов, называется Map. Map связывается (mapped) с помощью элемента <map> и (в случае неупорядоченной Map) инициализируется с помощью java.util.HashMap.

Для понимания того, как это работает на практике, рассмотрим пример простого приложения.

13.1. Шаг 1. Создадим две таблицы в бахе данных (далее – БД)

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS (
    ID INT NOT NULL AUTO_INCREMENT,
    FIRST_NAME VARCHAR(50) DEFAULT NULL,
    LAST_NAME VARCHAR(50) DEFAULT NULL,
    SPECIALTY VARCHAR(50) DEFAULT NULL,
    EXPERIENCE INT DEFAULT NULL,
    PRIMARY KEY(ID)
);
HIBERNATE_PROJECTS
create table HIBERNATE_PROJECTS (
    id INT NOT NULL auto_increment,
    SPHERE VARCHAR(50) default NULL,
    PROJECT_NAME VARCHAR(50) default NULL,
    COMPANY VARCHAR(50) default NULL,
    DEVELOPER_ID INT default NULL,
    PRIMARY KEY (id)
);

Мы будем использовать отношение one-to-many между таблицами HIBERNATE_DEVELOPERS и HIBERNATE_PROJECTS.

13.2. Шаг 2. Создадим POJO – классы

Developer.java
package net.proselyte.hibernate.mappings.map;

import java.util.Map;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Map<String, Project> projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Map<String, Project> getProjects() {
        return projects;
    }

    public void setProjects(Map<String, Project> projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}
Project.java
package net.proselyte.hibernate.mappings.map;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

13.3. Шаг 3. Создадим конфигурационные файлы

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ ВАШЕЙ БД</property>
        <property name="hibernate.connection.username">ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
Developer.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="net.proselyte.hibernate.mappings.map.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer's details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <map name="projects" cascade="all">
            <key column="DEVELOPER_ID"/>
            <index column="SPHERE" type="string" />
            <one-to-many class="net.proselyte.hibernate.mappings.map.Project"/>
        </map>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.map.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">This class contains project's records.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

13.4. Шаг 4. Создаём класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.mappings.map;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the collection of projects.");

        HashMap<String, Project> projects1 = new HashMap<String, Project>();
        projects1.put("Computer Science", new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.put("Aviation", new Project("SkybleLib", "SkybleSoft"));

        HashMap<String, Project> projects2 = new HashMap<String, Project>();
        projects2.put("Some Sphere", new Project("Some Project", "Some Company"));
        projects2.put("E-commerce", new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        Integer developerId2 = developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, HashMap<String, Project> projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            Map<String, Project> projects = developer.getProjects();
            System.out.println(developer.getFirstName() + "'s projects:\n");
            System.out.println(projects);
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы мы получим, примерно, следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7540 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.map.DeveloperRunner
Feb 20, 2016 8:30:38 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 20, 2016 8:30:38 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 20, 2016 8:30:38 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 20, 2016 8:30:38 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 20, 2016 8:30:40 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 20, 2016 8:30:40 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 20, 2016 8:30:40 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 20, 2016 8:30:40 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 20, 2016 8:30:40 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Sat Feb 20 20:30:40 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 20, 2016 8:30:40 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the collection of projects.
Adding developer's records to the DB
List of developers
Feb 20, 2016 8:30:41 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 79
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Proselyte's projects:

{Aviation=Project:
id: 21
Project Name: SkybleLib
Company Name: SkybleSoft
, Computer Science=Project:
id: 22
Project Name: Proselyte Tutorial
Company Name: proselyte.net
}

================

Developer:
id: 80
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Peter's projects:

{Some Sphere=Project:
id: 23
Project Name: Some Project
Company Name: Some Company
, E-commerce=Project:
id: 24
Project Name: One more Project
Company Name: One more Company
}

================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 79
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Proselyte's projects:

{Aviation=Project:
id: 21
Project Name: SkybleLib
Company Name: SkybleSoft
, Computer Science=Project:
id: 22
Project Name: Proselyte Tutorial
Company Name: proselyte.net
}

================

Developer:
id: 80
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Peter's projects:

{Some Sphere=Project:
id: 23
Project Name: Some Project
Company Name: Some Company
, E-commerce=Project:
id: 24
Project Name: One more Project
Company Name: One more Company
}

================

===================================
Feb 20, 2016 8:30:41 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

14. Виды связей. SortedMap Mapping.

Коллекция Java, хранящая только уникальные (разрешено хранение только уникальных элементов) пары “ключ – значение” и в которой все элементы упорядочены, называется SortedMap.

SortedMap связывается (mapped) с помощью элемента <map> и инициализируется с помощью java.util.TreeMap.

Для понимания того, как это работает на практике, рассмотрим пример простого приложения.

14.1. Шаг 1. Создадим две таблицы в бахе данных (далее – БД)

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS (
    ID INT NOT NULL AUTO_INCREMENT,
    FIRST_NAME VARCHAR(50) DEFAULT NULL,
    LAST_NAME VARCHAR(50) DEFAULT NULL,
    SPECIALTY VARCHAR(50) DEFAULT NULL,
    EXPERIENCE INT DEFAULT NULL,
    PRIMARY KEY(ID)
);
HIBERNATE_PROJECTS
create table HIBERNATE_PROJECTS (
    id INT NOT NULL auto_increment,
    SPHERE VARCHAR(50) default NULL,
    PROJECT_NAME VARCHAR(50) default NULL,
    COMPANY VARCHAR(50) default NULL,
    DEVELOPER_ID INT default NULL,
    PRIMARY KEY (id)
);

Мы будем использовать отношение one-to-many между таблицами HIBERNATE_DEVELOPERS и HIBERNATE_PROJECTS.

14.2. Шаг 2. Создадим POJO – классы

Developer.java
package net.proselyte.hibernate.mappings.sortedmap;

import java.util.SortedMap;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private SortedMap<String, Project> projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public SortedMap<String, Project> getProjects() {
        return projects;
    }

    public void setProjects(SortedMap<String, Project> projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}
Project.java
package net.proselyte.hibernate.mappings.sortedmap;

public class Project implements Comparable {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public int compareTo(String that) {
        final int BEFORE = -1;
        final int AFTER = 1;

        if (that == null) {
            return BEFORE;
        }

        Comparable thisProject = this;
        Comparable thatProject = that;

        if (thisProject == null) {
            return AFTER;
        } else if (thatProject == null) {
            return BEFORE;
        } else {
            return thisProject.compareTo(thatProject);
        }
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}
ProjectComparator.java
package net.proselyte.hibernate.mappings.sortedmap;

import java.util.Comparator;

public class ProjectComparator implements Comparator {
    public int compare(String o1, String o2) {
        final int BEFORE = -1;
        final int AFTER = 1;

        /* To reverse the sorting order, multiple by -1 */
        if (o2 == null) {
            return BEFORE * -1;
        }

        Comparable thisProject = o1;
        Comparable thatProject = o2;

        if (thisProject == null) {
            return AFTER * 1;
        } else if (thatProject == null) {
            return BEFORE * -1;
        } else {
            return thisProject.compareTo(thatProject) * -1;
        }
    }
}

14.3. Шаг 3. Создадим конфигурационные файлы

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ ВАШЕЙ БД</property>
        <property name="hibernate.connection.username">ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Developer.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="net.proselyte.hibernate.mappings.sortedmap.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer's details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <map name="projects" cascade="all" sort = "net.proselyte.hibernate.mappings.sortedmap.ProjectComparator">
            <key column="DEVELOPER_ID"/>
            <index column="SPHERE" type="string" />
            <one-to-many class="net.proselyte.hibernate.mappings.sortedmap.Project"/>
        </map>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.sortedmap.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">This class contains project's records.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

14.4. Шаг 4. Создаём класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.mappings.sortedmap;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the TreeMaps of projects.");

        TreeMap<String, Project> projects1 = new TreeMap<String, Project>();
        projects1.put("Computer Science", new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.put("Aviation", new Project("SkybleLib", "SkybleSoft"));

        TreeMap<String, Project> projects2 = new TreeMap<String, Project>();
        projects2.put("Some Sphere", new Project("Some Project", "Some Company"));
        projects2.put("E-commerce", new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        Integer developerId2 = developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, TreeMap<String, Project> projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            SortedMap<String, Project> projects = developer.getProjects();
            System.out.println(developer.getFirstName() + "'s projects:\n");
            System.out.println(projects);
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы мы получим, примерно, следующий результат:

/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7534 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/3.6.9.Final/hibernate-core-3.6.9.Final.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.6/antlr-2.7.6.jar:/home/proselyte/.m2/repository/commons-collections/commons-collections/3.1/commons-collections-3.1.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-commons-annotations/3.2.0.Final/hibernate-commons-annotations-3.2.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.0-api/1.0.1.Final/hibernate-jpa-2.0-api-1.0.1.Final.jar:/home/proselyte/.m2/repository/javax/transaction/jta/1.1/jta-1.1.jar:/home/proselyte/.m2/repository/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.sortedmap.DeveloperRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Sat Feb 20 21:45:50 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Creating the TreeMaps of projects.
Adding developer's records to the DB
List of developers
Developer:
id: 81
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Proselyte's projects:

{Computer Science=Project:
id: 26
Project Name: Proselyte Tutorial
Company Name: proselyte.net
, Aviation=Project:
id: 25
Project Name: SkybleLib
Company Name: SkybleSoft
}

================

Developer:
id: 82
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Peter's projects:

{Some Sphere=Project:
id: 28
Project Name: Some Project
Company Name: Some Company
, E-commerce=Project:
id: 27
Project Name: One more Project
Company Name: One more Company
}

================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 81
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Proselyte's projects:

{Computer Science=Project:
id: 26
Project Name: Proselyte Tutorial
Company Name: proselyte.net
, Aviation=Project:
id: 25
Project Name: SkybleLib
Company Name: SkybleSoft
}

================

Developer:
id: 82
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Peter's projects:

{Some Sphere=Project:
id: 28
Project Name: Some Project
Company Name: Some Company
, E-commerce=Project:
id: 27
Project Name: One more Project
Company Name: One more Company
}

================

===================================

15. Виды связей. Many-to-One.

Наиболее распространённым типом ассоциации является тип Many-to-One, при котором один объект может быть ассоциирован с несколькими другими объектами. Например, мы можем ассоциировать несколько разработчиков с одной компанией, в которой они работают.

Для понимания того, как это работает на практике, рассмотрим пример простого приложения.

15.1. Шаг 1. Создадим две таблицы в нашей базе данных (далее – БД)

HIBERNATE_DEVELOPERS

CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   COMPANY INT NOT NULL,
   PRIMARY KEY(ID)
);

HIBERNATE_COMPANIES

CREATE TABLE HIBERNATE_COMPANIES (
    id INT NOT NULL AUTO_INCREMENT,
    COMPANY_NAME VARCHAR(100) default NULL,
    PRIMARY KEY (id)
);

15.2. Шаг 2. Создадим POJO – классы

Developer.java

package net.proselyte.hibernate.association.manytoone;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Company company;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience, Company company) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
        this.company = company;
    }

    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n" +
                "Company: " + company;
    }
}

Company.java

package net.proselyte.hibernate.association.manytoone;

public class Company {
    private int id;
    private String companyName;

    public Company() {
    }

    public Company(String companyName) {
        this.companyName = companyName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @Override
    public String toString() {
        return "Company:" +
                "\nCompany ID: " + id +
                "\nCompany Name: " + companyName + "\n";
    }
}

15.3. Шаг 3. Создадим конфигурационные файлы

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>

        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БД
        </property>
        <property name="hibernate.connection.username">
            ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ
        </property>
        <property name="hibernate.connection.password">
            ВАШ_ПАРОЛЬ
        </property>

        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Developer.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="net.proselyte.hibernate.association.manytoone.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer's detail.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
        <many-to-one name="company" column="COMPANY" not-null="true"
                     class="net.proselyte.hibernate.association.manytoone.Company"/>
    </class>

    <class name="net.proselyte.hibernate.association.manytoone.Company" table="HIBERNATE_COMPANIES">
        <meta attribute="class-description">
            This class contains company details.
        </meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <property name="companyName" column="COMPANY_NAME" type="string"/>
    </class>

</hibernate-mapping>

15.4. Шаг 4. Создадим класс DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.association.manytoone;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.List;


public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();
        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding company record...");
        Company company = developerRunner.addCompany("Proselyte.net");

        System.out.println("Creating developer's records...");
        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, company);
        Integer developerId2 = developerRunner.addDeveloper("Peter", "Programmer", "C++ Developer", 2, company);

        System.out.println("List of Developers: ");
        developerRunner.listDevelopers();

        System.out.println("Updating experience of Proselyte to 3 years and removing Peter...");
        developerRunner.updateDeveloper(developerId1, 3);
        developerRunner.removeDeveloper(developerId2);

        System.out.println("Final list of Developers: ");
        developerRunner.listDevelopers();

        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, Company company) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience, company);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public Company addCompany(String companyName) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Company company = null;

        transaction = session.beginTransaction();
        company = new Company(companyName);
        session.save(company);
        transaction.commit();
        session.close();
        return company;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы мы получим, примерно, следующий результат:

/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7537 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.association.manytoone.DeveloperRunner
Feb 22, 2016 11:54:37 AM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 22, 2016 11:54:37 AM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 22, 2016 11:54:37 AM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 22, 2016 11:54:37 AM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 22, 2016 11:54:38 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 22, 2016 11:54:38 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 22, 2016 11:54:38 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 22, 2016 11:54:38 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 22, 2016 11:54:38 AM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Mon Feb 22 11:54:38 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 22, 2016 11:54:39 AM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding company record...
Creating developer's records...
List of Developers:
Feb 22, 2016 11:54:40 AM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2
Company: Company:
Company ID: 1
Company Name: Proselyte.net


================

Developer:
id: 2
First Name: Peter
Last Name: Programmer
Specialty: C++ Developer
Experience: 2
Company: Company:
Company ID: 1
Company Name: Proselyte.net


================

Updating experience of Proselyte to 3 years and removing Peter...
Final list of Developers:
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3
Company: Company:
Company ID: 1
Company Name: Proselyte.net


================

Feb 22, 2016 11:54:40 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

16. Виды связей. One-to-One.

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

Для понимания того, как это работает на практике рассмотрим пример простого приложения.

16.1. Шаг 1. Создадим две таблицы в нашей базе данных (далее – БД)

HIBERNATE_DEVELOPERS

CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   CONTACT INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

HIBERNATE_CONTACTS

CREATE TABLE HIBERNATE_CONTACTS (
   ID INT NOT NULL AUTO_INCREMENT,
   ADDRESS VARCHAR(100) default NULL,
   CITY VARCHAR(50) default NULL,
   PHONE_NUMBER VARCHAR(20) default NULL,
   EMAIL VARCHAR(100) default NULL,
   PRIMARY KEY (id)
);

16.2. Шаг 2. Создаём POJO-классы

Developer.java

package net.proselyte.hibernate.association.onetoone;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Contact contact;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience, Contact contact) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
        this.contact = contact;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Contact getContact() {
        return contact;
    }

    public void setContact(Contact contact) {
        this.contact = contact;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n" +
                "Contact Information:\n" + contact;
    }
}

Contact.java

package net.proselyte.hibernate.association.onetoone;

public class Contact {
    private int id;
    private String address;
    private String city;
    private String phoneNumber;
    private String email;

    public Contact() {
    }

    public Contact(String address, String city, String phoneNumber, String email) {
        this.address = address;
        this.city = city;
        this.phoneNumber = phoneNumber;
        this.email = email;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Address: " + address +
                "\nCity: " + city +
                "\nPhone Number: " + phoneNumber +
                "\nemail: " + email + "\n";
    }
}

16.3. Шаг 3. Создаём конфигурационные файлы

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>

        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost/ИМЯ ВАШЕЙ БАЗЫ ДАННЫХ
        </property>
        <property name="hibernate.connection.username">
            ВАШЕ ИМЯ ПОЛЬЗОВАТЕЛЯ
        </property>
        <property name="hibernate.connection.password">
            ВАШ ПАРОЛЬ
        </property>

        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Developer.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="net.proselyte.hibernate.association.onetoone.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer's detail.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
        <many-to-one name="contact" column="CONTACT" not-null="true" unique="true"
                    class="net.proselyte.hibernate.association.onetoone.Contact" />
    </class>

    <class name="net.proselyte.hibernate.association.onetoone.Contact" table="HIBERNATE_CONTACTS">
        <meta attribute="class-description">
            This class contains the address detail.
        </meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <property name="address" column="ADDRESS" type="string"/>
        <property name="city" column="CITY" type="string"/>
        <property name="phoneNumber" column="PHONE_NUMBER" type="string"/>
        <property name="email" column="EMAIL" type="string"/>
    </class>

</hibernate-mapping>

16.4. Шаг 4. Создаём класс DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.association.onetoone;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.List;


public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();
        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding contact information...");
        Contact contact1 = developerRunner.addContact("Some address", "Dnipropetrovsk", "+380501234567", "proselytear@yahoo.com");
        Contact contact2 = developerRunner.addContact("One More address", "Kyiv", "+380667654321", "peter@mail.com");

        System.out.println("Creating developer's records...");
        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, contact1);
        Integer developerId2 = developerRunner.addDeveloper("Peter", "Programmer", "C++ Developer", 2, contact2);

        System.out.println("List of Developers: ");
        developerRunner.listDevelopers();

        System.out.println("Updating experience of Proselyte to 3 years and removing Peter...");
        developerRunner.updateDeveloper(developerId1, 3);
        developerRunner.removeDeveloper(developerId2);

        System.out.println("Final list of Developers: ");
        developerRunner.listDevelopers();

        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, Contact contact) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience, contact);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public Contact addContact(String address, String city, String phoneNumber, String email) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Contact contact = new Contact(address, city, phoneNumber, email);
        session.save(contact);
        transaction.commit();
        session.close();

        return contact;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано верно, то в результате работы программы мы получим, примерно, следующий результат:

/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7540 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.association.onetoone.DeveloperRunner
Feb 21, 2016 7:02:14 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 21, 2016 7:02:14 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 21, 2016 7:02:14 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 21, 2016 7:02:15 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 21, 2016 7:02:16 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 21, 2016 7:02:16 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 21, 2016 7:02:16 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 21, 2016 7:02:16 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 21, 2016 7:02:16 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Sun Feb 21 19:02:16 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 21, 2016 7:02:16 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding contact information...
Creating developer's records...
List of Developers:
Feb 21, 2016 7:02:17 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 11
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2
Contact Information:
Address: Some address
City: Dnipropetrovsk
Phone Number: +380501234567
email: proselytear@yahoo.com


================

Developer:
id: 12
First Name: Peter
Last Name: Programmer
Specialty: C++ Developer
Experience: 2
Contact Information:
Address: One More address
City: Kyiv
Phone Number: +380667654321
email: peter@mail.com


================

Updating experience of Proselyte to 3 years and removing Peter...
Final list of Developers:
Developer:
id: 11
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3
Contact Information:
Address: Some address
City: Dnipropetrovsk
Phone Number: +380501234567
email: proselytear@yahoo.com


================

Feb 21, 2016 7:02:17 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

17. Виды связей. One-to-Many.

Связь One-to-Many (один ко многим) используется в том случае, когда один объект, в качестве одного из аттрибутов, имеет коллекцию других объектов.

Например, разработчик учавствовал в более, чем одном проекте, поэтому лучше использовать связь One-to-Many.

Для понимания того, как это работает на практике, рассмотрим пример простого приложения.

17.1. Шаг 1. Создадим две таблицы в базе данных (далее – БД)

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS(
    ID INT NOT NULL AUTO_INCREMENT,
    FIRST_NAME VARCHAR(50) DEFAULT NULL,
    LAST_NAME VARCHAR(50) DEFAULT NULL,
    SPECIALTY VARCHAR(50) DEFAULT NULL,
    EXPERIENCE INT DEFAULT NULL,
    PRIMARY KEY(ID)
);
mysql
create table HIBERNATE_PROJECTS (
    id INT NOT NULL AUTO_INCREMENT,
    PROJECT_NAME VARCHAR(50) default NULL,
    COMPANY VARCHAR(50) default NULL,
    DEVELOPER_ID INT default NULL,
    PRIMARY KEY (id)
);

17.2. Шаг 2. Создадим POJO – классы

Developer.java
package net.proselyte.hibernate.association.onetomany;

import java.util.Set;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Set projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Set getProjects() {
        return projects;
    }

    public void setProjects(Set projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}
Project.java
package net.proselyte.hibernate.association.onetomany;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    /**
     * Methods equals and hashCode for comparing objects
     */

    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (!this.getClass().equals(object.getClass())) {
            return false;
        }

        Project object2 = (Project) object;
        if ((this.id == object2.getId())
                && (this.projectName == object2.getProjectName())
                && (this.companyName == object2.getCompanyName())) {
            return true;
        }
        return false;
    }

    public int hasCode() {
        int code = 0;
        code = (id + projectName + companyName).hashCode();
        return code;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

17.3. Шаг 3. Создадим конфигурационные файлы

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ ВАШЕЙ БАЗЫ ДАННЫХ</property>
        <property name="hibernate.connection.username">ВАШЕ ИЯ ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
Developer.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="net.proselyte.hibernate.association.onetomany.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer's details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <set name="projects" cascade="all">
            <key column="DEVELOPER_ID"/>
            <one-to-many class="net.proselyte.hibernate.association.onetomany.Project"/>
        </set>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.association.onetomany.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">This class contains project's records.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

17.4. Шаг 4. Создадим класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.association.onetomany;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the set of projects.");
        HashSet projects1 = new HashSet();
        projects1.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.add(new Project("SkybleLib", "SkybleSoft"));

        HashSet projects2 = new HashSet();
        projects2.add(new Project("Some Project", "Some Company"));
        projects2.add(new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        int developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        int developerId2 = developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public int addDeveloper(String firstName, String lastName, String specialty, int experience, Set projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        int developerId = (int)session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            Set projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано верно, то в результате работы программы мы получим примерно следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7533 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.association.onetomany.DeveloperRunner
Feb 21, 2016 4:47:08 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 21, 2016 4:47:08 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 21, 2016 4:47:08 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 21, 2016 4:47:08 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 21, 2016 4:47:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 21, 2016 4:47:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 21, 2016 4:47:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 21, 2016 4:47:10 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 21, 2016 4:47:10 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Sun Feb 21 16:47:10 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 21, 2016 4:47:10 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the set of projects.
Adding developer's records to the DB
List of developers
Feb 21, 2016 4:47:11 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 5
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 10
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 9
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 6
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 12
Project Name: Some Project
Company Name: Some Company

Project:
id: 11
Project Name: One more Project
Company Name: One more Company


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 5
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 9
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 10
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 6
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Feb 21, 2016 4:47:11 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Project:
id: 12
Project Name: Some Project
Company Name: Some Company

Project:
id: 11
Project Name: One more Project
Company Name: One more Company


================

===================================

18. Виды связей. Many-to-Many.

Представим ситуацию, когда у нас есть несколько проектов, в каждом из которых участвуют несколько разработчиков. Для такого варианта подходит ассоциация Many-to-Many.

Для понимания того, как это работает на практике рассмотрим пример простого приложения.

18.1. Шаг 1. Создадим три таблицы в нашей базе данных (далее – БД).

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS(
    ID INT NOT NULL AUTO_INCREMENT,
    FIRST_NAME VARCHAR(50) DEFAULT NULL,
    LAST_NAME VARCHAR(50) DEFAULT NULL,
    SPECIALTY VARCHAR(50) DEFAULT NULL,
    EXPERIENCE INT DEFAULT NULL,
    PRIMARY KEY(ID)
);
HIBERNATE_PROJECTS
CREATE TABLE HIBERNATE_PROJECTS (
    id INT NOT NULL AUTO_INCREMENT,
    PROJECT_NAME VARCHAR(50) default NULL,
    COMPANY VARCHAR(50) default NULL,
    PRIMARY KEY (id)
);
HIBERNATE_DEV_PROJECTS (промежуточная таблица, которая содержит идентификаторы разработчика и проекта)
CREATE TABLE HIBERNATE_DEV_PROJECTS (
    DEVELOPER_ID INT NOT NULL,
    PROJECT_ID INT NOT NULL,
    PRIMARY KEY (DEVELOPER_ID, PROJECT_ID)
);

18.2. Шаг 2. Создадим POJO – классы

Developer.java
package net.proselyte.hibernate.association.manytomany;

import java.util.Set;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Set projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Set getProjects() {
        return projects;
    }

    public void setProjects(Set projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}
Project.java
package net.proselyte.hibernate.association.manytomany;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    /**
     * Methods equals and hashCode for comparing objects
     */

    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (!this.getClass().equals(object.getClass())) {
            return false;
        }

        Project object2 = (Project) object;
        if ((this.id == object2.getId()) && (this.projectName == object2.getProjectName()) && (this.companyName == object2.getCompanyName())) {
            return true;
        }
        return false;
    }

    public int hasCode() {
        int code = 0;
        code = (id + projectName + companyName).hashCode();
        return code;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}

18.3. Шаг 3. Создаём конфигурационные файлы

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver </property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БАЗЫ_ДАННЫХ</property>
        <property name="hibernate.connection.username">ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ_ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
Developer.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="net.proselyte.hibernate.association.manytomany.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <set name="projects" cascade="save-update" table="HIBERNATE_DEV_PROJECTS">
            <key column="DEVELOPER_ID" />
            <many-to-many column="PROJECT_ID" class="net.proselyte.hibernate.association.manytomany.Project"/>
        </set>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.association.manytomany.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">This class contains project details.</meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

18.4. Шаг 4. Создаём класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.association.manytomany;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the set of projects.");
        HashSet projects = new HashSet();
        projects.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects.add(new Project("SkybleLib", "SkybleSoft"));

        System.out.println("Adding developer's records to the DB");

        int developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects);
        int developerId2 = developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public int addDeveloper(String firstName, String lastName, String specialty, int experience, Set projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        int developerId = (int)session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            Set projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы мы получим, примерно, следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7533 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.association.manytomany.DeveloperRunner
Feb 22, 2016 12:29:48 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 22, 2016 12:29:48 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 22, 2016 12:29:48 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 22, 2016 12:29:48 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 22, 2016 12:29:50 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 22, 2016 12:29:50 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 22, 2016 12:29:50 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 22, 2016 12:29:50 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 22, 2016 12:29:50 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Mon Feb 22 12:29:50 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 22, 2016 12:29:50 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the set of projects.
Adding developer's records to the DB
List of developers
Feb 22, 2016 12:29:51 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3


================

Developer:
id: 3
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 2
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 1
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 4
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 2
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 1
Project Name: SkybleLib
Company Name: SkybleSoft


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3


================

Developer:
id: 3
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Feb 22, 2016 12:29:51 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
Project:
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
id: 1
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 2
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 4
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 1
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 2
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

===================================

19. Виды связей. Component Mapping.

Часто мы встречаемся с ситуацией, когда один из нащих классов содержит в качестве своего поля ссылку на другой объект. Ранее мы уже рассматривали такую ситуацию, но применяли другие способы связывания (mapping).

В этой статье мы рассмотрим элемент под названием <component>.

Давайте рассмотрим пример простого приложения.

Пример:

Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.

  1. Создадим таблицу в нашей базе данных (далее – БД).

    HIBERNATE_DEVELOPERS

    CREATE TABLE HIBERNATE_DEVELOPERS (
    id INT NOT  NULL auto_increment,
    FIRST_NAME  VARCHAR(20) default NULL,
    LAST_NAME  VARCHAR(20) default NULL,
    SPECIALTY  VARCHAR(40) default NULL ,
    EXPERIENCE    INT  default NULL,
    COMPANY_NAME  VARCHAR(40) default NULL,
    PRIMARY KEY (id)
    );
  2. Создаём POJO – классы

    Developer.java

    package net.proselyte.hibernate.component;
    
    public class Developer {
        private int id;
        private String firstName;
        private String lastName;
        private String specialty;
        private int experience;
        private Company company;
    
        /**
         * Default Constructor
         */
        public Developer() {
        }
    
        /**
         * Plain constructor
         */
        public Developer(String firstName, String lastName, String specialty, int experience, Company company) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.specialty = specialty;
            this.experience = experience;
            this.company = company;
        }
    
        /**
         * Getters and Setters
         */
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getSpecialty() {
            return specialty;
        }
    
        public void setSpecialty(String specialty) {
            this.specialty = specialty;
        }
    
        public int getExperience() {
            return experience;
        }
    
        public void setExperience(int experience) {
            this.experience = experience;
        }
    
        public Company getCompany() {
            return company;
        }
    
        public void setCompany(Company company) {
            this.company = company;
        }
    
        /**
         * toString method (optional)
         */
        @Override
        public String toString() {
            return "Developer:\n" +
                    "id: " + id +
                    "\nFirst Name: " + firstName + "\n" +
                    "Last Name: " + lastName + "\n" +
                    "Specialty: " + specialty + "\n" +
                    "Experience: " + experience + "\n" +
                    "Company: " + company;
        }
    }

    Company.java

    package net.proselyte.hibernate.component;
    
    public class Company {
        private int id;
        private String companyName;
    
        public Company() {
        }
    
        public Company(String companyName) {
            this.companyName = companyName;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getCompanyName() {
            return companyName;
        }
    
        public void setCompanyName(String companyName) {
            this.companyName = companyName;
        }
    
        @Override
        public String toString() {
            return "Company:" +
                    "\nCompany ID: " + id +
                    "\nCompany Name: " + companyName + "\n";
        }
    }
  3. Создадим конфигурационные файлы

    hibernate.cfg.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE hibernate-configuration SYSTEM
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.dialect">
                org.hibernate.dialect.MySQLDialect
            </property>
            <property name="hibernate.connection.driver_class">
                com.mysql.jdbc.Driver
            </property>
    
            <!-- Assume PROSELYTE_TUTORIAL is the database name -->
            <property name="hibernate.connection.url">
                jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БАЗЫ_ДАННЫХ
            </property>
            <property name="hibernate.connection.username">
                ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ
            </property>
            <property name="hibernate.connection.password">
                ВАШ_ПАРОЛЬ
            </property>
    
            <!-- List of XML mapping files -->
            <mapping resource="Developer.hbm.xml"/>
    
        </session-factory>
    </hibernate-configuration>

    Developer.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="net.proselyte.hibernate.component.Developer" table="HIBERNATE_DEVELOPERS">
            <meta attribute="class-description">
                This class contains developer details.
            </meta>
            <id name="id" type="int" column="id">
                <generator class="native"/>
            </id>
            <component name="company" class="net.proselyte.hibernate.component.Company">
                <property name="companyName" column="COMPANY_NAME"/>
            </component>
            <property name="firstName" column="FIRST_NAME" type="string"/>
            <property name="lastName" column="LAST_NAME" type="string"/>
            <property name="specialty" column="SPECIALTY" type="string"/>
            <property name="experience" column="EXPERIENCE" type="int"/>
        </class>
    
        <class name="net.proselyte.hibernate.component.Company" table="HIBERNATE_COMPANIES">
            <meta attribute="class-description">
                This class contains company details.
            </meta>
            <id name="id" type="int" column="ID">
                <generator class="native"/>
            </id>
            <property name="companyName" column="COMPANY_NAME" type="string"/>
        </class>
    
    </hibernate-mapping>
  4. Создаём класс DeveloperRunner.java

    DeveloperRunner.java

    package net.proselyte.hibernate.component;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    
    import java.util.List;
    
    
    public class DeveloperRunner {
        private static SessionFactory sessionFactory;
    
        public static void main(String[] args) {
            sessionFactory = new Configuration().configure().buildSessionFactory();
            DeveloperRunner developerRunner = new DeveloperRunner();
    
            System.out.println("Adding company1 record...");
            Company company1 = developerRunner.addCompany("Proselyte.net");
            Company company2 = developerRunner.addCompany("Some Company");
            System.out.println("Creating developer's records...");
            Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, company1);
            Integer developerId2 = developerRunner.addDeveloper("Peter", "Programmer", "C++ Developer", 2, company2);
    
            System.out.println("List of Developers: ");
            developerRunner.listDevelopers();
    
            System.out.println("Updating experience of Proselyte to 3 years and removing Peter...");
            developerRunner.updateDeveloper(developerId1, 3);
            developerRunner.removeDeveloper(developerId2);
    
            System.out.println("Final list of Developers: ");
            developerRunner.listDevelopers();
    
            sessionFactory.close();
        }
    
        public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, Company company) {
            Session session = sessionFactory.openSession();
            Transaction transaction = null;
            Integer developerId = null;
    
            transaction = session.beginTransaction();
            Developer developer = new Developer(firstName, lastName, specialty, experience, company);
            developerId = (Integer) session.save(developer);
            transaction.commit();
            session.close();
            return developerId;
        }
    
        public Company addCompany(String companyName) {
            Session session = sessionFactory.openSession();
            Transaction transaction = null;
            Company company = null;
    
            transaction = session.beginTransaction();
            company = new Company(companyName);
            session.save(company);
            transaction.commit();
            session.close();
            return company;
        }
    
        public void listDevelopers() {
            Session session = sessionFactory.openSession();
            Transaction transaction = null;
    
            transaction = session.beginTransaction();
            List developers = session.createQuery("FROM Developer").list();
            for (Developer developer : developers) {
                System.out.println(developer);
                System.out.println("\n================\n");
            }
            session.close();
        }
    
        public void updateDeveloper(int developerId, int experience) {
            Session session = sessionFactory.openSession();
            Transaction transaction = null;
    
            transaction = session.beginTransaction();
            Developer developer = (Developer) session.get(Developer.class, developerId);
            developer.setExperience(experience);
            session.update(developer);
            transaction.commit();
            session.close();
        }
    
        public void removeDeveloper(int developerId) {
            Session session = sessionFactory.openSession();
            Transaction transaction = null;
    
            transaction = session.beginTransaction();
            Developer developer = (Developer) session.get(Developer.class, developerId);
            session.delete(developer);
            transaction.commit();
            session.close();
        }
    }

Если всё было сделано правильно, то в результате работы программы мы получим, примерно, следующий результат:

/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7533 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.component.DeveloperRunner
Feb 22, 2016 8:00:43 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 22, 2016 8:00:43 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 22, 2016 8:00:43 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 22, 2016 8:00:43 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 22, 2016 8:00:45 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 22, 2016 8:00:45 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 22, 2016 8:00:45 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 22, 2016 8:00:45 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 22, 2016 8:00:45 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Mon Feb 22 20:00:45 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 22, 2016 8:00:45 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding company1 record...
Creating developer's records...
List of Developers:
Feb 22, 2016 8:00:46 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2
Company: Company:
Company ID: 0
Company Name: Proselyte.net


================

Developer:
id: 2
First Name: Peter
Last Name: Programmer
Specialty: C++ Developer
Experience: 2
Company: Company:
Company ID: 0
Company Name: Some Company


================

Updating experience of Proselyte to 3 years and removing Peter...
Final list of Developers:
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3
Company: Company:
Company ID: 0
Company Name: Proselyte.net


================

Feb 22, 2016 8:00:47 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

В этой статье мы рассмотрели пример использования Component Mapping с помощью элемента <component>.

20. Аннотации.

Для mapping можно использовать конфигурационные XML – файлы для конфигурирования Hibernate. В этих XML – файлах указываются для Hibernate с какой таблицей в нашей базе данных (далее – БД) необходимо связать тот или иной POJO – класс и к каким колонкам относятся те или иные поля в этом классе. Но в Hibernate предусмотрена возможность конфигурирования приложения с помощью аннотаций.

Аннотации являются мощным инструментом для предоставления метаданных, а также намного нагляднее при чтении нашего кода другим разработчиком.

Обязательными аннотациями являются следующие:

  • @Entity
    Эта аннотация указывает Hibernate, что данный класс является сущностью (entity bean). Такой класс должен иметь конструктор по-умолчанию (пустой конструктор).

  • @Table
    С помощью этой аннотации мы говорим Hibernate, с какой именно таблицей необходимо связать (map) данный класс. Аннотация @Table имеет различные аттрибуты, с помощью которых мы можем указать имя таблицы, каталог, БД и уникальность столбцов в таблице БД.

  • @Id
    С помощью аннотации @Id мы указываем первичный ключ (Primary Key) данного класса.

  • @GeneratedValue
    Эта аннотация используется вместе с аннотацией @Id и определяет такие параметры, как strategy и generator.

  • @Column
    Аннотация @Column определяет к какому столбцу в таблице БД относится конкретное поле класса (аттрибут класса).

Наиболее часто используемые аттрибуты аннотации @Column такие:

  • name- Указывает имя столбца в таблице

  • unique Определяет, должно ли быть данное значение уникальным

  • nullable Определяет, может ли данное поле быть NULL, или нет.

  • length Указывает, какой размер столбца (например количество символов, при использовании String).

Для понимания того, как это работает на практике, рассмотрим пример небольшого приложения.

20.1. Шаг 1. Создадим таблицу в нашей БД.

HIBERNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS(
    ID INT NOT NULL AUTO_INCREMENT,
    FIRST_NAME VARCHAR(50) DEFAULT NULL,
    LAST_NAME VARCHAR(50) DEFAULT NULL,
    SPECIALTY VARCHAR(50) DEFAULT NULL,
    EXPERIENCE INT DEFAULT NULL,
    PRIMARY KEY(ID)
);

20.2. Шаг 2. Создадим POJO – класс.

Developer.java
package net.proselyte.hibernate.annotations;

import javax.persistence.*;

@Entity
@Table(name = "HIBERNATE_DEVELOPERS")
public class Developer {
    @Id
    @GeneratedValue (strategy = GenerationType.AUTO)
    @Column (name = "id")
    private int id;
    @Column (name = "FIRST_NAME")
    private String firstName;
    @Column (name = "LAST_NAME")
    private String lastName;
    @Column (name = "SPECIALTY")
    private String specialty;
    @Column (name = "EXPERIENCE")
    private int experience;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }

    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}

20.3. Шаг 3. Создадим конфигурационные файлы

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БД</property>
        <property name="hibernate.connection.username">ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ_ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
Developer.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="net.proselyte.hibernate.annotations.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>
</hibernate-mapping>

20.4. Шаг 4. Создадим класс DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.annotations;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.List;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding Developer's records to the database");
        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2);
        Integer developerId2 = developerRunner.addDeveloper("Some", "Developer", "C++ Developer", 2);
        Integer developerId3 = developerRunner.addDeveloper("Peter", "Team Lead", "Java Team Lead", 6);

        System.out.println("List of Developers:");
        developerRunner.listDevelopers();

        System.out.println("Removing \'Some Developer\' and updating \'Proselyte Developer\''s experience:");
        developerRunner.removeDeveloper(developerId2);
        developerRunner.updateDeveloper(developerId1, 3);

        System.out.println("Final list of Developers:");
        developerRunner.listDevelopers();
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы вы получите, примерно, следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7536 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.annotations.DeveloperRunner
Feb 22, 2016 9:34:01 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 22, 2016 9:34:01 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 22, 2016 9:34:01 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 22, 2016 9:34:01 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 22, 2016 9:34:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 22, 2016 9:34:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 22, 2016 9:34:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 22, 2016 9:34:02 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 22, 2016 9:34:02 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Mon Feb 22 21:34:03 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 22, 2016 9:34:03 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding Developer's records to the database
List of Developers:
Feb 22, 2016 9:34:04 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2


================

Developer:
id: 2
First Name: Some
Last Name: Developer
Specialty: C++ Developer
Experience: 2


================

Developer:
id: 3
First Name: Peter
Last Name: Team Lead
Specialty: Java Team Lead
Experience: 6


================

Removing 'Some Developer' and updating 'Proselyte Developer''s experience:
Final list of Developers:
Developer:
id: 1
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3


================

Developer:
id: 3
First Name: Peter
Last Name: Team Lead
Specialty: Java Team Lead
Experience: 6


================

Так будет выглядеть наша таблица HIBERNATE_DEVELOPERS в БД

ID FIRST_NAME LAST_NAME SPECIALTY EXPERIENCE

1

Proselyte

Developer

Java Developer

3

3

Peter

Team Lead

Java Team Lead

6

21. Язык запросов Hibernate (HQL).

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

Отличие между HQL и SQL состоит в том, что SQL работает таблицами в базе данных (далее – БД) и их столбцами, а HQL – с сохраняемыми объектами (Persistent Objects) и их полями (аттрибутами класса).

Hibernate транслирует HQL – запросы в понятные для БД SQL – запросы, которые и выполняют необходимые нам действия в БД.

Мы также имеем возможность использовать обычные SQL – запросы в Hibernate используя Native SQL, но использование HQL является более предпочтительным.

Давайте рассмотрим основные ключевые слова языка HQL.

21.1. FROM

Если мы хотим загрузить в память наши сохраняемые объекты, то мы будем использовать ключевое слово FROM. Вот пример его использования:

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery("FROM Developer");
        List developers = query.list();
    }
}

Developer – это POJO – класс Developer.java, который ассоциирован с таблицей в БД.

21.2. INSERT

Мы используем ключевое слово INSERT, в том случае, если хотим добавить запись в таблицу нашей БД.

public class Program {
    // ...

    public void executeHqlQuery() {
      Query query = session.createQuery("INSERT INTO Developer (firstName, lastName, specialty, experience)");
    }
}

21.3. UPDATE

Ключевое слово UPDATE используется для обновления одного или нескольких полей объекта.

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery("UPDATE Developer SET experience =: experience WHERE id =: developerId");
        query.setParameter("experience", 3);
    }
}

21.4. DELETE

Это ключевое слово используется для удаления одного или нескольких объектов.

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery("DELETE FROM Developer WHERE id = :developerId");
        query.setParameter("developerId", 1);
    }
}

21.5. SELECT

Если мы хотим получить запись из таблицы нашей БД, то мы должны использовать ключевое слово SELECT.

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery("SELECT D.lastName FROM Developer D");
        List developers = query.list();
    }
}

21.6. AS

В предыдущем примере использовалась запись формы Developer D. С использованием опционального ключевого слова AS это будет выглядеть так:

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery("FROM Developer AS D");
        List developers = query.list();
    }
}

21.7. WHERE

В том случае, если мы хотим получить объекты, которые соответствуют определённым параметрам, то мы должны использовать ключевое слово WHERE.

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery("FROM Developer D WHERE D.id = 1");
        List developer = query.list();
    }
}

21.8. ORDER BY

Для того чтобы отсортировать список объектов, полученных в результате запроса, мы должны применить ключевое слово ORDER BY. Нам необходимо указать параметр, по которому список будет отсортирован и тип сортировки – по возрастанию (ASC) или по убыванию (DESC).

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query =
        session.createQuery("FROM Developer D WHERE experience > 3 ORDER BY D.experience DESC");
    }
}

21.9. GROUP BY

С помощью ключевого слова GROUP BY мы можем группировать данные, полученные из БД по какому-либо признаку.

public class Program {
    // ...

    public void executeHqlQuery() {
        Query query = session.createQuery(
                "SELECT MAX(D.experience), D.lastName, D.specialty FROM Developer D GROUP BY D.lastName");
        List developers = query.list();
    }
}

21.10. Методы агрегации

Язык запросов Hibernate (HQL) поддерживает различные методы агрегации, которые доступны и в SQL. HQL поддерживает следующие методы:

  • min(имя свойства) — минимальное значение данного свойства.

  • max(имя свойства) — максимальное значение данного свойства.

  • sum(имя свойства) — сумма всех значений данного свойства.

  • avg(имя свойства) — среднее арифметическое всех значений данного свойства

  • count(имя свойства) — какое количество раз данное свойство встречается в результате.

22. Запросы с использованием Criteria

Hibernate поддерживает различные способы манипулирования объектами и транслирования их в таблицы баз данных (далее – БД). Одним из таких способов является Criteria API, который позволяет создавать запросы с критериями, программным методом.

Для создания Criteria используется метод createCriteria() интерфейса Session. Этот метод возвращает экземпляр сохраняемого класса (persistent class) в результате его выполнения.

Вот как это выглядит на практике:

public class Program {
    // ...

    public static void main(String[] args){
        Criteria criteria = session.createCriteria(Developer.class);
        List developers = criteria.list();
    }
}

Criteria имеет два важных метода:

  • public Criteria setFirstResult(int firstResult)

Этот метод указывает первый ряд результата, который начинается с 0.

  • public Criteria setMaxResults(int maxResults)

Этот метод ограничивает максимальное количество объектов, которое Hibernate сможет получить в результате запроса.

Для понимания того, как это работает на практике рассмотрим пример простого приложения.

22.1. Шаг 1. Создание таблицы HIBERNATE_DEVELOPERS в БД.

CREATE TABLE HIBERNATE_DEVELOPERS (
    id INT NOT NULL auto_increment,
    FIRST_NAME VARCHAR(50) default NULL,
    LAST_NAME VARCHAR(50) default NULL,
    SPECIALTY VARCHAR(50) default NULL,
    EXPERIENCE INT default NULL,
    SALARY INT default NULL,
    PRIMARY KEY (id)
);

22.2. Шаг 2. Создание POJO–класса

Developer.java
package net.proselyte.hibernate.criteria;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private int salary;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience, int salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
        this.salary = salary;
    }

    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "id: " + id +
                "\nFirst Name: " + firstName +
                "\nLast Name: " + lastName +
                "\nSpecialty: " + specialty +
                "\nExperience: " + experience +
                "\nSalary: " + salary + "\n";
    }
}

22.3. Шаг 3. Создание конфигурационных файлов

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БД</property>
        <property name="hibernate.connection.username">ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ_ПАРОЛЬ</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Developer.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="net.proselyte.hibernate.criteria.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
        <property name="salary" column="SALARY" type="int"/>
    </class>
</hibernate-mapping>

22.4. Шаг 4. Создание класса DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.criteria;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import java.util.List;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();
        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding developer's records to the database...");
        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 3, 2000);
        Integer developerId2 = developerRunner.addDeveloper("First", "Developer", "C++ Developer", 10, 2000);
        Integer developerId3 = developerRunner.addDeveloper("Second", "Developer", "C# Developer", 5, 2000);
        Integer developerId4 = developerRunner.addDeveloper("Third", "Developer", "PHP Developer", 1, 2000);

        System.out.println("List of Developers with experience more than 3 years:");
        developerRunner.listDevelopersOverThreeYears();

        System.out.println("Total Salary of all Developers:");
        developerRunner.totalSalary();
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, int salary) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience, salary);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopersOverThreeYears() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Criteria criteria = session.createCriteria(Developer.class);
        criteria.add(Restrictions.gt("experience", 3));
        List developers = criteria.list();

        for (Developer developer : developers) {
            System.out.println("=======================");
            System.out.println(developer);
            System.out.println("=======================");
        }
        transaction.commit();
        session.close();
    }

    public void totalSalary() {
        Session session  = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Criteria criteria = session.createCriteria(Developer.class);
        criteria.setProjection(Projections.sum("salary"));

        List totalSalary = criteria.list();
        System.out.println("Total salary of all developers: " + totalSalary.get(0));
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы получится, примерно, следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7536 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.criteria.DeveloperRunner
Feb 23, 2016 1:58:13 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 23, 2016 1:58:13 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 23, 2016 1:58:13 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 23, 2016 1:58:14 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 23, 2016 1:58:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 23, 2016 1:58:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 23, 2016 1:58:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 23, 2016 1:58:15 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 23, 2016 1:58:15 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Tue Feb 23 13:58:15 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 23, 2016 1:58:16 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding developer's records to the database...
List of Developers with experience more than 3 years:
=======================
id: 18
First Name: First
Last Name: Developer
Specialty: C++ Developer
Experience: 10
Salary: 2000

=======================
=======================
id: 19
First Name: Second
Last Name: Developer
Specialty: C# Developer
Experience: 5
Salary: 2000

=======================
Total Salary of all Developers:
Total salary of all developers: 8000
Feb 23, 2016 1:58:16 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

23. Нативный SQL.

Для того чтобы формировать запросы для базы данных (далее – БД), при этом используя все возможности БД, может использоваться нативный SQL.

Таким образом приложение создаст нативный SQL – запрос, используя метод createSQLQuery() интерфейса Session, который выглядит следующим образом:

  • public SQLQuery createSQLQuery (String sqlString) throws HibernateException;

После того как методу createSQLQuery() передать строку (String), содержащая SQL–запрос, можно связать результат этого запроса с сохраняемым объектом (persistent object).

Для понимания того, как это работает на практике, рассмотрим пример простого приложения.

Пример:
Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.

23.1. Шаг 1. Создание таблицы HIBRNATE_DEVELOPERS в БД

CREATE TABLE HIBERNATE_DEVELOPERS (
    id INT NOT NULL auto_increment,
    FIRST_NAME VARCHAR(50) default NULL,
    LAST_NAME VARCHAR(50) default NULL,
    SPECIALTY VARCHAR(50) default NULL,
    EXPERIENCE INT default NULL,
    SALARY INT default NULL,
    PRIMARY KEY (id)
);

23.2. Шаг 2. Создание POJO–класса

Developer.java

package net.proselyte.hibernate.criteria;

public class Developer {
private int id;
private String firstName;
private String lastName;
private String specialty;
private int experience;
private int salary;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience, int salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
        this.salary = salary;
    }

    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "id: " + id +
                "\nFirst Name: " + firstName +
                "\nLast Name: " + lastName +
                "\nSpecialty: " + specialty +
                "\nExperience: " + experience +
                "\nSalary: " + salary + "\n";
    }
}

23.3. Шаг 3. Создание конфигурационных файлов

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>

        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БД
        </property>
        <property name="hibernate.connection.username">
            ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ
        </property>
        <property name="hibernate.connection.password">
            ВАШ_ПАРОЛЬ
        </property>

        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Developer.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="net.proselyte.hibernate.nativesql.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer details.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
        <property name="salary" column="SALARY" type="int"/>
    </class>

</hibernate-mapping>

23.4. Шаг 4. Создание класса DeveloperRunner.java

DeveloperRunner.java

package net.proselyte.hibernate.nativesql;

import org.hibernate.*;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Projections;

import java.util.List;
import java.util.Map;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();
        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding developer's records to the database...");
        Integer developerId1 = developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 3, 2000);
        Integer developerId2 = developerRunner.addDeveloper("First", "Developer", "C++ Developer", 10, 5000);
        Integer developerId3 = developerRunner.addDeveloper("Second", "Developer", "C# Developer", 5, 4000);
        Integer developerId4 = developerRunner.addDeveloper("Third", "Developer", "PHP Developer", 1, 1000);

        System.out.println("List of Developers using Entity Query:");
        developerRunner.listDevelopers();

        System.out.println("List of Developers using Scalar Query:");
        developerRunner.listDevelopersScalar();
        sessionFactory.close();
    }

    public Integer addDeveloper(String firstName, String lastName, String specialty, int experience, int salary) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience, salary);
        developerId = (Integer) session.save(developer);
        transaction.commit();
        session.close();
        return developerId;
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM HIBERNATE_DEVELOPERS");
        sqlQuery.addEntity(Developer.class);
        List developers = sqlQuery.list();

        for (Developer developer : developers) {
            System.out.println("=======================");
            System.out.println(developer);
            System.out.println("=======================");
        }
        transaction.commit();
        session.close();
    }

    public void listDevelopersScalar() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM HIBERNATE_DEVELOPERS");
        sqlQuery.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
        List developers = sqlQuery.list();
        for (Object developer : developers) {
            Map row = (Map) developer;
            System.out.println("=======================");
            System.out.println("id: " + row.get("id"));
            System.out.println("First Name: " + row.get("FIRST_NAME"));
            System.out.println("Last Name: " + row.get("LAST_NAME"));
            System.out.println("Specialty: " + row.get("SPECIALTY"));
            System.out.println("Experience: " + row.get("EXPERIENCE"));
            System.out.println("Salary: " + row.get("SALARY"));
            System.out.println("=======================");
        }
        transaction.commit();
        session.close();
    }

    public void totalSalary() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Criteria criteria = session.createCriteria(Developer.class);
        criteria.setProjection(Projections.sum("salary"));

        List totalSalary = criteria.list();
        System.out.println("Total salary of all developers: " + totalSalary.get(0));
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, в результате работы программы получится, примерно, следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7541 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.nativesql.DeveloperRunner
Feb 23, 2016 9:44:03 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 23, 2016 9:44:03 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 23, 2016 9:44:03 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 23, 2016 9:44:04 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 23, 2016 9:44:05 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 23, 2016 9:44:05 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 23, 2016 9:44:05 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 23, 2016 9:44:05 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 23, 2016 9:44:05 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Tue Feb 23 21:44:05 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 23, 2016 9:44:05 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding developer's records to the database...
List of Developers using Entity Query:
=======================
id: 69
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3
Salary: 2000

=======================
=======================
id: 70
First Name: First
Last Name: Developer
Specialty: C++ Developer
Experience: 10
Salary: 5000

=======================
=======================
id: 71
First Name: Second
Last Name: Developer
Specialty: C# Developer
Experience: 5
Salary: 4000

=======================
=======================
id: 72
First Name: Third
Last Name: Developer
Specialty: PHP Developer
Experience: 1
Salary: 1000

=======================
List of Developers using Scalar Query:
=======================
id: 69
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3
Salary: 2000
=======================
=======================
id: 70
First Name: First
Last Name: Developer
Specialty: C++ Developer
Experience: 10
Salary: 5000
=======================
=======================
id: 71
First Name: Second
Last Name: Developer
Specialty: C# Developer
Experience: 5
Salary: 4000
=======================
=======================
id: 72
First Name: Third
Last Name: Developer
Specialty: PHP Developer
Experience: 1
Salary: 1000
=======================
Feb 23, 2016 9:44:06 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Table 2. Таблица HIBERNATE_DEVELOPERS
id FIRST_NAME LAST_NAME SPECIALTY EXPERIENCE SALARY

65

Proselyte

Developer

Java Developer

3

2000

66

First

Developer

C++ Developer

10

5000

67

Second

Developer

C# Developer

5

4000

68

Third

Developer

PHP Developer

1

1000

24. Hibernate cache

Кэш - это память с большей скоростью доступа, предназначенная для ускорения обращения к данным, содержащимся постоянно в памяти с меньшей скоростью доступа. Кэширование применяется ЦПУ, жёсткими дисками, браузерами, веб-серверами, службами DNS и WINS.

Кэш состоит из набора записей. Каждая запись ассоциирована с элементом данных или блоком данных (небольшой части данных), которая является копией элемента данных в основной памяти. Каждая запись имеет идентификатор, часто называемый тегом, определяющий соответствие между элементами данных в кэше и их копиями в основной памяти.

cache

Когда клиент кэша (ЦПУ, веб-браузер, операционная система) обращается к данным, прежде всего исследуется кэш. Если в кэше найдена запись с идентификатором, совпадающим с идентификатором затребованного элемента данных, то используются элементы данных в кэше. Такой случай называется попаданием кэша. Если в кэше не найдена запись, содержащая затребованный элемент данных, то он читается из основной памяти в кэш, и становится доступным для последующих обращений. Такой случай называется промахом кэша.

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

Кэширование, как правило, есть смысл применять в больших, высоконагруженных проектах, с десятками тысяч запросов в минуту. В таких проектах, чтобы не перегружать базу, как правило, кэшируют обращения к репозиторию. Особенно если известно, что данные из какой-нибудь мастер-системы обновляются с некоторой периодичностью. Если же проект маленький и перегрузки ему не грозят, тогда, конечно, лучше ничего не кэшировать — всегда свежие данные всегда лучше периодически обновляемых.

app hibernate db

В Hibernate имеется 3 вида кеширования:

  • First-level cache (Кеш первого уровня);

  • Second-level cache (Кеш второго уровня);

  • Query cache (Кеш запросов);

hibernate first and second level cache

24.1. Кеш первого уровня

В Hibernate кэш первого уровня представлен интерфейсом Session, который является расширением к JPA EntityManager. В терминологии JPA кэш первого уровня называется Persistence Context, и он представлен интерфейсом EntityManager.

Кеш первого уровня всегда привязан к объекту Session. Hibernate по умолчанию использует этот кеш и его нельзя отключить.

В JPA и Hibernate кэш первого уровня - это Java Map, в котором ключ представлен объектом, инкапсулирующим имя сущности и ее идентификатор, а значение - это сам объект сущности. Поэтому в JPA EntityManager или Hibernate Session может быть только одна сущность, хранящаяся с использованием одного и того же идентификатора и типа класса сущности. Причина, по которой невозможно иметь не более одного представления сущности, хранящегося в кэше первого уровня, заключается в том, что в противном случае мы можем получить различные отображения одной и той же строки базы данных, не зная, какая из них является правильной версией, синхронизируемой с соответствующей записью базы данных.

Session session = sessionFactory.openSession();
Pet pet = (Pet) session.load(Pet.class, id);
pet = (Pet) session.load(Pet.class, id);

В примере выше будет выполнен 1 запрос в базу, несмотря на то, что делается 2 вызова load(), так как эти вызовы происходят в контексте одной сессии. Во время второй попытки загрузить план с тем же идентификатором будет использован кеш сессии.

Сначала Hibernate проверяет, хранится ли сущность в кэше первого уровня, и если да, то возвращается текущая управляемая ссылка на нее. Если сущность не найдена в кэше первого уровня, то Hibernate загрузит ее из базы данных с помощью SQL-запроса.

При использовании методов save(), update(), saveOrUpdate(), load(), get(), list(), iterate(), scroll() всегда будет задействован кеш первого уровня.

Для работы с данными кеша в сессии имеются следующие методы:

  • flush() — обновляет объекты кеша с базой данных.

  • evict() — удаляет объект из кеша.

  • contains() — определяет, находится ли объект в кеше или нет.

  • clear() — очищает весь кеш.

При кеше первого уровня все активные сессии работает изолированно друг от друга, и у каждой сессии соответственно имеется свой кеш, который очищается при закрытии сессии.

24.2. Кеш второго уровня

Кеш второго уровня привязан к SessionFactory, поэтому видимость этого кеша гораздо шире кеша первого уровня. Данный вид кеша доступен на протяжении всего времени работы приложения.

По умолчанию кеш второго уровня отключен. Для включения необходимо при конфигурировании настроек Hibernate в строке hibernate.cache.use_second_level_cache установить значение true.

На самом деле, Hibernate сам не реализует кеширование как таковое, а лишь предоставляет структуру для его реализации. Поэтому подключить можно любую реализацию, которая соответствует спецификации ORM фреймворка.

Из сказанного выше следует, что помимо hibernate.cache.use_second_level_cache еще необходимо в настройках указать провайдера кеша hibernate.cache.region.factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory, которого также нужно добавить в зависимости Maven.

spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          region:
            factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory

Из популярных реализаций провайдеров можно выделить следующие:

  • EHCache

  • OSCache

  • SwarmCache

  • JBoss TreeCache

Эти провайдеры дополнительно можно настраивать, например в файле ehcache.xml, который должен находиться в директории ресурсов. В данном файле, например, можно ограничить количество записей в кеше maxElementsInMemory="500".

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
	monitoring="autodetect" dynamicConfig="true">

    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="500"
        eternal="false"
        timeToIdleSeconds="60"
        timeToLiveSeconds="60"
        overflowToDisk="true"/>
    <cache name="cache1"
        maxElementsInMemory="500"
        maxEntriesLocalHeap="10000"
        maxEntriesLocalDisk="1000"
        eternal="false"
        diskSpoolBufferSizeMB="20"
        timeToIdleSeconds="300" timeToLiveSeconds="600"
        memoryStoreEvictionPolicy="LFU"
        transactionalMode="off">
        <persistence strategy="localTempSwap"/>
    </cache>
</ehcache>

Помимо вышеуказанного нужно еще самому Hibernate, что именно кешировать. Делается это с помощью аннотаций @Cacheable и @Cache.

@Entity
@Table(name = "shared_doc")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class SharedDoc{
    private Set<User> users;
}

@Cacheable это аннотация JPA и позволяет объекту быть закэшированным. Hibernate поддерживает эту аннотацию в том же ключе. @Cache это аннотация Hibernate, настраивающая тонкости кэширования объекта в кэше второго уровня Hibernate. Аннотации @Cacheable достаточно, чтобы объект начал кэшироваться с настройками по умолчанию. При этом @Cache, использованная без @Cacheable, не разрешит кэширование объекта.

Аннотация @Cache имеет несколько атрибутов:

  • usage - отвечает за стратегию кеширования.

  • region - это логический разделитель памяти вашего кеша. Для каждого региона можно настроить свою политику кеширования (для EhCache в том же ehcache.xml). Если регион не указан, то используется регион по умолчанию, который имеет полное имя вашего класса для которого применяется кеширование.

  • include - Могут ли свойства сущности, указанные с lazy=true, кэшироваться, когда разрешена "ленивая" выборка на уровне атрибутов. По-умолчанию all и может быть также non-lazy

Стратегии кеширования определяют поведения кеша в определенных ситуациях. Выделяют четыре группы:

  • Read-only - используется только для сущностей, которые никогда не изменяются (выбрасывается исключение при попытке обновить такую сущность). Это очень просто и эффективно. Очень подходит для некоторых статических эталонных данных, которые не меняются.

  • Nonstrict-read-write - кэш обновляется после фиксации транзакции, которая изменила затронутые данные. Таким образом, строгая согласованность не гарантируется, и существует небольшое временное окно, в течение которого устаревшие данные могут быть получены из кэша. Этот тип стратегии подходит для вариантов использования, которые могут допустить возможную согласованность.

  • Read-write - эта стратегия гарантирует строгую согласованность, которая достигается за счет использования «мягких» блокировок - когда кэшированный объект обновляется, программная блокировка также сохраняется в кэше для этого объекта, которая освобождается после фиксации транзакции. Все параллельные транзакции, которые обращаются к заблокированным записям, будут извлекать соответствующие данные непосредственно из базы данных.

  • Transactional - изменения кэша выполняются в распределенных транзакциях. Изменение в кэшированном объекте либо фиксируется, либо откатывается как в базе данных, так и в кэше в одной и той же транзакции.

Помимо вышесказанного, следует помнить — зависимости Вашего класса по умолчанию также не кешируются. Например, если рассмотреть класс выше — SharedDoc, то при выборке коллекция users будет доставаться из базы данных, а не из кеша второго уровня. Если Вы хотите также кешировать и зависимости, то необходимо также проаннотировать все необходимы объекты аннотацией @Cache.

@Entity
@Table(name = "shared_doc")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class SharedDoc{
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<User> users;
}

Еще одна важная деталь про кеш второго уровня. Hibernate не хранит сами объекты классов. Он хранит информацию в разобранном (гидратированном) состоянии, в виде массивов строк, чисел и т. д.

  • Id (первичный ключ) не сохраняется (он хранится как часть ключа кэша)

  • Переходные свойства не сохраняются

  • Коллекции не хранятся

  • Значения свойств, не связанные с ассоциацией, хранятся в исходном виде

  • Для отношений ToOne сохраняется только идентификатор (внешний ключ)

Идентификатор объекта выступает указателем на эту информацию. Концептуально это нечто вроде Map, в которой id объекта — ключ, а массивы данных — значение. Приблизительно можно представить себе это так - 1 → { "Pupkin", 1, null , {1,2,5} }, что есть очень разумно, учитывая сколько лишней памяти занимает каждый объект.

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

Session session = factory.openSession();
Pet pet = (Pet) session.load(Pet.class, 1L);
session.close();
session = factory.openSession();
pet = (Pet) session.load(Pet.class, 1L);
session.close();

В примере выше, если будет реализован кеш второго уровня, то к базе данных будет выполнен всего один запрос.

24.3. Кеш запросов

Кэш запросов, так же как и кэш второго уровня, существует на уровне SessionFactory и доступен во всех persistence context. Для кэша запросов требуются две дополнительные области физического кэша, в которых хранятся результаты кэшированного запроса и отметки времени последнего обновления таблицы.

Кэши первого и второго уровней работают с объектами загружаемыми по id. Но к базе данных чаще выполняются запросы с условиями, чем загружаются какие-то заранее известные объекты. И результат выполнения таких запросов тоже может потребоваться кэшировать. Например, если вы делаете поисковый сайт по автозапчастям, то можете кэшировать запросы пользователей, которые, скорее всего, ищут одни запчасти гораздо чаще других.

У кэша запросов есть и своя цена — Hibernate будет вынужден отслеживать сущности закешированные с определённым запросом и выкидывать запрос из кэша, если кто-то поменяет значение сущности. То есть для кэша запросов стратегия параллельного доступа всегда read-only. По этим причинам, Hibernate по-умолчанию выключает кэширование запросов.

Кеш запросов похож на кеш второго уровня. Но в отличии от него — ключом к данным кеша выступает не идентификатор объекта, а совокупность параметров запроса. А сами данные — это идентификаторы объектов соответствующих критериям запроса. Таким образом, этот кеш рационально использовать с кешем второго уровня.

Если у вас есть запросы, выполняющиеся снова и снова, с одними и теми же параметрами, кэширование запросов предоставит выигрыш в производительности.

Для включения нужно добавить свойства hibernate.cache.use_query_cache=true. Установив свойства значение true, вы заставляете Hibernate создавать необходимые кеши в памяти для хранения наборов запросов и идентификаторов. Также надо установить setCacheable(true).

spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_query_cache: true
Session session = SessionFactory.openSession();
Query query = session.createQuery("FROM EMPLOYEE");
query.setCacheable(true);
query.setCacheRegion("employee");
List users = query.list();
SessionFactory.closeSession();

Hibernate также поддерживает очень тонкую поддержку кэша благодаря концепции области кэша. Регион кеша является частью кеша, которому дано имя. Пример выше использует метод, чтобы сообщить Hibernate хранить и искать запрос в области кэша сотрудников.

25. Кеширование

Кеширование является одним из способов оптимизации работы приложения, ключевой задачей которого является уменьшить количество прямых обращений к базе данных (далее – БД).

Если речь идёт о Hibernate, то схематически кэширование можно представить в виде следующего изображения:

Caching

25.1. First Level Cache (Кэш первого уровня)

Кэш первого уровня – это кэш Сессии (Session), который является обязательным. Через него проходят все запросы. Перед тем, как отправить объект в БД, сессия хранит объект за счёт своих ресурсов.

В том случае, если выполняем несколько обновлений объекта, Hibernate старается отсрочить (насколько это возможно) обновление для того, чтобы сократить количество выполненных запросов. Если закроем сессию, то все объекты, находящиеся в кэше теряются, а далее – либо сохраняются, либо обновляются.

25.2. Second level Cache (Кэш второго уровня)

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

25.3. Query Cache (Кэш запросов)

В Hibernate предусмотрен кэш для запросов и он интегрирован с кэшем второго уровня. Это требует двух дополнительных физических мест для хранения кэшированных запросов и временных меток для обновления таблицы БД. Этот вид кэширования эффективен только для часто используемых запросов с одинаковыми параметрами.

Рассмотрим, как это выглядит на практике.

Developer.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="net.proselyte.hibernate.nativesql.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains developer details.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
        <property name="salary" column="SALARY" type="int"/>
    </class>
</hibernate-mapping>
hibenrate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БАЗЫ_ДАННЫХ</property>
        <property name="hibernate.connection.username">ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ_ПАРОЛЬ</property>

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

Два предыдущих файла уже знакомы, а теперь нам необходимо создать файл ehcache.xml

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="500"
        eternal="false"
        timeToIdleSeconds="60"
        timeToLiveSeconds="60"
        overflowToDisk="true"
    />
    <cache
        name="Developer"
        maxElementsInMemory="200"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
    />
</ehcache>

Для того, что б кэширование стало доступным для нашего приложения мы должны активировать его следующим образом:

public class Program {
    public static void main(String[] args){
        Session session = sessionFactory.openSession();
        Query query = session.createQuery("FROM HIBERNATE_DEVELOPERS");
        query.setCacheable(true);
        query.setCacheRegion("developer");
        List developers = query.list();
        sessionFactory.close();
    }
}

26. Обработка “пакетов”.

Необходимо выполнить 100,000 записей в таблицу базы данных (далее – БД). Рассмотрим примитивный способ:

public class Program {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();

        for(int i = 0; i < 100_000; i++){
           Developer developer = new Developer(/*Some parameters*/);
           session.save(developer);
        }
        transaction.commit();
        session.close();
    }
}

На первый взгляд кажется, что получим 100_000 записей в нашу БД, но на практике получим OutOfMemoryException примерно в тот момент, когда попытаемся выполнить 50_000 – тысячную запись. Это вызвано тем, что Hibernate кэширует все сохраняемые объекты в кэш сессии.

Как же нам решить данную проблему?

Для этого нам необходимо использовать batch processing (обработку пакетов). Например, говорим Hibernate, что хотим вставлять каждые 50 объектов, как единый пакет. Для этого необходимо установить hibernate.jdbc.atch_size на 50 и написать, примерно, такой кусок кода:

public class Program {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        for(int i = 0; i < 100000; i++) {
            Developer developer = new Developer(/*Some parameters*/);
            session.save(developer);
            if (i % 50 == 0) {
                session.flush();
                session.clear();
            }
        }
        transaction.commit();
        session.close();
    }
}

Для понимания того, как это работает на практике – рассмотрим пример небольшого приложения.

26.1. Шаг 1. Создаём таблицу в нашей БД

HIBRNATE_DEVELOPERS
CREATE TABLE HIBERNATE_DEVELOPERS (
   id INT NOT NULL auto_increment,
   FIRST_NAME VARCHAR(50) default NULL,
   LAST_NAME VARCHAR(50) default NULL,
   SPECIALTY VARCHAR(50) default NULL,
   EXPERIENCE INT default NULL,
   SALARY INT default NULL,
   PRIMARY KEY (id)
   );

26.2. Шаг 2. Создадим POJO – класс

Developer.java
package net.proselyte.hibernate.batch;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private int salary;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience, int salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
        this.salary = salary;
    }

    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "id: " + id +
                "\nFirst Name: " + firstName +
                "\nLast Name: " + lastName +
                "\nSpecialty: " + specialty +
                "\nExperience: " + experience +
                "\nSalary: " + salary + "\n";
    }
}

26.3. Шаг 3. Создаём конфигурационные файлы

hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Assume PROSELYTE_TUTORIAL is the database name -->
        <property name="hibernate.connection.url">jdbc:mysql://localhost/ИМЯ_ВАШЕЙ_БАЗЫ_ДАННЫХ</property>
        <property name="hibernate.connection.username">ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ</property>
        <property name="hibernate.connection.password">ВАШ_ПАРОЛЬ</property>
        <!--  Specifying batch size -->
        <property name="hibernate.jdbc.batch_size">50</property>
        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
Developer.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="net.proselyte.hibernate.batch.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">This class contains developer details.</meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
        <property name="salary" column="SALARY" type="int"/>
    </class>
</hibernate-mapping>

26.4. Шаг 4. Создаём класс DeveloperRunner.java

DeveloperRunner.java
package net.proselyte.hibernate.batch;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();
        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Adding 100,000 developer's records to the database...");
        developerRunner.addDevelopers();
        System.out.println("100,000 developer's records successfully added to the database...");
        sessionFactory.close();
    }

    public void addDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Integer developerId = null;

        transaction = session.beginTransaction();

        for (int i = 0; i < 100_000; i++) {
            String firstName = "First Name " + i;
            String lastName = "Last Name " + i;
            String specialty = "Specialty " + i;
            int experience = i;
            int salary = i * 10;
            Developer developer = new Developer(firstName, lastName, specialty, experience, salary);
            session.save(developer);
            if (i % 50 == 0) {
                session.flush();
                session.clear();
            }
        }
        transaction.commit();
        session.close();
    }
}

Если всё было сделано правильно, то в результате работы программы получим, примерно, следующий результат:

Output
/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7533 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.1.0.Final/hibernate-core-5.1.0.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/com/fasterxml/classmate/1.3.0/classmate-1.3.0.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.batch.DeveloperRunner
Feb 24, 2016 12:54:18 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Feb 24, 2016 12:54:18 PM org.hibernate.cfg.Environment
INFO: HHH000206: hibernate.properties not found
Feb 24, 2016 12:54:18 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 24, 2016 12:54:18 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 24, 2016 12:54:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 24, 2016 12:54:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 24, 2016 12:54:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 24, 2016 12:54:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 24, 2016 12:54:20 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Wed Feb 24 12:54:20 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 24, 2016 12:54:20 PM org.hibernate.dialect.Dialect
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Adding 100,000 developer's records to the database...
100,000 developer's records successfully added to the database...
Feb 24, 2016 12:54:41 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]

Примерно, вот так будет выглядеть наша таблица

Table 3. HIBERNATE_DEVELOPERS
id FIRST_NAME LAST_NAME SPECIALTY EXPERIENCE SALARY

100061

First Name 99988

Last Name 99988

Specialty 99988

99988

999880

100062

First Name 99989

Last Name 99989

Specialty 99989

99989

999890

100063

First Name 99990

Last Name 99990

Specialty 99990

99990

999900

100064

First Name 99991

Last Name 99991

Specialty 99991

99991

999910

100065

First Name 99992

Last Name 99992

Specialty 99992

99992

999920

100066

First Name 99993

Last Name 99993

Specialty 99993

99993

999930

100067

First Name 99994

Last Name 99994

Specialty 99994

99994

999940

100068

First Name 99995

Last Name 99995

Specialty 99995

99995

999950

100069

First Name 99996

Last Name 99996

Specialty 99996

99996

999960

100070

First Name 99997

Last Name 99997

Specialty 99997

99997

999970

100071

First Name 99998

Last Name 99998

Specialty 99998

99998

999980

100072

First Name 99999

Last Name 99999

Specialty 99999

99999

999990