# 5. Интеграция приложения с СУБД

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

**Что такое** **JDBC, драйвер,** **JPA,** **ORM** **и как это все между собой соотносится?**

Как правило, каждая система управления базами данных (MySQL, PostgreSQL и так далее) имеет свой протокол взаимодействия с клиентами. Чтобы работать с базой данных, клиент должен соблюдать протокол взаимодействия с базой данных.

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

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

Если разработчики СУБД хотят, чтобы их база данных использовалась Java-разработчиками, они предоставляют JDBC-драйвер для их базы данных. Разработчики Java подключат драйвер и используют его для общения с той или иной базой данных. Если, в какой-то момент, разработчики захотят сменить СУБД, они просто меняют драйвер старой базы на драйвер новой. Благодаря стандарту JDBC, ничего менять в коде работы с базой данных не требуется.

**Что такое и зачем нужна технология** **ORM?**

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

1. данные в программе и в базе данных используют разные парадигмы (объектно-ориентированная и реляционная соответственно). Работу по преобразованию данных из одной парадигмы в другую ложатся на плечи программиста, что влечет за собой лишнюю работу и может приводить к ошибкам в процессе преобразования;
2. программисту желательно абстрагироваться от конкретной схемы хранения данных. То есть, программисту желательно работать не с реляционной базой данных, а просто с некоторым «хранилищем», а конкретная реализация этого «хранилища» может быстро и безболезненно меняться.

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

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MWiEXEHFtsvUrLbWUTI%2F-MWiEiUBuODJ4uAg27II%2Fimage.png?alt=media\&token=52188bb2-f98b-4318-aec8-c84d864f7046)

Проще говоря, **ORM** **– это прослойка, посредник между базой данных и объектным кодом**. Используя ORM, программист не занимается формированием SQL-запросов и не думает в терминах «таблица», «записи» и «реляционные отношения», а просто работает с «хранилищем объектов» – он может туда записывать и получать объекты, не заботясь о подробностях их хранения.

В Java предусмотрен специальный стандарт **JPA** (Java Persistence API), который использует концепцию ORM. Существует несколько реализаций этого интерфейса, например, Hibernate, OpenJPA, EclipseLink и другие.

**Spring** **Data** **JPA** – обертка над JPA в Spring, которая предоставляет много полезных «фишек» разработчику. Она позволяет легче создавать Spring-управляемые приложения, которые используют новые способы доступа к данным, например нереляционные базы данных, map-reduce фреймворки, cloud сервисы, а так же уже хорошо улучшенную поддержку реляционных баз данных.

**Терминология** **JPA**

Основное понятие JPA – **сущность (Entity)**. **Сущность** – это Java-класс, который представляет бизнес-логику приложения и определяет данные, которые будут храниться в базе данных и извлекаться из нее.

Как правило, **класс** сущности представляет **таблицу** в базе данных, **поля** или свойства класса представляют собой **колонки** в таблице, а **объект** сущности представляет собой одну **запись** в таблице.

Важным моментом при работе с JPA являются аннотации, коих здесь будет очень много. Разберемся с некоторыми из них:

* **@Entity** – позволяет серверу узнать, что это не просто какой-то класс, а сущность;
* **@Id** – помечает первичный ключ в таблице. Вопрос составных ключей в данном занятии не рассматривается;
* **@Table** – позволяет настраивать отображение класса в таблицу. В данном случае, мы можем указать, какое имя будет иметь соответствующая таблица в базе данных;
* **@GeneratedValue** – указывает, что данное поле является генерируемым значением. Очень часто этой аннотацией помечают первичные ключи, чтобы они генерировались автоматически при добавлении новых записей в таблицу;
* **@Column** – позволяет настраивать отображение колонки в таблице. В данном случае, мы можем указать, какое имя будет иметь соответствующая колонка в таблицу.

**Репозитории**. Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью.

В большинстве случаев, структура запросов к репозиторию будет одинаковая: «получить все записи», «получить записи, где столбец равен определенному значению» и так далее.

Spring Data JPA позволяет вам избежать рутинного создания запросов. Для этого вместо класса создадим интерфейс, который будет наследоваться от стандартного generic-интерфейса. Первый параметр означает тип класса-сущности, второй параметр – тип первичного ключа.

## Практическая часть

Установим СУБД Postgres и запустим pgAdmin 4.

Создадим пользователя ejournal\_user, после чего создадим базу данных для нашего приложения.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-LuTieO4YxGqujVNMx_P%2F-LuTneEZpVNfcebqD9gt%2Fimage.png?alt=media\&token=2783246b-c251-499f-a205-f74f6da3d40d)

Добавляем в pom.xml зависимости для работы с Spring Data JPA и JDBC драйвер для Postgres.

{% code title="pom.xml" %}
```markup
<dependencies>

    ...
    
    <!-- jpa, crud repository -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- PostgreSQL -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>
</dependencies>
```
{% endcode %}

Далее необходимо настроить подключение к СУБД и нужной базе данных.

Для настройки приложения Spring воспользуемся языком YAML. Для этого удалим файл **resources/application.properties** и создадим вместо него файл **application.yml**.

