Spring — это не страшно, или как наполнить h2 базу данных (и немного Hibernate)

Статья из группы Java Developer
СОДЕРЖАНИЕ ЦИКЛА СТАТЕЙ Продолжаем наш проект. Как создать веб проект. Подключаем базу к своему веб проекту. В этот раз букв будет больше, пятью минутами не обойдемся. В прошлой статье я советовал почитать про Spring несколько страниц или несколько статей, или хотя бы погуглить про что такое бины, контекст, сущности ,внедрение зависимостей в spring, способы конфигурации бинов. Если нет, то советую это сделать сразу сейчас или после этой статьи. Перед тем как наполнять нашу h2 базу. Необходимо создать класс утилиту для запуска в ней методов по заполнению базы. В пакете
ru.java.rush
Создаем пакет для утилит
utils
И саму утилиту:

package ru.java.rush.utils;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;

@Service //аннотация помечает бин как сервис
public class InitiateUtils implements CommandLineRunner { //имплементируем интерфейс CommandLineRunner (командная строка запуска)
   
    @Override
//переопределяем метод который позволит 
//нам выполнять методы нашего приложения при запуске
    public void run(String... args) throws Exception { 
        System.out.println("run"); //проверим что это работает
    }
}
Запустим приложение и в консоль выведется "run". Нам нужна эта утилита, в качестве альтернативы классу Applications, так как он должен отвечать только за запуск приложения. Создадим сущности. Сущность - это бин, цель которого хранить некоторые данные. В пакете
ru.java.rush
Создаем пакет для сущностей
entities
И саму сущность, пусть это будет фрукты:

package ru.java.rush.entities;

import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

@Entity//помечаем бин как сущность
@Table(name = "fruit_table")//в этой аннотации можно указать имя создаваемой таблицы 
public class FruitEntity {

    @Id//аннотация из пакета avax.persistence.*, помечает поле как id
    @Column(name = "id_fruit")//в этой аннотации можно указать имя поля 
    @GenericGenerator(name = "generator", strategy = "increment")//незаметно добрались до hibernate,
// здесь указывается что id будет автоматически увеличиваться при новых записях  
    @GeneratedValue(generator = "generator")//аннотация генерации id
    private Integer id;

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

    @Column(name = "provider_code")
    private Integer providerCode;

   //что бы в с классом можно было совершать манипуляции создается
  //пустой конструктор, геттеры, сеттеры и переопределяется метод toString()

  public FruitEntity(){ //пустой конструктор

 }

public Integer getId() {
    return id;
}

 //геттеры, сеттеры
public String getFruitName() {
    return fruitName;
}

public FruitEntity setFruitName(String fruitName) {
    this.fruitName = fruitName;
    return this;
}

public Integer getProviderCode() {
    return providerCode;
}

public FruitEntity setProviderCode(Integer providerCode) {
    this.providerCode = providerCode;
    return this;
}

//переопределяем toString()
@Override
public String toString() {
    return "FruitEntity{" +
            "id=" + id +
            ", fruitName='" + fruitName + '\'' +
            ", providerCode=" + providerCode +
            '}';
}
}
Конструктор, геттеры, сеттеры и toString() не обязательно писать руками, можно быстро нагенерировать . Ладно, сущность у нас взаимодействует с базой и хранит в себе данные из БД. Сущность при деле. Но кто то должен оперировать сущностью в приложении. Для этого придумали "репозиторий". В пакете
ru.java.rush
Создаем пакет для репозиториев
repositories
И сам репозиторий

package ru.java.rush.repositories;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.java.rush.entities.FruitEntity;

@Repository//помечаем что этот бин - репозиторий
public interface FruitRepository extends JpaRepository<FruitEntity,Integer> {
//репозиторий является интерфейсом, который наследуется от другого интерфейса JpaRepository<>
//для него необходимо указать с какой сущность он должен работать, у нас это FruitEntity
//и тип данных у поля id данной сущности, у нас это Integer
}
Вопрос, а почему тело интерфейса пустое, нет ни одного метода, что он объявляет? Для ответа зажмем Ctrl и кликнем на JpaRepository и посмотрим что он и сам наследуется от PagingAndSortingRepository<t, ""> и QueryByExampleExecutor<>, они тоже объявляют какие то методы. Не буду сюда копировать методы сами по смотрите. Поскольку репозиторий это интерфейс, он ни чего не делает, он только объявляет методы, необходим еще кто-то что бы эти методы реализовать. Для этого придуман "сервис". В пакете
ru.java.rush
Создаем пакет для сервисов
services
И сам сервис

package ru.java.rush.services;

import org.springframework.stereotype.Service;

