Spring — это не страшно, или как задать вопрос БД

Статья из группы Java Developer
СОДЕРЖАНИЕ ЦИКЛА СТАТЕЙ Сегодня мы доразбираем работу с базой в рамках нашего проекта. Если вы все делали правильно, то у вас должен быть pom вот с такими зависимостями:

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <java.version>1.8</java.version>
</properties>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.0.RELEASE</version>
    <relativePath/><!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
И вот такая структура проекта: Spring — это не страшно, или как задать вопрос БД - 1 Знаете кого мы давно не вспоминали? Это application.yml, мы поговорили не много о нем в одной из прошлых статей. А теперь давайте возьмем и удалим его! Да, да delete и все! Если мы запустим сейчас проект, то все будет работать как и раньше, попробуйте это сделать. Это произошло по тому, что Spring сам сконфигурировался на дефолных настройках. Теперь надо вернуть наш ямл файл обратно в папку ресурсы, он нам еще пригодиться: application.yml (наименование должно быть таким же)

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:test;
    username: sa
    password:
    h2:
      console:
        enabled: true
  jpa:
    hibernate.ddl-auto: create
    generate-ddl: true
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
В прошлый раз мы с помощью методов интерфейса JpaRepository<> реализовали несколько запросов к базе:

//сохранить одну запись в таблицу фруктов
public void save(FruitEntity fruitEntity){
    fruitRepository.save(fruitEntity);
}
//получить все записи из таблицы фруктов
public List<FruitEntity> getAll(){
   return fruitRepository.findAll();
}
//сохранить несколько записей в таблицу фруктов
public void saveAll(List<FruitEntity> fruits){
    fruitRepository.saveAll(fruits);
}
Если вы почитали про SQL, как я вас просил в прошлый раз, то вы должны знать, что такие действия с БД должны выполняться с помощью запросов SQL . Но в проекте на это нет и намека, даже в логах консоли нет ни чего похожего. Давайте их найдем, открываем application.yml, находим там срочку show-sql: (показать sql) и меняем false на true. Запускаем проект и смотрим в консоль, логи наполнились новыми записями очень похожими на SQL, на самом деле большинство из них понять не мудрено, например:

Hibernate: drop table fruit_table if exists //удалить таблицу fruit_table  если она есть
Hibernate: drop table provider_table if exists
Hibernate: create table fruit_table (id_fruit integer not null, fruit_name varchar(255), provider_code integer, primary key (id_fruit))//создать таблицу fruit_table с полями id_fruit тип integer not null, fruit_name  тип varchar(255), provider_code тип integer, назначить первичным ключем поле id_fruit
Hibernate: create table provider_table (id_provider integer not null, provider_name varchar(255), primary key (id_provider))
Но вот эта запись, может вызвать много вопросов из-за своих знаков вопросов:

Hibernate: insert into fruit_table (fruit_name, provider_code, id_fruit) values (?, ?, ?)
Давайте мыслить логически: Во-первых мы видим слово Hibernate, значит этот скрытный парень приложил тут свою мохнатую лапу. Прочитав про него в интернете, мы узнаем, что мистер Hiber это реализация ORM-модели. Объектно-реляционная модель описывает отношения между программными объектами и записями в БД. Зафиксировали эту мысль, продолжаем мыслить логически: С одной стороны, у нас есть объект FruitEntity у него есть три поля: Integer id; String fruitName; Integer providerCode. С другой стороны, у нас есть таблица в БД fruit_table с полями id_fruit тип integer, fruit_name тип varchar(255), provider_code тип integer. Грубо говоря Hibernate берет объект FruitEntity, вытаскивает значения полей объекта и записывает их в соответствующие поля таблицы. У меня к вам вопрос: Смотрите, в классе InitiateUtils мы реализовали заполнение таблицы фруктов, но почему то мы тут задали значение только двум полям, где третье?

new FruitEntity()
        .setFruitName("Fruit1")//раз
        .setProviderCode(Math.abs(new Random().nextInt() % 10)),//два
                                            //три???
Уверен вы разберетесь в этом сами, к тому же мы мельком касались этого вопроса в позапрошлой статье. Для начала разберитесь какого поля тут нет, а дальше вы все поймете. Ну что ж, Hiber молодец, нагенерировал за нас кучу запросов. Но и мы не лыком шиты, давайте в классе FruitService реализуем еще несколько методов из интерфейса JpaRepository<>

