Занятие №9
Spring Data
Губайдулин
Константин
О себе
Губайдулин Константин
Опыт работы:
Технологии:
- Фронтенд: Adobe Flex, Angular
- Бэкенд: Intersystems Cache, Intersystems Ensemble, Java Spring, Camunda, PostgreSQL
- Мобильная разработка: Adobe AIR, Flutter, SailfishOS
Личное:
- Не женат
- Музыка, кино и спорт
- На майских праздниках участвую в туристическом слёте "Сухая Мана"
- Старший разработчик
- В СИС с 2015 года
План занятия
- Spring Data. Мотивация использования
- Основные элементы
- Репозиторий
- Пагинация и сортировка
- Поиск по образцу
- Спецификации
- Кастомные запросы
Мотивация
Вспомним прошлое занятие по JPA и Hibernate.
Чтобы работать с данными, мы должны:
- настраивать соединение
- работать с менеджером сущностей
- писать код по сохранению
А может это сделает кто-нибудь другой?
Spring Data - Да!
Какие суперсилы есть у Spring Data?
Вы просто пишете интерфейс репозитория
И все..
Подключаем зависимость
dependencies {
// ...
implementation
'org.springframework.boot:spring-boot-starter-data-jpa'
// ...
}
Постановка задачи
- У нас есть книга.
- У книги есть название, год выпуска.
- У книги может быть несколько авторов.
Схема данных
Создаем сущность Book
@Entity
public class Book {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String title;
@Column
private String description;
@Column
private Integer year;
}
Создаем репозиторий
public interface BookRepository
extends CrudRepository<Book, Long> {
}
Domain Drive Design см. здесь
Реализуем репозиторий?
@SpringBootApplication
@EnableJpaRepositories(basePackages="pro.sisit.unit9.data")
public class SpringDataApplication {
CRUD Repository - источник базовых операций
public interface CrudRepository<T, ID>
extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
Create Read Update Delete aka CRUD
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);
}
}
Проверяем CRUD
@Test
public void testSave() {
Book book = new Book();
book.setTitle("Буратино");
book.setYear(1936);
bookRepository.save(book);
boolean founded = false;
for (Book iteratedBook : bookRepository.findAll()) {
if (iteratedBook.getTitle().equals("Буратино")
&& iteratedBook.getId() > 0) {
founded = true;
}
}
Assert.assertTrue(founded);
}
Как найти книгу по году?
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 "предполагает" выборку по названию метода и параметрам.
Как Spring Data это делает?
List<Book> findByTitleAndYear(String title, int year);
наименования полей сущности
Ключевые слова
Параметры метода
тип результата
Поддерживаемые ключевые слова
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname |
|
Or | findByLastnameOrFirstname |
|
Is,Equals | findByFirstname, findByFirstnameIs, findByFirstnameEquals | … where x.firstname = ?1 |
Полный перечень см. здесь
А если нужна пагинация?
PagingAndSortingRepository
public interface BookRepository
extends CrudRepository<Book, Long>,
PagingAndSortingRepository<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 буквами?
public interface BookRepository
extends CrudRepository<Book, Long>,
JpaRepository<Book, Long> {
}
public List<Book> findSame(Book book) {
return bookRepository.findAll(Example.of(book));
}
- Попробуем поиск по образцу
Включаем логирование и проверяем работу
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
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
А если нужен хитрый запрос?
Specification API
public interface BookRepository
extends CrudRepository<Book, Long>,
JpaSpecificationExecutor<Book> {
}
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);
}
}
Добавляем спецификации
public List<Book> FindInRange(Book book) {
return bookRepository.findAll(
BookSpecifications.byYearRange(1800, 1900));
}
Использование спецификаций
Если не хватает Specification API?
@Query - jpql
@Query("select aob.book from "
+ "AuthorOfBook aob "
+ "join aob.author "
+ "where aob.author.lastname = ?1")
List<Book> findByAuthor(String lastname);
Про jpql см. здесь
Превращается в:
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
- чистый SQL
- можете вызывать хранимые процедуры и функции базы данных
Что делать, когда башня абстракций падает?
Создаем отдельный интерфейс
Пишем свою имплементацию
(имя репозитория + Impl)
public interface BookComplexQueryRepository {
List<Book> complexQueryMethod();
}
@Repository
public class BookComplexQueryRepositoryImpl
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,
bookRepository.complexQueryMethod().size());
}
Итоги
- Spring Data - невероятно мощный инструмент.
- Количество инфраструктурного кода - минимально.
- При необходимости вы можете написать запросы самостоятельно.
Где еще узнать о Spring Data
Домашнее задание
- Добавить сущность "покупатель" с атрибутами имя и строка адреса
- Добавить сущность "купленная книга", у которой есть атрибуты покупатель, стоимость, и книга
- Реализовать репозитории и бизнес-логику:
- операция покупки книги покупателем
- подсчет общей стоимости продаж по книге
- подсчет общей стоимости купленных книг по покупателю
Спасибо за внимание!
JavaSIS #3.20 Занятие 9
By kgubaidulin
JavaSIS #3.20 Занятие 9
Spring Data
- 265