ЗМІСТ ЦИКЛУ СТАТЕЙ До БД, як до справжньої жінки, потрібен свій підхід: мало того, що питати її необхідно по-своєму, то ще й над відповідями доведеться подумати. В одній з попередніх статей ви як практика продавали проектик про книги. Ось його й візьмемо за основу. Якщо ще не зробабо, то давайте реалізуємо скелет проекту якнайшвидше: 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>
На цьому все! На поточному рівні вам цілком вистачить найпростішого мапінгу, про інші методи треба просто знати. Ось тут у статті буржуазною мовою описані варіації методів роботи з відповідями від БД, які ми розібрали. За мову не переживайте, натисніть перекласти сторінку і вийде переклад. Я це точно знаю, тому що цю статтю підготовлено на її основі, а в школі я вчив німецьку. Для практики:
- Додайте до проекту нову сутність, сховище книг:
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 - за другим і третім.