//возвращает запись из таблицы по id
public Optional<FruitEntity> getById(Integer id){
   return fruitRepository.findById(id);
}

//удаляет запись из таблицы по id
public void delById(Integer id){
    fruitRepository.deleteById(id);
}

//возвращает true или false при поиске в таблице Фруктов объекта который соответствует типу FruitEntity или принадлежит к типу объекта который наследуется от FruitEntity
public Boolean exist(Example<? extends FruitEntity> example){
    return fruitRepository.exists(example);
}
Реализуйте такие же методы в классе ProviderService. Затем используйте их в классе InitiateUtils для FruitEntity и ProviderEntity, результат выведете в консоль. (Кстати, если вы не знали то быстро написать “System.out.println()” можно если набрать sout и нажать ентер, таже штука работает с “public static void main(String[] args){}” достаточно набрать psvm ну и так далее). Я думаю вы уже справились и мы готовы идти дальше. Давайте зайдем в интерфейс FruitRepository и начнем в нем набирать (именно набирать а не копировать) следующий метод: List<FruitEntity> f Должна получиться следующая штука Spring — это не страшно, или как задать вопрос БД - 2 Просто называете метод как будто составляете запрос: findById(Integer id) - найдет объект по id; countFruitEntityByFruitName(String name) - посчитает количество фруктов с определенным именем; Это запросы, генерируемые по имени метода обязательно почитайте про них и реализуйте в классе FruitService метод between(Integer from, Integer to) для поиска List<FruitEntity> по значениям поля provider_code входящим в определенный интервал, результат работы выведете в консоль. Например: найти все фрукты, у которых номер поставщика находиться между 5 и 7. Не торопитесь читать дальше, пока не реализуете метод, это не долго. Как вы могли прочитать в статье про запросы по имени метода: «Все запросы так не напишешь, но простые можно.» Для более сложных запросов используется аннотация @Query и вместо SQL здесь используется JPQL(тоже возмите на заметку эту статью), для нашего проекта можно сделать JOIN запросы, так:

 package ru.java.rush.repositories;

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

import java.util.List;

@Repository
public interface FruitRepository extends JpaRepository<FruitEntity,Integer> {

    @Query("select f.fruitName, p.providerName from  FruitEntity f left join ProviderEntity p on f.providerCode = p.id")
    List<String> joinSting();

}
Стандартный SQL запрос был бы таким: "select fruit_table.fruit_name, provider_table.provider_name from fruit_table left join provider_table on fruit_table.provider_сode = provider_table.id". Здесь легко можно установить соответствие: fruit_table это FruitEntiy f, где FruitEntiy это тип переменной, f это ее наименование, то есть SQL работает с таблицами и полями, а JPQL с объектами и их полями. Опять же fruit_table.fruit_name это f.fruitName; Так как мы работаем с объектами то можем и объект вывести: Напишем еще один метод FruitRepository

@Query("select f from  FruitEntity f  join ProviderEntity p on f.providerCode = p.id")
List<FruitEntity> joinFruit();
Реализуем оба метода в классе FruitService

public List<String> joinString(){
   return fruitRepository.joinSting();
}

public List<FruitEntity> joinFruit(){
    return fruitRepository.joinFruit();
}
Звучит не плохо, но все равно для сложных запросов используют старый добрый SQL

    @Query(
            value = "select fruit_table.fruit_name, provider_table.provider_name from  fruit_table  join provider_table on fruit_table.provider_code = provider_table.id_provider",  //по идее эту портянку надо засунуть в какой нибудь  Enum
            nativeQuery = true) //нужно только пометить только nativeQuery = true
    ListList<String> joinSqlFruit();
И используем их всех в классе InitiateUtils

System.out.println("\nТаблица фруктов и их поставщиков");
for (String join : fruitService.joinString()) {
    System.out.println(join);
}

System.out.println("\nТаблица фруктов и их поставщиков");
for (FruitEntity join : fruitService.joinFruit()) {
    System.out.println(join);
}

System.out.println("\nТаблица фруктов и их поставщиков");
        for (String join : fruitService.joinSqlFruit()) {
            System.out.println(join);
        }
