JavaRush /Java блог /Random UA /Spring - це не страшно, або як зрозуміти, що конкретно ма...
Павел
11 рівень

Spring - це не страшно, або як зрозуміти, що конкретно мала на увазі БД

Стаття з групи Random UA
ЗМІСТ ЦИКЛУ СТАТЕЙ До БД, як до справжньої жінки, потрібен свій підхід: мало того, що питати її необхідно по-своєму, то ще й над відповідями доведеться подумати. В одній з попередніх статей ви як практика продавали проектик про книги. Ось його й візьмемо за основу. Якщо ще не зробабо, то давайте реалізуємо скелет проекту якнайшвидше: git clone https: //FromJava@bitbucket.org/FromJava/book.git https://bitbucket.org/FromJava/book/src/master/ подивіться його структуру, запустіть. Далі працюватимемо з ним. Пам'ятаєте цей запис із статті про запити до БД?
@Query("select f.fruitName, p.providerName from  FruitEntity f left join ProviderEntity p on f.providerCode = p.id")
    List<String> joinSting();
Для бібліотеки зробимо те саме. Заходимо в BookRepository і пишемо:
@Query("select b.nameBook, a.firstNameAuthor, a.lastNameAuthor, b.yearCreat
from  AuthorEntity a left join BookEntity b on a.id = b.authorId")
List<String> joinBookString();
Реалізуємо метод у BookService :
public List<String> joinBookSting() {
    return bookRepository.joinBookString();
}
Використовуємо його в InitialUtils і виведемо в консоль:
System.out.println("\nТаблица книг и их авторов ,через строку");
for(String book: bookService.joinBookSting()){
    System.out.println(book);
}
Результат:
Таблиця книг та їх авторів Горе від розуму,Олександр,Грибоєдов,1824 Війна і мир,Лев,Толстой,1863 Мцирі,Михайло,Лермонтов,1838 Євгеній Онєгін,Олександр,Пушкін,1833
Думаю, багато хто з вас уже зрозумів, що рядок — це не найзручніший формат для роботи, і, напевно, багато хто вже намагався переробити запит, щоб отримати об'єкт. Давайте оголосимо в репозиторії книг BookService новий метод joinBookObj() з тим самим запитом, але замість String поставимо Object[] :
@Query("select b.nameBook, a.firstNameAutor, a.lastNameAutor,
b.yearCreat from  AutorEntity a left join BookEntity b on a.id = p.autorId")
    List<Object[]> joinBookObj ();
Реалізуємо його в BookService :
public List<Object[]> joinBookObj() {
    return bookRepository.joinBookObj();
}
І використовуємо в InitialUtils :
System.out.println("\nТаблица книг и их авторов, нечитаемый об'єкт");
for(Object book: bookService.joinBookObj()){
    System.out.println(book);
}
Ура, ми отримали об'єкт, тільки виведення в консоль цього запису зовсім не тішить.
Таблиця книг та їх авторів, нечитаний об'єкт [Ljava.lang.Object; @ 48f2054d [Ljava.lang.Object; @ 4b3a01d8 [Ljava.lang.Object; @ 19fbc594 [Ljava.lang.Object;
Та й не зрозуміло, як працювати з цими об'єктами далі. Настав час маппит і використовувати StreamAPI (спокій). Нагадаю, у нашому випадку мапінг – це конвертація одного об'єкта в інший. Один об'єкт вже є – це, наприклад, [Ljava.lang.Object;@48f2054d6 , він є масивом об'єктів, з наступними елементами Object[b.nameBook , a.firstNameAutor , a.lastNameAutor , b.yearCreat] . А інший об'єкт із полями аналогічними елементам масиву ми зробимо самі. У пакеті entities створимо клас BookValueEntity :
package ru.java.rush.entities;

import lombok.Data;
import lombok.experimental.Accessors;

import javax.persistence.Entity;
import javax.persistence.Id;

@Accessors(chain = true)
@Entity
@Data
public class BookValueEntities {

    @Id
    Integer id;

    String nameBook;

    String firstNameAuthor;
Тобто ми написали клас, який містить поля, аналогічні полям, які запитують у БД. Тепер у BookService реалізуємо сам мапінг із використанням стриму. Перепишіть цей метод у місці з коментарями, прочитайте їх, спробуйте зрозуміти, що робить цей метод на кожному етапі виконання.
public List<BookValueEntities> bookValueEntitiesList() {
    List<Object[]> objects = bookRepository.joinBookObj();//положим ответ от БД в переменную с типом Лист массивов Object-ов

    List<BookValueEntities> bookValueEntities = new ArrayList<>();//создадим лист конечных об'єктов

    objects//берем переменную типа List<Object[]> (Лист массивов Object-ов), с ответом БД
            .stream()//превращаем Лист, состоящий из массивов Object-ов в стрим
            .forEach(//фор ич - терминальный оператор, выполняет указанное действие для каждого елемента стрима
                    //дальше идет лямбда, она говорит фор ичу - что делать для каждого елемента стрима
                    (obj) ->//объявляем(называем) переменную "obj" ей будут присваиваться об'єкты стрима (массивы Object-ов)
                    {//так як запись в лямбде у нас в несколько строк, ставим {}
                        bookValueEntities.add(//фор ич возмет "obj" и добавит в List<BookValue>, предварительно сделав маппинг
                                new BookValueEntities()//создаем об'єкт BookValueEntities
                                        //ниже происходит собственно маппинг
                                        //поля(элементы) "obj" записываются в соответсвующие поля созданного BookValueEntities
                                        //так як поле "obj" имеет тип Object  необходимо его привести к типу поля об'єкта BookValueEntities т.е. String
                                        .setNameBook((String) obj[0])//записываем данные из одного поля в другое, [0] - значит первый элемент в массиве Object-ов
                                        //так як поле "obj" имеет тип Object  необходимо его привести к типу поля об'єкта BookValue т.е. String
                                        .setFirstNameAuthor((String) obj[1])//записываем данные из одного поля в другое, [1] - значит второй элемент в массиве Object-ов
                                        //так як поле "obj" имеет тип Object  необходимо его привести к типу поля об'єкта BookValue т.е. String
                                        .setLastNameAuthor((String) obj[2])//записываем данные из одного поля в другое, [2] - значит третий элемент в массиве Object-ов
                                        //так як поле "obj" имеет тип Object  необходимо его привести к типу поля об'єкта BookValue т.е. Integer
                                        .setYearCreat((Integer) obj[3])//записываем данные из одного поля в другое, [3] - значит четвертый элемент в массиве Object-ов
                        );
                    }
            );
    return bookValueEntities;
}
Реалізуємо виведення в консоль в InitiateUtils :
System.out.println("\nТаблица книг и их авторов , через стрим");
for(BookValueEntities book: bookService.bookValueEntitiesList()){
    System.out.println(book);
}
Натискаємо виконати. Отримуємо висновок:
Таблиця книг та їх авторів, через стрім BookValueEntities(id=null, nameBook=Горе від розуму, firstNameAuthor=Олександр, lastNameAuthor=Грибоєдів, yearCreat=1824) BookValueEntities(id=null, nameBook=Війна та мир Толстой, yearCreat=1863) BookValueEntities(id=null, nameBook=Мцирі, firstNameAuthor=Михайло, lastNameAuthor=Лермонтів, yearCreat=1838) BookValueEntities(id=null, nameBook=Євген н, yearCreat=1833 )
Із цими об'єктами тепер можна нормально працювати. Вам не подумати: чому id = null, і як зробити? щоб був не null? Як говорилося в попередніх статтях, для складних, зокрема міжтабличних запитів застосовують SQL. Погляньмо, що можна з цим зробити. Для початку переробимо запит на отримання «Таблиці книг та їх авторів» у SQL і покладемо цей запит у фінальну змінну у class BookService .
private final String SQL_COMPARISON = "select BOOKENTITY.id_book, BOOKENTITY.name_book, AUTHORENTITY.first_name, AUTHORENTITY.last_name,BOOKENTITY.year_creat from  " +
        "AUTHORENTITY left join BOOKENTITY on AUTHORENTITY.id_author = BOOKENTITY.author_id";
COMPARISON російською – зіставлення. Давайте створимо в entities клас для порівняння цього запиту:
package ru.java.rush.entities;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;


@Data
@Entity
public class BookValueEntitiesComparison {

    @Id
    @Column(name = "id_book")//назвали поле як в запите
    Integer id;

    @Column
    String nameBook;//поле и так называется як в запите, потому что Hibernate сгенерирует для него ім'я сам (name_book)

    @Column(name = "first_name")//назвали поле як в запите
    String firstNameAuthor;

    @Column(name = "last_name")//назвали поле як в запите
    String lastNameAuthor;

    @Column
    Integer yearCreat; //поле и так называется як в запите
Для реалізації методу в class BookService нам потрібно вивести на світ божий EntityManager . Як очевидно з назви, це начальник над сутностями. Запишемо в class BookService змінну.
private final EntityManager entityManager;
Теж мені начальник знайшовся, зараз ми його збудуємо. Для цього в class BookService реалізуємо метод читання запиту:
public List<BookValueEntitiesComparison> bookValueEntitiesComparisonList() {
    return entityManager //зовем менеджера и начинаем ему указывать
            .createNativeQuery(//для начала создай пожалуйста "чистый"(native) SQL запит
                    SQL_COMPARISON,//из этой строковой переменной возьми запит
                    BookValueEntitiesComparison.class)// ответ замаппить в этот класс
            .getResultList();//а результат мне заверни в лист!!! И побыстрее!!!Шнеля, шнеля!!!
}
Начебто потараканив виконувати наші вказівки, давайте глянемо як він упорався. InitiateUtils виведемо в консоль:
System.out.println("\nТаблица книг и их авторов, через сопоставление");
for(Object book: bookService.bookValueEntitiesComparisonList()){
    System.out.println(book);
}
Таблиця книг та їх авторів, через зіставлення BookValueEntitiesComparison(id=1, nameBook=Горе від розуму, firstNameAuthor=Олександр, lastNameAuthor=Грибоєдів, yearCreat=1824) BookValueEntitiesComparison(id=2, name hor= Толстой, yearCreat=1863) BookValueEntitiesComparison(id=3, nameBook=Мцирі, firstNameAuthor=Михайло, lastNameAuthor=Лермонтів, yearCreat=1838) BookValueEntitiesComparison(id=4, name Author=Пушкін, yearCreat=1833 )
Молодець, упорався. Як він це зробив? Просто зіставив найменування полів сутності-класу, що ми йому вказали, та результату запиту. Можна виконати це завдання іншим способом, через анотації: У class BookService створюємо нову змінну із запитом:
private final String SQL_ANNOTATION = "select  BOOKENTITY.id_book as id_book_value, BOOKENTITY.name_book, AUTHORENTITY.first_name, AUTHORENTITY.last_name,BOOKENTITY.year_creat from  " +
        "AUTHORENTITY left join BOOKENTITY on AUTHORENTITY.id_author = BOOKENTITY.author_id";
Зверніть увагу: він трохи змінився, знайдіть різницю. Створимо в entities окремий клас, куди будемо маппити.
package ru.java.rush.entities;


import lombok.Data;

import javax.persistence.*;

@SqlResultSetMapping(
        name = "BookValueMapping",//даем название нашему маппингу
        entities = @EntityResult(
                entityClass = BookValueEntitiesAnnotation.class,//указываем конечный класс куда будем маппить
                fields = {//в блоке полей указываем соответствие полей(name =) конечного класса и полей(colum =) результата запита
                        @FieldResult(name = "id", column = "id_book_value"),
                        @FieldResult(name = "nameBook", column = "name_book"),
                        @FieldResult(name = "firstNameAuthor", column = "first_name"),
                        @FieldResult(name = "lastNameAuthor", column = "last_name"),
                        @FieldResult(name = "yearCreat", column = "year_creat")
                }
        )
)
@Data
@Entity
@Table(name = "BookValueEntitiesAnnotation")
public class BookValueEntitiesAnnotation {

    @Id
    @Column
    Integer id;

    @Column
    String nameBook;

    @Column
    String firstNameAuthor;

    @Column
    String lastNameAuthor;

    @Column
    Integer yearCreat;
}
У class BookService реалізуємо метод:
public List<BookValueEntitiesAnnotation> bookValueEntitiesAnnotationList() {
        return entityManager//як и в прошлый раз зовем начальника
                .createNativeQuery(//давай нам чистый SQL запит
                        SQL_ANNOTATION,//вот тебе текст запита
                        "BookValueMapping")//вот тебе ім'я нашего маппинга
                .getResultList();//и як обычно заверни нам в лист!!! Ты еще тут?
    }
}
Бачабо, яка солідна назва методу вийшла? Чим довше ви називаєте метод, тим з великою повагою ставляться до вас колеги 😁 (частка правди тут є). Проконтролюємо роботу менеджера як звичайно через InitiateUtils і виведемо в консоль:
System.out.println("\nТаблица книг и их авторов, через аннотацию");
for(Object book: bookService.bookValueEntitiesAnnotationList()){
    System.out.println(book);
}
Результат аналогічний попереднім:
Таблиця книг та їх авторів, через анотацію BookValueEntitiesAnnotation(id=1, nameBook=Горе від розуму, firstNameAuthor=Олександр, lastNameAuthor=Грибоєдів, yearCreat=1824) BookValueEntitiesAnnotation(id=2,Name = Толстой, yearCreat=1863) BookValueEntitiesAnnotation(id=3, nameBook=Мцирі, firstNameAuthor=Михайло, lastNameAuthor=Лермонтов, yearCreat=1838) BookValueEntitiesAnnotation(id=4, nameBook= hor=Пушкін, yearCreat=1833 )
Ну і останній варіант за списком, але не за значенням, це зробити мапінг через файл XML-відображення. Файл зіставлення за промовчанням називається orm.xml і буде використовуватися автоматично, якщо він буде доданий до META-INF каталогу файлу jar. Як ви можете бачити нижче, це зіставлення дуже схоже на зіставлення на основі анотацій, яке ми обговорювали раніше.
<sql-result-set-mapping name="BookMappingXml">
    <entity-result entity-class="ru.java.rush.entities
.BookValueEntitiesAnnotation ">
        <field-result name="id" column=" id_book_value "/>
        <field-result name=" nameBook " column=" name_book "/>
        <field-result name=" firstNameAuthor " column=" first_name"/>
        <field-result name=" yearCreat " column=" year_creat "/>
    </entity-result>
</sql-result-set-mapping>
На цьому все! На поточному рівні вам цілком вистачить найпростішого мапінгу, про інші методи треба просто знати. Ось тут у статті буржуазною мовою описані варіації методів роботи з відповідями від БД, які ми розібрали. За мову не переживайте, натисніть перекласти сторінку і вийде переклад. Я це точно знаю, тому що цю статтю підготовлено на її основі, а в школі я вчив німецьку. Для практики:
  1. Додайте до проекту нову сутність, сховище книг:
    BookStorageEntity
    Integer id;
    Integer bookId;
    String status; //книга выдана або нет
  2. Наповніть таблицю:
    Id = 1 bookId = 1 status = Видано;
    Id = 2 bookId = 2 status = У сховищі;
    Id = 3 bookId = 3 status = На реставрації;
    Id = 4 bookId = 4 status = Видано;
  3. Створіть BookStorageRepository та BookStorageService .
  4. Створіть міжтабличні JPQL та SQL запити, які виводять найменування книги та видана вона чи ні.
  5. Для JPQL реалізуйте мапінг за першим варіантом, для SQL - за другим і третім.
Бувайте усі! Побачимося!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