[Программирование] Введение в Spring Data JDBC (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Для будущих студентов курса "Java Developer. Professional" подготовили перевод полезного материала.
Также приглашаем принять участие в открытом уроке на тему "Введение в Spring Data jdbc"
Spring Data JDBC был анонсирован в 2018 году. Целью было предоставить разработчикам более простую альтернативу JPA, продолжая при этом следовать принципам Spring Data. Подробнее узнать о мотивах, лежащих в основе проекта, вы можете в документации. В этой статье я покажу несколько примеров использования Spring Data JDBC. Здесь не будет подробного руководства, но, надеюсь, приведенной информации хватит, чтобы попробовать его самостоятельно. Очень хорошо, если вы уже знакомы со Spring Data JPA. Исходный код вы можете найти в github.Для быстрого старта я использовал этот шаблон.Предварительная подготовкаИз зависимостей нам нужны data-jdbc — стартер, flyway для управления схемой и драйвер postgres для подключения к базе данных.
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.flywaydb:flyway-core'
runtimeOnly 'org.postgresql:postgresql'
}
Далее настраиваем приложение для подключения к базе данных:
# application.yml
spring:
application:
name: template-app
datasource:
url: jdbc:postgresql://localhost:5432/demo_app?currentSchema=app
username: app_user
password: change_me
driver-class-name: org.postgresql.Driver
Маппинг сущностейДля этого примера будем использовать следующую таблицу:
create table book (
id varchar(32) not null,
title varchar(255) not null,
author varchar(255),
isbn varchar(15),
published_date date,
page_count integer,
primary key (id)
);
И соответствующий java-класс (обратите внимание, что @Id импортируется из org.springframework.data.annotation.Id):
// Book.java
public class Book {
@Id
private String id;
private String title;
private String author;
private String isbn;
private Instant publishedDate;
private Integer pageCount;
}
Однако, если мы запустим тест:
// BookRepositoryTest.java
@Test
void canSaveBook() {
var book = Book.builder().author("Steven Erikson").title("Gardens of the Moon").build();
var savedBook = bookRepository.save(book);
assertThat(savedBook.getId()).isNotBlank();
assertThat(savedBook.getAuthor()).isEqualTo(book.getAuthor());
assertThat(savedBook.getTitle()).isEqualTo(book.getTitle());
assertThat(savedBook).isEqualTo(bookRepository.findById(savedBook.getId()).get());
}
То увидим ошибку — ERROR: null value in column "id" violates not-null constraint. Это происходит, потому что мы не определили ни способ генерации id ни значение по умолчанию. Поведение Spring Data JDBC в части идентификаторов немного отличается от Spring Data JPA. В нашем примере нужно определить ApplicationListener для BeforeSaveEvent:
// PersistenceConfig.java
@Bean
public ApplicationListener<BeforeSaveEvent> idGenerator() {
return event -> {
var entity = event.getEntity();
if (entity instanceof Book) {
((Book) entity).setId(UUID.randomUUID().toString());
}
};
}
Теперь тест пройдет, потому что поле Id заполняется. Полный список поддерживаемых событий жизненного цикла смотрите в документации.Методы запросов Одной из особенностей проектов Spring Data является возможность определять методы запросов в репозиториях. Spring Data JDBC использует здесь несколько иной подход. Для демонстрации определим метод запроса в BookRepository:
Optional<Book> findByTitle(String title);
И если запустим соответствующий тест:
@Test
void canFindBookByTitle() {
var title = "Gardens of the Moon";
var book = Book.builder().author("Steven Erikson").title(title).build();
var savedBook = bookRepository.save(book);
assertThat(bookRepository.findByTitle(title).get()).isEqualTo(savedBook);
}
Получим ошибку — Caused by: java.lang.IllegalStateException: No query specified on findByTitle. В настоящее время Spring Data JDBC поддерживает только явные запросы, задаваемые через @Query. Напишем sql-запрос для нашего метода:
@Query("select * from Book b where b.title = :title")
Optional<Book> findByTitle(@Param("title") String title);
Тест пройден! Не забывайте об этом при создании репозиториев.Примечание переводчика: в Spring Data JDBC 2.0 появилась поддержка генерации запросов по именам методов. СвязиДля работы со связями Spring Data JDBC также использует другой подход. Основное отличие в том, что отсутствует ленивая загрузка. Поэтому если вам не нужна связь в сущности, то просто не добавляйте ее туда. Такой подход основан на одной из концепций предметно-ориентированного проектирования (Domain Driven Design), согласно которой сущности, которые мы загружаем, являются корнями агрегатов, поэтому проектировать надо так, чтобы корни агрегатов тянули за собой загрузку других классов.Один-к-одномуДля связей "один-к-одному" и "один-ко-многим" используется аннотация @MappedCollection. Сначала посмотрим на "один-к-одному". Класс UserAccount будет ссылаться на Address. Вот соответствующий sql:
create table address
(
id varchar(36) not null,
city varchar(255),
state varchar(255),
street varchar(255),
zipcode varchar(255),
primary key (id)
);
create table user_account
(
id varchar(36) not null,
name varchar(255) not null,
email varchar(255) not null,
address_id varchar(36),
primary key (id),
constraint fk_user_account_address_id foreign key (address_id) references address (id)
);
Класс UserAccount выглядит примерно так:
// UserAccount.java
public class UserAccount implements GeneratedId {
// ...other fields
@MappedCollection(idColumn = "id")
private Address address;
}
Здесь опущены другие поля, чтобы показать маппинг address. Значение в idColumn — это имя поля идентификатора класса Address. Обратите внимание, что в классе Address нет ссылки на класс UserAccount, поскольку агрегатом является UserAccount. Это продемонстрировано в тесте:
//UserAccountRepositoryTest.java
@Test
void canSaveUserWithAddress() {
var address = stubAddress();
var newUser = stubUser(address);
var savedUser = userAccountRepository.save(newUser);
assertThat(savedUser.getId()).isNotBlank();
assertThat(savedUser.getAddress().getId()).isNotBlank();
var foundUser = userAccountRepository.findById(savedUser.getId()).orElseThrow(IllegalStateException::new);
var foundAddress = addressRepository.findById(foundUser.getAddress().getId()).orElseThrow(IllegalStateException::new);
assertThat(foundUser).isEqualTo(savedUser);
assertThat(foundAddress).isEqualTo(savedUser.getAddress());
}
Один-ко-многимВот sql, который будем использовать для демонстрации связи "один-ко-многим":
create table warehouse
(
id varchar(36) not null,
location varchar(255),
primary key (id)
);
create table inventory_item
(
id varchar(36) not null,
name varchar(255),
count integer,
warehouse varchar(36),
primary key (id),
constraint fk_inventory_item_warehouse_id foreign key (warehouse) references warehouse (id)
);
В этом примере на складе (warehouse) есть много товаров/объектов (inventoryitems). Поэтому в классе Warehouse мы также будем использовать @MappedCollection для InventoryItem:
public class Warehouse {
// ...other fields
@MappedCollection
Set<InventoryItem> inventoryItems = new HashSet<>();
public void addInventoryItem(InventoryItem inventoryItem) {
var itemWithId = inventoryItem.toBuilder().id(UUID.randomUUID().toString()).build();
this.inventoryItems.add(itemWithId);
}
}
public class InventoryItem {
@Id
private String id;
private String name;
private int count;
}
В этом примере мы устанавливаем поле id во вспомогательном методе addInventoryItem. Можно также определить ApplicationListener для класса Warehouse с обработкой BeforeSaveEvent, в котором установить поле id для всех InventoryItem. Вам не обязательно делать в точности так, как сделано у меня. Посмотрите тесты с демонстрацией некоторых особенностей поведения связи "один-ко-многим". Главное то, что сохранение или удаление экземпляра Warehouse влияет на соответствующие InventoryItem.В нашем случае InventoryItem не должен знать о Warehouse. Таким образом, у этого класса есть только те поля, которые описывают его. В JPA принято делать двусторонние связи, но это может быть громоздким и провоцировать ошибки, если вы забудете поддерживать обе стороны связи. Spring Data JDBC способствует созданию только необходимых вам связей, поэтому обратная связь "многие-к-одному" здесь не используется.Многие-к-одному и многие-ко-многимВ рамках этого руководства я не буду вдаваться в подробности о связях "многие-к-одному" или "многие ко многим". Я советую избегать связей "многие-ко-многим" и использовать их только в крайнем случае. Хотя иногда они могут быть неизбежны. Оба этих типа связей реализуются в Spring Data JDBC через ссылки на Id связанных сущностей. Поэтому имейте ввиду, что здесь вам предстоит еще немного потрудиться. ЗаключениеЕсли вы использовали Spring Data JPA, то большая часть из того, что я рассказал, должна быть вам знакома. Я уже упоминал ранее, что Spring Data JDBC стремится быть проще, и поэтому отсутствует ленивая загрузка. Помимо этого, отсутствует кеширование, отслеживание "грязных" объектов (dirty tracking) и сессии (session). Если в Spring Data JDBC вы загружаете объект, то он загружается полностью (включая связи) и сохраняется тогда, когда вы сохраняете его в репозиторий. Примеры, которые я показал, очень похожи на свои аналоги в JPA, но помните, что многие концепции Spring Data JPA отсутствуют в Spring Data JDBC.В целом мне нравится Spring Data JDBC. Признаю, что это может быть не лучший выбор для всех приложений, однако я бы рекомендовал его попробовать. Как человек, который в прошлом боролся с ленивой загрузкой и dirty tracking, я ценю его простоту. Я думаю, что это хороший выбор для простых предметных областей, которые не требуют большого количества нестандартных запросов.На этом пока все, спасибо за чтение! Надеюсь, вы нашли это руководство полезным и оно будет отправной точкой для использования Spring Data JDBC.
Подробнее о курсе "Java Developer. Professional".
Записаться на открытый урок "Введение в Spring Data jdbc".
===========
Источник:
habr.com
===========
===========
Автор оригинала: Lumberjack Dev
===========Похожие новости:
- [Программирование, Разработка игр, Unity] Реактивное программирование для разработчиков игр: Введение (перевод)
- [Программирование, C++, Работа с 3D-графикой, Разработка игр, CGI (графика)] Vulkan. Руководство разработчика. Рисуем треугольник (перевод)
- [Высокая производительность, Серверное администрирование] Как Netflix поддерживает надежность сервиса: ограничение нагрузки на основе приоритетов (перевод)
- [Разработка веб-сайтов, JavaScript, Программирование] Сниппет, расширение для VSCode и CLI. Часть 1
- [Программирование, Разработка игр, Unity] Представляем Owlcat Mono Profiler для Unity
- [Программирование, .NET, C#] Вышел .NET 5. И что?
- [Промышленное программирование, Программирование микроконтроллеров, Разработка под Arduino, Производство и разработка электроники] Кому в микроконтроллере жить хорошо?
- [Разработка веб-сайтов, Программирование, TypeScript] ТайпСкрип: Ох уж эта весёлая система типов
- [Программирование, Разработка под iOS, Objective C, Swift] Модуляризация iOS-приложения: зачем и как мы разбиваем Badoo на модули
- [Программирование, C++] Небольшие, но важные функции (перевод)
Теги для поиска: #_programmirovanie (Программирование), #_spring, #_spring_data, #_jdbc, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:07
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Для будущих студентов курса "Java Developer. Professional" подготовили перевод полезного материала.
Также приглашаем принять участие в открытом уроке на тему "Введение в Spring Data jdbc" // build.gradle
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.flywaydb:flyway-core' runtimeOnly 'org.postgresql:postgresql' } # application.yml
spring: application: name: template-app datasource: url: jdbc:postgresql://localhost:5432/demo_app?currentSchema=app username: app_user password: change_me driver-class-name: org.postgresql.Driver create table book (
id varchar(32) not null, title varchar(255) not null, author varchar(255), isbn varchar(15), published_date date, page_count integer, primary key (id) ); // Book.java
public class Book { @Id private String id; private String title; private String author; private String isbn; private Instant publishedDate; private Integer pageCount; } // BookRepositoryTest.java
@Test void canSaveBook() { var book = Book.builder().author("Steven Erikson").title("Gardens of the Moon").build(); var savedBook = bookRepository.save(book); assertThat(savedBook.getId()).isNotBlank(); assertThat(savedBook.getAuthor()).isEqualTo(book.getAuthor()); assertThat(savedBook.getTitle()).isEqualTo(book.getTitle()); assertThat(savedBook).isEqualTo(bookRepository.findById(savedBook.getId()).get()); } // PersistenceConfig.java
@Bean public ApplicationListener<BeforeSaveEvent> idGenerator() { return event -> { var entity = event.getEntity(); if (entity instanceof Book) { ((Book) entity).setId(UUID.randomUUID().toString()); } }; } Optional<Book> findByTitle(String title);
@Test
void canFindBookByTitle() { var title = "Gardens of the Moon"; var book = Book.builder().author("Steven Erikson").title(title).build(); var savedBook = bookRepository.save(book); assertThat(bookRepository.findByTitle(title).get()).isEqualTo(savedBook); } @Query("select * from Book b where b.title = :title")
Optional<Book> findByTitle(@Param("title") String title); create table address
( id varchar(36) not null, city varchar(255), state varchar(255), street varchar(255), zipcode varchar(255), primary key (id) ); create table user_account ( id varchar(36) not null, name varchar(255) not null, email varchar(255) not null, address_id varchar(36), primary key (id), constraint fk_user_account_address_id foreign key (address_id) references address (id) ); // UserAccount.java
public class UserAccount implements GeneratedId { // ...other fields @MappedCollection(idColumn = "id") private Address address; } //UserAccountRepositoryTest.java
@Test void canSaveUserWithAddress() { var address = stubAddress(); var newUser = stubUser(address); var savedUser = userAccountRepository.save(newUser); assertThat(savedUser.getId()).isNotBlank(); assertThat(savedUser.getAddress().getId()).isNotBlank(); var foundUser = userAccountRepository.findById(savedUser.getId()).orElseThrow(IllegalStateException::new); var foundAddress = addressRepository.findById(foundUser.getAddress().getId()).orElseThrow(IllegalStateException::new); assertThat(foundUser).isEqualTo(savedUser); assertThat(foundAddress).isEqualTo(savedUser.getAddress()); } create table warehouse
( id varchar(36) not null, location varchar(255), primary key (id) ); create table inventory_item ( id varchar(36) not null, name varchar(255), count integer, warehouse varchar(36), primary key (id), constraint fk_inventory_item_warehouse_id foreign key (warehouse) references warehouse (id) ); public class Warehouse {
// ...other fields @MappedCollection Set<InventoryItem> inventoryItems = new HashSet<>(); public void addInventoryItem(InventoryItem inventoryItem) { var itemWithId = inventoryItem.toBuilder().id(UUID.randomUUID().toString()).build(); this.inventoryItems.add(itemWithId); } } public class InventoryItem { @Id private String id; private String name; private int count; } Подробнее о курсе "Java Developer. Professional".
Записаться на открытый урок "Введение в Spring Data jdbc". =========== Источник: habr.com =========== =========== Автор оригинала: Lumberjack Dev ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:07
Часовой пояс: UTC + 5