Запускаем проект и видим в консоли новые логи: Таблица фруктов и их поставщиков Fruit1,null Fruit2,Provider5 Fruit3,Provider2 Fruit4,Provider5 Fruit5,null Fruit6,null Fruit7,null Fruit8,null Fruit9,null Таблица фруктов и их поставщиков FruitEntity(id=2, fruitName=Fruit2, providerCode=5) FruitEntity(id=3, fruitName=Fruit3, providerCode=2) FruitEntity(id=4, fruitName=Fruit4, providerCode=5) Таблица фруктов и их поставщиков Fruit2,Provider5 Fruit3,Provider2 Fruit4,Provider5 Да, если вам уже надоели "псевдо" и просто SQL запросы в консоли, можете в ямл файле вернуть false на место. Почему в первой таблице есть null, это вы узнаете если почитаете про JOIN SQL . И так, с запросами к БД мы закончили, я уверен, что у вас осталось много вопросов, но я надеюсь, что вы будете искать на них ответы, пути поиска я попытался подсветить. Давайте попытаемся с резюмировать все что мы узнали за это время: 1. Можно запустить веб сервер на Spring и это не сложно. 2. Для понимания как это все работает необходимо погрузиться в теорию. Про книгу Статья про спринг Статья про полезные вещи 3. А что бы понимание перешло в физический код надо кодить, и прежде чем идти дальше, набейте руку на простых проектах на spring-boot. И лучше не копировать написанный код, а переписывать его. Я выложу проект, который мы с вами пилили сюда, но буду надеяться на вашу сознательность и уверен вы не будете бездумно копипастить. Ссылка на репозиторий git clone https://FromJava@bitbucket.org/FromJava/jd.git Для тех кто не знает как пользоваться этой ссылкой Рекомендую реализовать два тренировочных проекта: Проект про покраску машин: Первый класс: CarEntity{ Integer id; String modelName; String color; } Второй класс: ColorEntity{ Integer id; String color; Integer price; } Наполните базу (придумайте реалистичные названия так будет легче для понимания), реализуйте: , entity, repositories, services, создайте стандартные и межтабличные запросы (Сколько стоит покраска BMW в красный цвет? Какой цвет самый дорогой? Напишите модели в консоль в алфавитном порядке и т.д.); Проект про библиотеку: Первый класс: BookEntity{ Integer id; String nameBook; Integer yearCreat; Integer autorId; } Второй класс: AutorEntity{ Integer id; String firstNameAutor; String lastNameAutor; } Наполните базу (придумайте реалистичные названия так будет легче для понимания), реализуйте: entity, repositories, services, создайте стандартные и межтабличные запросы (Кто написал какую книгу? Какая книга написана раньше всех? Какие книги были написаны с 1800 по 1900? Кто из авторов написал больше всего книг?); Примеры для заполнения базы проекта "Библиотека" Таблица книг BookEntity(id=1, nameBook=Горе от ума, yearCreat=1824, authorId=1) BookEntity(id=2, nameBook=Война и мир, yearCreat=1863, authorId=2) BookEntity(id=3, nameBook=Мцыри, yearCreat=1838, authorId=3) BookEntity(id=4, nameBook=Евгений Онегин, yearCreat=1833, authorId=4) Таблица авторов AuthorEntity(id=1, firstNameAuthor=Александр, lastNameAuthor=Грибоедов) AuthorEntity(id=2, firstNameAuthor=Лев, lastNameAuthor=Толстой) AuthorEntity(id=3, firstNameAuthor=Михаил, lastNameAuthor=Лермонтов) AuthorEntity(id=4, firstNameAuthor=Александр, lastNameAuthor=Пушкин) Всем удачи, еще увидимся!
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Василий Бабин Уровень 28, Москва, Россия Expert
8 августа 2021
Я думаю вы уже справились, и мы готовы идти дальше. Давайте зайдем в интерфейс FruitRepository и начнем в нем набирать (именно набирать, а не копировать) следующий метод: List<FruitEntity> f Должна получиться следующая штука
Магия не происходит, т.е. происходит, но только со стандартными методами. Хотя я попробовал реализовать это, и оно сработало (но, как-то, мне так показалось, криво) как не странно. В общем я в недоумении. Возможном по тому, что у меня Идея комьюнити.