@Service//помечаем что этот бин - сервис
public class FruitService {
}
Сейчас мы дошли до важного момента: "Как и зачем заинжектить бин" (внедрить зависимость). Если вы не понимаете о чем это я, то прошу вас почитать на эту тему, сейчас или после, особенно обратите внимание на способы "инжекта",сколько их, какой лучше, какой хуже, и почему. Мы используем один из способов. Нам нужно что бы "сервис" как то был связан с "репозиторием". Дополняем наш сервис аннотацией и переменной.

package ru.java.rush.services;

import org.springframework.stereotype.Service;
import ru.java.rush.repositories.FruitRepository;

@Service
public class FruitService {
    
    private final FruitRepository fruitRepository;  //final переменная репозитория 
    
public FruitService(FruitRepository fruitRepository) {//внедрили зависимость через конструктор
    this.fruitRepository = fruitRepository;
}

}
Теперь можно реализовать метод из "репозитория" Дополняем "сервис"

package ru.java.rush.services;

import org.springframework.stereotype.Service;
import ru.java.rush.entities.FruitEntity;
import ru.java.rush.repositories.FruitRepository;

@Service
public class FruitService {

    private final FruitRepository fruitRepository;

public FruitService(FruitRepository fruitRepository) {//внедили зависимость
    this.fruitRepository = fruitRepository;
}

//создали публичный метод (название любое может быть)
//на вход принимает сущность и сохраняет ее в базу
    public void save(FruitEntity fruitEntity){ 
        fruitRepository.save(fruitEntity); //реализовали метод внедренного бина
    }

//возвращает лист всех сущностей из базы
    public List<FruitEntity> getAll(){
       return fruitRepository.findAll(); //реализовали метод внедренного бина
    }
}
Осталось только реализовать это в нашей утилите Переходим к классу InitiateUtils

package ru.java.rush.utils;


import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import ru.java.rush.entities.FruitEntity;
import ru.java.rush.services.FruitService;

import java.util.List;

@Service
public class InitiateUtils implements CommandLineRunner {

    private final FruitService fruitService;

public InitiateUtils (FruitService fruitService) {//незабываем конструктор для внедрения
    this. fruitService = fruitService;
}

    @Override
    public void run(String... args) throws Exception {

//создаем несколько сущностей фруктов, через сеттеры заполняем поля
        FruitEntity fruitEntity1 = new FruitEntity();
        fruitEntity1.setFruitName("fruit1");
        fruitEntity1.setProviderCode(1);

        FruitEntity fruitEntity2 = new FruitEntity();
        fruitEntity2.setFruitName("fruit2");
        fruitEntity2.setProviderCode(2);

        FruitEntity fruitEntity3 = new FruitEntity();
        fruitEntity3.setFruitName("fruit3");
        fruitEntity3.setProviderCode(3);

//с помощью переменной сервиса вызываем методы сохранения в базу, по разу для одного объекта
        fruitService.save(fruitEntity1);
        fruitService.save(fruitEntity2);
        fruitService.save(fruitEntity3);

//здесь вытаскиваем базу обратно
        List<FruitEntity> all = fruitService.getAll();

//и выводим что получилось
        for (FruitEntity entity : all) {
            System.out.println(entity);
        }
    }
}
Вывод в консоль: FruitEntity(id=1, fruitName=fruit1, providerCode=1) FruitEntity(id=2, fruitName=fruit2, providerCode=2) FruitEntity(id=3, fruitName=fruit3, providerCode=3) Вот тут можно и закончить. "Секундочку!" - воскликнет самый внимательный читатель - "А где же тут все таки Hibernate?" А Hibernate выступает здесь в роли борца невидимого фронта, он сделал очень важную вещь: создал нам структуры БД. Вот когда мы в "сущности" написали поля, и пометили нужными аннотациями, Hibernate сделал свое дело. На самом деле, при разработке на проме вы вряд ли будете заниматься структурой БД, все уже будет создано и развернуто за вас. Но в таких небольших проектах Hibernate со своим умением делать структуры БД просто не заменим, конечно это его не единственный плюс, например он хорош в создании связанных сущностей (в этом проекте мы не будем их использовать). Давайте поздороваемся с этим скромным трудягой: зайдем в IDEA в структуру проекта (слева такое дерево с папочками и файликами ), там найдем External Libraries раскроем его и среди прочих библиотек увидим

Maven: org.hibernate.common:hibernate-commons-annotations:5.1.0.Final
Maven: org.hibernate.validator:hibernate-validator:6.0.17.Final
Maven: org.hibernate:hibernate-core:5.4.6.Final
В частности Hibernate был выкачан для нас мавеном и

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
На самом деле мистер Hiber провел еще одну скрытую операцию, но о ней мы поговорим в следующей статье. Вот теперь точно всё. В качестве тренировки предлагаю вам самим реализовать метод saveAll() для FruitEntity который сохранит все сущности в базу за один раз. Дальше по смотрим как сократить код проекта с помощью библиотеки Lombok
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