Занятие №10
Spring Data
Кернер
Денис
План занятия
- Spring Data. Мотивация использования
- Основные элементы
- Репозиторий
- Пагинация и сортировка
- Поиск по образцу
- Спецификации
- Кастомные запросы
Мотивация
Вспомним прошлое занятие по JPA и Hibernate.
Чтобы работать с данными, мы должны настраивать соединение, работать с менеджером сущностей.
Писать код по сохранению.
А может это сделает кто-нибудь другой?
Spring Data - Да!
Какие суперсилы есть у Spring Data?
Работать с данными еще проще
Вы просто пишете интерфейс репозитория
И все..
Подключаем зависимость
dependencies {
// ...
implementation
'org.springframework.boot:spring-boot-starter-data-jpa'
// ...
}
Постановка задачи
У нас есть книга. У книги есть название, год выпуска.
У книги может быть несколько авторов.
Создаем сущность Book
@Entity
@Data
public class Book {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String title;
@Column
private String description;
@Column
private Integer year;
}
Для упрощения используем lombok. (статья как подключить)
Создаем репозиторий
public interface BookRepository
extends CrudRepository<Book, Long> {
}
Domain Drive Design см. здесь
Реализуем репозиторий?
@SpringBootApplication
@EnableJpaRepositories(
basePackages = "io.github.bael.spring.data")
public class SpringDataApplication {
CRUD работает из коробки
public class BookServiceImpl implements BookService {
private final BookRepository bookRepository;
@Override
public void save(Book book) {
bookRepository.save(book);
}
@Override
public Optional<Book> findById(Long id) {
return bookRepository.findById(id);
}
}
Create Read Update Delete aka CRUD
Проверяем CRUD
@Test
public void testSave() {
Book book = new Book();
book.setTitle("Приключения Тома Сойера");
book.setYear(1876);
bookRepository.save(book);
boolean founded = false;
for (Book iteratedBook : bookRepository.findAll()) {
if (iteratedBook.getTitle()
.equals("Приключения Тома Сойера")
&& iteratedBook.getId() > 0) {
founded = true; }
}
Assert.assertTrue(founded);
}
CRUD Repository - источник базовых операций
@NoRepositoryBean
public interface CrudRepository<T, ID>
extends Repository<T, ID> {
/**
* Saves a given entity. Use the returned
instance for further operations as
the save operation might have changed the
* entity instance completely.
*
* @param entity must not be {@literal null}.
* @return the saved entity will
* never be {@literal null}.
*/
<S extends T> S save(S entity);
Как найти книгу по году?
public interface BookRepository
extends CrudRepository<Book, Long> {
List<Book> findByYear(int year);
}
@Test
public void testFindByYear() {
Assert.assertTrue(
bookRepository.findByYear(1876)
.stream()
.anyMatch(
book ->
book.getYear() == 1876));
}
Проверяем
Как Spring Data это делает?
Ответ:
Proxy + кодогенерация
Spring "предполагает" выборку по названию метода и параметрам.
Насколько точно предполагает?
Keyword | Sample | JPQL snippet |
---|---|---|
|
findByLastnameAndFirstname |
|
|
findByLastnameOrFirstname |
|
Is,Equals |
|
|
|
|
|
|
|
|
|
|
|
Полный перечень см. здесь
А если нужна пагинация?
PagingAndSortingRepository
public interface BookRepository
extends CrudRepository<Book, Long>,
PagingAndSortingRepository<Book, Long>,
JpaRepository<Book, Long> {
public Page<Book> findAtPage(int pageIndex,
int pageNumber,
Direction direction,
String sortField) {
PageRequest request = PageRequest.of(pageIndex, pageNumber,
direction, sortField);
return bookRepository.findAll(request);
}
А если не хочется писать имя метода с 40 буквами?
select
book0_.id as id1_0_,
book0_.description as descript2_0_,
book0_.title as title3_0_,
book0_.year as year4_0_
from
book book0_
where
book0_.year=1876
@Override
public List<Book> findSame(Book book) {
return bookRepository.findAll(Example.of(book));
}
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Включаем логирование и проверяем работу
А если нужен хитрый запрос?
Specification API
public class BookSpecifications {
public static Specification<Book> byYearRange(int startYear, int finishYear) {
return yearLessThan(finishYear).and(yearGreaterThan(startYear));
}
public static Specification<Book> yearLessThan(int finishYear) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.lessThanOrEqualTo(root.get("year"), finishYear);
}
public static Specification<Book> yearGreaterThan(int startYear) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.greaterThanOrEqualTo(root.get("year"), startYear);
}
Если не хвататет Specification API?
@Query - jpql
@Query("select aob.book from "
+ "AuthorOfBook aob join aob.book "
+ "join aob.author "
+ "where aob.author.id = ?1")
List<Book> findByAuthor(Long authorId);
Превращается в:
Hibernate:
select
book1_.id as id1_2_,
book1_.description as descript2_2_,
book1_.title as title3_2_,
book1_.year as year4_2_
from
author_of_book authorofbo0_
inner join
book book1_
on authorofbo0_.book_id=book1_.id
inner join
author author2_
on authorofbo0_.author_id=author2_.id
where
authorofbo0_.author_id=?
Если jpql не ваше, есть еще @NamedNativeQuery
@NamedNativeQuery = чистый SQL
Можете вызывать хранимые процедуры и функции базы данных
Что делать, когда башня абстракций падает?
Что делать, когда башня абстракций падает?
Создаем отдельный интерфейс
Пишем свою имплементацию
(имя репозитория + Impl)
public interface BookComplexQueryRepository {
List<Book> complexQueryMethod();
}
@Repository
public class BookRepositoryImpl implements
BookComplexQueryRepository {
@Override
public List<Book> complexQueryMethod() {
...
Расширяем главный репозиторий
public interface BookRepository
extends CrudRepository<Book, Long>,
PagingAndSortingRepository<Book, Long>,
JpaRepository<Book, Long>,
BookComplexQueryRepository {
Используем в сервисе и проверяем
@Test
public void testComplexQuery() {
Assert.assertEquals(2,
bookService.complexQuery().size());
}
@Override
public List<Book> complexQuery() {
return bookRepository.complexQueryMethod();
}
Итоги
- Spring Data - невероятно мощный инструмент.
- Количество инфраструктурного кода - минимально.
- При необходимости вы можете написать запросы самостоятельно.
Где еще узнать о Spring Data
Домашнее задание
- Добавить сущность "покупатель" с атрибутами имя и строка адреса
- Добавить сущность "купленная книга", у которой есть атрибуты покупатель, стоимость, и книга
- Реализовать репозитории и бизнес-логику:
- операция покупки книги покупателем
- подсчет общей стоимости продаж по книге
- подсчет общей стоимости купленных книг по покупателю
Спасибо за внимание!
JavaSIS#19 unit 10 Spring Data!
By Dennis Kerner
JavaSIS#19 unit 10 Spring Data!
- 326