Автор оригинала: Philippe Sevestre.
1. введение
В предыдущем учебнике мы рассмотрели основы JDBI , библиотеки с открытым исходным кодом для доступа к реляционным базам данных , которая удаляет большую часть шаблонного кода, связанного с прямым использованием JDBC.
На этот раз мы посмотрим, как мы можем использовать JDBI в приложении Spring Boot . Мы также рассмотрим некоторые аспекты этой библиотеки, которые делают ее хорошей альтернативой Spring Data JPA в некоторых сценариях.
2. Настройка проекта
Прежде всего, давайте добавим соответствующие зависимости JDBI в наш проект. На этот раз мы будем использовать плагин Spring integration от JDBI, который приносит все необходимые основные зависимости . Мы также добавим плагин SQLObject, который добавляет некоторые дополнительные функции в базовый JDBI, которые мы будем использовать в наших примерах:
org.springframework.boot spring-boot-starter-jdbc 2.1.8.RELEASE org.jdbi jdbi3-spring4 3.9.1 org.jdbi jdbi3-sqlobject 3.9.1
Последнюю версию этих артефактов можно найти в Maven Central:
Нам также нужен подходящий драйвер JDBC для доступа к нашей базе данных. В этой статье мы будем использовать H2 , поэтому мы также должны добавить его драйвер в наш список зависимостей:
com.h2database h2 1.4.199 runtime
3. Создание и настройка JDBI
Мы уже видели в нашей предыдущей статье, что нам нужен экземпляр Jdbi в качестве точки входа для доступа к API JDBI. Поскольку мы находимся в весеннем мире, имеет смысл сделать экземпляр этого класса доступным в качестве компонента.
Мы будем использовать возможности автоматической настройки Spring Boot для инициализации источника данных и передачи его в @Bean -аннотированный метод, который создаст наш глобальный Jdbi экземпляр.
Мы также передадим все обнаруженные плагины и экземпляры RowMapper этому методу, чтобы они были зарегистрированы заранее:
@Configuration public class JdbiConfiguration { @Bean public Jdbi jdbi(DataSource ds, ListjdbiPlugins, List > rowMappers) { TransactionAwareDataSourceProxy proxy = new TransactionAwareDataSourceProxy(ds); Jdbi jdbi = Jdbi.create(proxy); jdbiPlugins.forEach(plugin -> jdbi.installPlugin(plugin)); rowMappers.forEach(mapper -> jdbi.registerRowMapper(mapper)); return jdbi; } }
Здесь мы используем доступный источник данных и оборачиваем его в TransactionAwareDataSourceProxy . Нам нужна эта оболочка для интеграции транзакций , управляемых Spring, с JDBI , как мы увидим позже.
Регистрация плагинов и экземпляров RowMapper проста. Все, что нам нужно сделать, это вызвать install Plugin и install RowMapper для каждого доступного JdbiPlugin и RowMapper, соответственно. После этого у нас есть полностью настроенный экземпляр Jdbi , который мы можем использовать в нашем приложении.
4. Пример домена
В нашем примере используется очень простая модель предметной области, состоящая всего из двух классов: Автопроизводитель и Модель автомобиля . Поскольку JDBI не требует никаких аннотаций в наших доменных классах, мы можем использовать простые POJOS:
public class CarMaker { private Long id; private String name; private Listmodels; // getters and setters ... } public class CarModel { private Long id; private String name; private Integer year; private String sku; private Long makerId; // getters and setters ... }
5. Создание DAOs
Теперь давайте создадим объекты доступа к данным (DAO) для наших классов домена. Плагин JDBI SQLObject предлагает простой способ реализации этих классов, который напоминает подход Spring Data к этой теме.
Нам просто нужно определить интерфейс с несколькими аннотациями, и автоматически JDBI будет обрабатывать все низкоуровневые вещи, такие как обработка соединений JDBC и создание/удаление операторов и ResultSet s :
@UseClasspathSqlLocator public interface CarMakerDao { @SqlUpdate @GetGeneratedKeys Long insert(@BindBean CarMaker carMaker); @SqlBatch("insert") @GetGeneratedKeys ListbulkInsert(@BindBean List carMakers); @SqlQuery CarMaker findById(Long id); } @UseClasspathSqlLocator public interface CarModelDao { @SqlUpdate @GetGeneratedKeys Long insert(@BindBean CarModel carModel); @SqlBatch("insert") @GetGeneratedKeys List bulkInsert(@BindBean List models); @SqlQuery CarModel findByMakerIdAndSku(@Bind("makerId") Long makerId, @Bind("sku") String sku ); }
Эти интерфейсы сильно аннотированы, поэтому давайте быстро рассмотрим каждый из них.
5.1. @UseClasspathSqlLocator
Аннотация @ UseClasspathSqlLocator сообщает JDBI, что фактические операторы SQL, связанные с каждым методом, находятся во внешних файлах ресурсов . По умолчанию JDBI будет искать ресурс, используя полное имя и метод интерфейса. Например, учитывая FQN интерфейса a.b.c.Foo с помощью метода findById () , JDBI будет искать ресурс с именем a/b/c/Foo/findById.sql.
Это поведение по умолчанию может быть переопределено для любого заданного метода путем передачи имени ресурса в качестве значения для аннотации @SqlXXX .
5.2. @SqlUpdate/@SqlBatch/@SqlQuery
Мы используем аннотации @SqlUpdate , @SqlBatch и @SqlQuery для обозначения методов доступа к данным, которые будут выполняться с использованием заданных параметров . Эти аннотации могут принимать необязательное строковое значение, которое будет буквальным оператором SQL для выполнения , включая любые именованные параметры, или при использовании с @UseClasspathSqlLocator , содержащим его именем ресурса.
@SqlBatch -аннотированные методы могут иметь аргументы, подобные коллекции, и выполнять одну и ту же инструкцию SQL для каждого доступного элемента в одной пакетной инструкции. В каждом из вышеперечисленных классов DAO у нас есть метод bulk Insert , который иллюстрирует его использование. Основным преимуществом использования пакетных операторов является дополнительная производительность, которую мы можем достичь при работе с большими наборами данных.
5.3. @getGeneratedKeys
Как следует из названия, аннотация @getGeneratedKeys позволяет нам восстанавливать любые сгенерированные ключи в результате успешного выполнения . В основном он используется в операторах insert , где наша база данных автоматически генерирует новые идентификаторы, и нам нужно восстановить их в нашем коде.
5.4. @BindBean/@Bind
Мы используем @BindBean и @Bind аннотации для связывания именованных параметров в инструкции SQL с параметрами метода . @BindBean использует стандартные соглашения о компонентах для извлечения свойств из POJO, включая вложенные. @Bind использует имя параметра или предоставленное значение для сопоставления его значения с именованным параметром.
6. Использование DAOs
Чтобы использовать эти DAO в нашем приложении, мы должны создать их экземпляр, используя один из заводских методов, доступных в JDBI.
В контексте Spring самый простой способ-создать компонент для повседневного использования с помощью метода OnDemand :
@Bean public CarMakerDao carMakerDao(Jdbi jdbi) { return jdbi.onDemand(CarMakerDao.class); } @Bean public CarModelDao carModelDao(Jdbi jdbi) { return jdbi.onDemand(CarModelDao.class); }
Созданный экземпляр on Demand является потокобезопасным и использует соединение с базой данных только во время вызова метода . Поскольку JDBI мы будем использовать поставляемый TransactionAwareDataSourceProxy, это означает, что мы можем легко использовать его с транзакциями, управляемыми Spring .
Несмотря на простоту, подход, который мы использовали здесь, далек от идеала, когда нам приходится иметь дело с более чем несколькими таблицами. Один из способов избежать написания такого шаблонного кода-создать пользовательский BeanFactory. Однако описание того, как реализовать такой компонент, выходит за рамки этого руководства.
7. Транзакционные Услуги
Давайте используем наши классы DAO в простом классе обслуживания, который создает несколько экземпляров Модели автомобиля , заданных автопроизводителем , заполненных моделями. Во-первых, мы проверим, был ли данный автопроизводитель ранее сохранен, сохранив его в базе данных, если это необходимо. Затем мы вставим каждую Модель автомобиля одну за другой.
Если в какой-либо момент происходит нарушение уникального ключа (или какая-либо другая ошибка), вся операция должна завершиться неудачно и должен быть выполнен полный откат .
JDBI предоставляет @Transaction аннотацию, но мы не можем использовать ее здесь , поскольку она не знает о других ресурсах, которые могут участвовать в той же бизнес-транзакции. Вместо этого мы будем использовать аннотацию Spring @Transactional в нашем методе обслуживания:
@Service public class CarMakerService { private CarMakerDao carMakerDao; private CarModelDao carModelDao; public CarMakerService(CarMakerDao carMakerDao,CarModelDao carModelDao) { this.carMakerDao = carMakerDao; this.carModelDao = carModelDao; } @Transactional public int bulkInsert(CarMaker carMaker) { Long carMakerId; if (carMaker.getId() == null ) { carMakerId = carMakerDao.insert(carMaker); carMaker.setId(carMakerId); } carMaker.getModels().forEach(m -> { m.setMakerId(carMaker.getId()); carModelDao.insert(m); }); return carMaker.getModels().size(); } }
Сама реализация операции довольно проста: мы используем стандартное соглашение о том, что значение null в поле id подразумевает, что эта сущность еще не сохранена в базе данных. Если это так, мы используем экземпляр CarMakerDao , введенный в конструктор, чтобы вставить новую запись в базу данных и получить сгенерированный идентификатор .
Как только у нас есть CarMaker ‘, мы перебираем модели, устанавливая поле maker Id для каждой из них, прежде чем сохранить ее в базе данных.
Все эти операции с базой данных будут выполняться с использованием одного и того же базового соединения и будут частью одной и той же транзакции . Хитрость здесь заключается в том, как мы привязали JDBI к Spring, используя TransactionAwareDataSourceProxy и создавая по требованию DAOS. Когда JDBI запрашивает новое Соединение , он получает существующее соединение, связанное с текущей транзакцией, таким образом интегрируя свой жизненный цикл с другими ресурсами, которые могут быть зарегистрированы.
8. Заключение
В этой статье мы показали, как быстро интегрировать JDBI в приложение Spring Boot . Это мощная комбинация в сценариях, где мы по какой-то причине не можем использовать Spring Data JPA, но все же хотим использовать все другие функции, такие как управление транзакциями, интеграция и так далее.
Как обычно, весь код доступен на GitHub .