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

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

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

Полный перечень см. здесь

А если нужна пагинация?

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