Занятие №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

… where x.lastname = ?1 and x.firstname = ?2

Or findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

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 Сибирские интеграционные системы