{% code title="application.yml" %}
```yaml
spring:
  jpa:
    database: POSTGRESQL
    show-sql: true
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
  datasource:
    platform: postgres
    url: jdbc:postgresql://localhost:5432/ejournal
    username: ejournal_user
    password: 123456
    driverClassName: org.postgresql.Driver
```
{% endcode %}

Создадим класс сущности Student

{% code title="Student.java" %}
```java
@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

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

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

    private int age;
}
```
{% endcode %}

Для уменьшения количества кода, мы будем использовать плагин Lombok.

Проект Lombok — это плагин компилятора, который добавляет в Java новые «ключевые слова» и превращает аннотации в Java-код, уменьшая усилия на разработку и обеспечивая некоторую дополнительную функциональность.

Lombok преобразует аннотации в исходном коде в Java-операторы до того, как компилятор их обработает: зависимость `lombok` отсутствует в рантайме, поэтому использование плагина не увеличит размер сборки.

При использовании Lombok наш исходный код не будет валидным кодом Java. Поэтому потребуется установить плагин для IDE, иначе среда разработки не поймёт, с чем имеет дело. Lombok поддерживает все основные Java IDE. Интеграция бесшовная. Все функции вроде «показать использования» и «перейти к реализации» продолжают работать как и раньше, перемещая вас к соответствующему полю/классу.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-LuTieO4YxGqujVNMx_P%2F-LuU55rterknyrHyQxpn%2Fimage.png?alt=media\&token=f1910ff6-65dd-4702-af3c-62be8bc90df9)

Далее подключим библиотеку в **pom.xml**.

{% code title="pom.xml" %}
```markup
<dependencies>

    ...

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
```
{% endcode %}

Вернемся в класс Student, добавим аннотацию для геттеров, сеттеров, а также конструктор со всеми параметрами.

{% code title="Student.java" %}
```java
@Entity
@Table(name = "students")
@Data
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

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

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

    private int age;
}
```
{% endcode %}

### Программирование слоя сервисов

**Service** – это Java класс, который содержит в себе основную бизнес-логику. В основном сервис использует готовые DAO/Repositories или же другие сервисы, для того чтобы предоставить конечные данные для пользовательского интерфейса. Сервисы, как правило, вызываются контроллерами или другими сервисами.

{% code title="Service.java" %}
```java
@org.springframework.stereotype.Service
public class Service {

    public void addStudent(Student student, int id) {
        // Добавление нового студента
    }

    public List<Student> getAllStudents() {
        // Получение списка студентов
    }
}
```
{% endcode %}

Объект службы создается контейнером Spring, каждая служба является «одиночкой» (синглтоном), который создается в момент запуска приложения и уничтожается в момент закрытия приложения. Обратите внимание на аннотацию **@Service**. Этой аннотацией мы сообщаем контейнеру Spring, что это не просто класс, а класс сервиса.

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

Вернемся к созданию веб-слоя. Создадим класс контроллера, создадим две конечные точки: для добавления студента и для получения списка всех студентов.

{% code title="Controller.java" %}
```java
@RestController
public class Controller {

    @Autowired
    private Service service;

    @PostMapping("/student")
    public void addStudent(@RequestBody Student student) {
        service.addStudent(student);
    }

    @GetMapping("/student")
    public List<Student> getAllStudents() {
        return service.getAllStudents();
    }
}
```
{% endcode %}

Обратите внимание, что мы не создаем объект службы, а получаем его «извне» с помощью аннотации **@Autowired**. Контейнер Spring «внедрит» ссылку на объект службы в поле **service**. Подробнее про внедрение зависимостей будет изложено в следующем занятии.

```
@Entity
@Table(name = "groups")
@Data
public class Group {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;

    @OneToMany(mappedBy = "group")
    private List<Student> studentList;
}
```

### Работа с репозиторием

Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью.

В большинстве случаев, структура запросов к репозиторию будет одинаковая: «получить все записи», «получить записи, где столбец равен определенному значению» и так далее.

Spring Data JPA позволяет вам избежать рутинного создания запросов. Для этого вместо класса создадим интерфейс, который будет наследоваться от стандартного generic-интерфейса. Первый параметр означает тип класса-сущности, второй параметр – тип первичного ключа.

{% code title="StudentRepository.java" %}
```java
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
```
{% endcode %}

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

{% code title="Service.java" %}
```java
@org.springframework.stereotype.Service
public class Service {

    @Autowired
    private StudentRepository studentRepo;

    public void addStudent(Student student) {
        studentRepo.save(student);
    }

    public List<Student> getAllStudents() {
        return studentRepo.findAll();
    }
}
```
{% endcode %}

Обратите внимание, что мы не создавали класс, который реализует интерфейс **StudentRepository**, тогда откуда мы его получим объект интерфейсного типа? Дело в том, что **Spring** **сгенерирует класс за нас**. Этот сгенерированный класс будет иметь набор стандартных операций для работы с сущностями. В нашем случае, это операция **findAll()**, которая возвращает все сущности в таблице **student**.

Запустим сервер и выполним два клиентских запроса - один на создание студента, второй - на получение списка всех студентов.

