СОДЕРЖАНИЕ ЦИКЛА СТАТЕЙ
К БД, как к истинной женщине, нужен свой подход: мало того, что спрашивать ее необходимо по-своему, так еще и над ответами придется подумать. В одной из прошлых статей вы в качестве практики реализовывали проектик про книги. Вот его и возьмём за основу.
Если еще не сделали, то давайте реализуем скелет проекта по-быстрому:
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;@2f4d32bf
Да и не понятно, как работать с этими объектами дальше.
Пришла пора маппить и использовать 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=Война и мир, firstNameAuthor=Лев, lastNameAuthor=Толстой, yearCreat=1863)
BookValueEntities(id=null, nameBook=Мцыри, firstNameAuthor=Михаил, lastNameAuthor=Лермонтов, yearCreat=1838)
BookValueEntities(id=null, nameBook=Евгений Онегин, firstNameAuthor=Александр, lastNameAuthor=Пушкин, 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, nameBook=Война и мир, firstNameAuthor=Лев, lastNameAuthor=Толстой, yearCreat=1863)
BookValueEntitiesComparison(id=3, nameBook=Мцыри, firstNameAuthor=Михаил, lastNameAuthor=Лермонтов, yearCreat=1838)
BookValueEntitiesComparison(id=4, nameBook=Евгений Онегин, firstNameAuthor=Александр, lastNameAuthor=Пушкин, 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, nameBook=Война и мир, firstNameAuthor=Лев, lastNameAuthor=Толстой, yearCreat=1863)
BookValueEntitiesAnnotation(id=3, nameBook=Мцыри, firstNameAuthor=Михаил, lastNameAuthor=Лермонтов, yearCreat=1838)
BookValueEntitiesAnnotation(id=4, nameBook=Евгений Онегин, firstNameAuthor=Александр, lastNameAuthor=Пушкин, 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>
На этом все!
На текущем уровне вам вполне хватит самого простого маппинга, об остальных методах надо просто знать.
Вот тут в статье на буржуйском языке описаны вариации методов работы с ответами от БД, которые мы разобрали. За язык не переживайте, нажмите перевести страницу и получится вполне читаемый перевод. Я это точно знаю, потому что эта статья подготовлена на ее основе, а в школе я учил немецкий.
Для практики:- Добавьте в проект новую сущность, хранилище книг:
BookStorageEntity Integer id; Integer bookId; String status; //книга выдана или нет
- Наполните таблицу:
Id = 1 bookId = 1 status = Выдана;
Id = 2 bookId = 2 status = В хранилище;
Id = 3 bookId = 3 status = На реставрации;
Id = 4 bookId = 4 status = Выдана; - Создайте BookStorageRepository и BookStorageService.
- Создайте межтабличные JPQL и SQL запросы, которые выводят наименование книги и выдана она или нет.
- Для JPQL реализуйте маппинг по первому варианту, для SQL — по второму и третьему.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