Добавляем нового студента

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-Lud7ccCkVO9oF9vfM-K%2F-LudM7J5-woPxqXpKipE%2Fimage.png?alt=media\&token=02d0d1c8-757f-4bc5-a724-a72124d2ac79)

Теперь получим список всех студентов.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-Lud7ccCkVO9oF9vfM-K%2F-LudMH_AQI7IPVsTFKJi%2Fimage.png?alt=media\&token=8b13d82d-329b-4e5c-baa7-6302a99c78e0)

### Реализация отношения "один-ко-многим"

Как мы знаем, важной составляющей реляционных баз данных является отношения между таблицами "один-к-одному", "один-ко-многим", "многие-ко-многим".

Реализуем отношение "один-ко-многим". Создадим сущность Group - студенческая группа. В студенческой группе может быть от 0 до N студентов.

Прежде всего перейдем в сущность Student. Добавим поле group, который будет ссылаться на студенческую группу, в которой будет состоять студент. Так как в группе может быть много студентов, указываем аннотацию @ManyToOne. Также указываем аннотацию @JoinColumn, которая указывает на имя колонки, которая будет содержать Foreign Key.

{% hint style="info" %}
Технология ORM позволяет создавать двусторонние связи между таблицами. В этом случае, при выдаче JSON, может возникнуть бесконечный цикл. Чтобы его избежать, укажем аннотацию @JsonIgnore. В этом случае, колонка group будет проигнорирована в процессе сериализации\десериализации.
{% endhint %}

{% code title="Student.java" %}
```java
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String firstname;
    private String lastname;
    private int age;

    @ManyToOne (fetch = FetchType.LAZY,optional = false)
    @JoinColumn(name = "group_id",nullable = false)
    @JsonIgnore
    private Group group;
}
```
{% endcode %}

Далее создадим сущность Group.

{% code title="Group.java" %}
```java
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "groups")
public class Group {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @OneToMany(mappedBy = "group", cascade = CascadeType.ALL)
    private List<Student> students;

    public void addStudent(Student student) {
        students.add(student);
    }
}
```
{% endcode %}

Обратите внимание, что отношение один-ко-многим мы моделируем с помощью обычной коллекции. Указываем аннотацию @OneToMany, также в свойстве mappedBy указываем, какое поле "держит" отношение со стороны студента.

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

{% code title="Controller.java" %}
```java
@RestController
public class Controller {

    @Autowired
    private Service service;

    @PostMapping("/student/{group_id}")
    public void addStudent(@RequestBody Student student, @PathVariable(name = "group_id") int group_id) {
        service.addStudent(student, group_id);
    }

    @GetMapping("/student")
    public List<Student> getAllStudents() {
        return service.getAllStudents();
    }

    @PostMapping("/group")
    public void addGroup(@RequestBody Group group) {
        service.addGroup(group);
    }

    @GetMapping("/group")
    public List<Group> getAllGroups() {
        return service.getAllGroups();
    }

}
```
{% endcode %}

Теперь создадим репозиторий для сущности Group.

{% code title="GroupRepository.java" %}
```java
public interface GroupRepository extends JpaRepository<Group, Integer> {}
```
{% endcode %}

Далее модифицируем класс сервиса. Добавим методы для добавления новой группы, а также для получения списка всех групп. Также модифицируем метод добавления новой группы. Метод работает следующим образом: получаем объект группы по id, после чего добавляем ссылку на группу в поле group объекта Student.

{% code title="Service.java" %}
```java
@org.springframework.stereotype.Service
public class Service {

    @Autowired
    private StudentRepository studentRepo;

    @Autowired
    private GroupRepository groupRepo;

    public void addStudent(Student student, int id) {
        Group g = groupRepo.getOne(id);
        student.setGroup(g);
        studentRepo.save(student);
    }

    public List<Student> getAllStudents() {
        return studentRepo.findAll();
    }

    public void addGroup(Group group) {
        groupRepo.saveAndFlush(group);
    }

    public List<Group> getAllGroups() {
        return groupRepo.findAll();
    }
}
```
{% endcode %}

Запустим приложение и проверим его работу. Сначала добавим группу, после чего получим список групп.

Добавим новую группу

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-Lud7ccCkVO9oF9vfM-K%2F-LudPxnhMhxRaretqGXk%2Fimage.png?alt=media\&token=24b33822-3827-49f2-9fd7-a8046faaa88d)

Получим список групп

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-Lud7ccCkVO9oF9vfM-K%2F-LudQ8OgXx0BzcYqK7MT%2Fimage.png?alt=media\&token=5b1f51fa-e48b-469c-955f-1cbd38017ff8)

Теперь добавим нового студента

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-Lud7ccCkVO9oF9vfM-K%2F-LudQGKZJA1IixkK3emd%2Fimage.png?alt=media\&token=e9df255c-311d-41a1-841a-492a215ee0c7)

Получим список всех групп

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-M25NnFIv5H4pZSLe1U9%2F-M25TT56vRDA4kAUwXtx%2Fimage.png?alt=media\&token=fec10280-9680-4190-a54c-c1d6887ae7b3)
