Статьи

Сочетание Spring Boot и JDBI

Недавно я разработал еще один бэкэнд REST для микросервисов. В течение последних нескольких лет я писал с нуля бэкэнды в Spring , Dropwizard и даже в Python, используя web.py, но каждый оставил мне желание большего. В этой статье я покажу, как и почему я остановился на Spring Boot и JDBI как на комбинации фреймворков, которые позволяют мне легко разрабатывать REST-сервисы на основе реляционного хранилища данных.

ОТДЫХ

Давайте сравним и сопоставим некоторые из современных сред REST, которые можно использовать сегодня для создания служб REST. Отсутствуют некоторые очевидные (например, Ruby on Rails , Flask , Django , NodeJS ), но я собираюсь сосредоточиться на фреймворках, которые я лично использовал для создания значительных уровней обслуживания промышленного уровня.

весна

Spring, обладающий широкими возможностями для внедрения зависимостей (DI) и большой обширной библиотекой аддонов, является дедушкой современных бэкэндов для микросервисов, но имеет тенденцию к длительному времени запуска и выглядит довольно тяжелым и предприимчивым по сравнению с некоторыми из более новых платформ микросервисов. Сам Spring.io недавно опубликовал эту хорошую статью, в которой излагаются некоторые опасения, которые они слышали от разработчиков относительно среды Spring.

Он также имеет встроенную поддержку Hibernate в качестве слоя ORM, который (не стесняйтесь освещать меня в комментариях) в моем опыте мешает так же часто, как и нет. Он отлично подходит для простых реляционных структур и быстро загружает решения, но как только вы имеете дело со сложными структурами данных или хотите получить конкретные типы запросов (скажем, для эффективности), это становится довольно громоздким.

Dropwizard

Dropwizard — это, по сути, REST-уровень, построенный на облегченном контейнере Jersey, который обеспечивает отличную поддержку JDBI в качестве решения ORM-light. Я обнаружил, что JDBI — это простой способ взаимодействия с БД. Да, вы должны привязать свои ResultSet к вашим объектам модели вручную, но я нашел небольшую цену, чтобы заплатить за большую гибкость, которую он предоставляет, позволяя мне генерировать точные запросы, которые я хочу, не полагаясь (и не доверяя) SQL автоматически генерируется ORM. На мой взгляд, это действительно хорошее решение, которое находится где-то между JDBC и полноценной ORM.

Что Dropwizard не очень хорошо, это DI. Существует поддержка Guice с помощью расширений, таких как dropwizard-guice, но я считаю, что они в лучшем случае неуклюжи. Моя самая большая проблема со всеми этими решениями заключается в том, что вы получаете отдельные графы DI для каждого модуля. Это не проблема для простых приложений, но в сложных приложениях я сталкивался с тем, что dropwizard-guice становится довольно громоздким, и отдельные графы DI в конечном итоге получают один и тот же класс с другим экземпляром / состоянием, что может привести к непредвиденным результатам.

Я действительно просто хочу из коробки DI, который прост в использовании и убирается с дороги. Это должно быть сантехника, а не функция приложения.

web.py

Ну, ясно, что одна из этих вещей не похожа на другую. Я включил web.py, потому что я написал в нем полнофункциональный уровень отдыха, и если ваша религия — Python, то это хороший выбор. Если вы на самом деле рассматриваете фреймворк на основе Java, то это, очевидно, не ваша чашка чая.

Весенний ботинок

Spring Boot — это облегченная версия Spring, которая ориентирована на создание Spring-подобных приложений, которые «просто запускаются». Если вы новичок в Spring Boot, в справочном руководстве Spring Boot есть несколько отличных руководств по началу работы .

В то время как Spring может чувствовать себя достаточно предприимчивым, Spring Boot и чувствует, и работает как настоящая первоклассная инфраструктура микросервисов из-за быстрого обучения и быстрого запуска. Он имеет такую ​​же отличную поддержку DI, как Spring, а также имеет обширную библиотеку зависимостей spring-boot-starter-* maven, которую вы можете использовать для добавления поддержки для множества вещей.

Тем не менее, он по-прежнему использует Hibernate, поскольку является «родным» уровнем ORM. Теперь, если это хорошо для вас, тогда сделайте это, Spring Boot из коробки — отличное решение, но для меня я просто нахожу Hibernate слишком громоздким.

Hibernate против JDBI

Я заявил, что Hibernate «мешает» и что JDBI — «действительно хорошее решение», которое требует дальнейшего обсуждения, чтобы выделить несколько моментов. Во-первых, есть много приложений Hibernate, которые хорошо работают в масштабе (и я разработал много), однако, существует очень большая тенденция отходить от тяжелых ORM-решений по некоторым из следующих причин:

  • Hibernate может генерировать большое количество ненужных запросов, даже из-за незначительной неправильной конфигурации (например, здесь и здесь , просто для иллюстрации некоторых). При правильной настройке он все еще часто выполняет хуже, чем простые запросы SQL .
  • Hibernate думает о вашей структуре данных. Когда у вас есть структура данных, которая не следует за ней, это понятие ключей и отношений, Несоответствие объектно-реляционного импеданса может ударить вас еще сильнее. Каркасы Hybrid-ORM , такие как JDBI, обеспечивают большую гибкость при отображении на объекты и, таким образом, предоставляют больше взлетно-посадочной полосы, прежде чем начинать кодировать важные обходные пути для этой проблемы.
  • Вы не увидите проблем с производительностью, пока не масштабируете! Когда ваше приложение маленькое и простое, кажется, что все идет гладко, тогда вы масштабируете, и внезапно спящие запросы становятся причиной ваших проблем с производительностью. Я сам сталкивался с этой ситуацией много раз, и всегда требовались значительные усилия для решения проблем.

Этот пост-кворация — отличная дискуссия, которая подкрепляет некоторые из замечаний, которые я высказал выше, а также затрагивает другие. Обратите внимание, что я не пытаюсь ненавидеть Hibernate здесь. Это хороший инструмент, и при правильной настройке он может иметь отличную производительность в ряде ситуаций (я лично написал масштабные решения, использующие Hibernate в качестве ORM).

Так что это, безусловно, не правильное или неправильное обсуждение, и я не хочу утверждать, что JBDI — это лекарство от всех зол. Но это альтернатива «тяжелой» ORM, такой как Hibernate, и она может упростить вашу жизнь как разработчика. Я лично сталкивался со многими техническими компаниями, которые с радостью используют JDBI в Dropwizard и не вернутся к использованию Hibernate.

Так что же делать опытному программисту?

пчела

Интеграция Spring Boot + JDBI

Я хотел получить лучшее из Spring Boot DI, а также хороший простой и удобный слой ORM с использованием JDBI, поэтому я создал spring-boot-jdbi-seed . Этот проект на GitHub имеет полный уровень отдыха (примечание: нет безопасности, это для другого поста в блоге), а также интеграционные тесты, Checkstyle, FindBugs и т. Д., И вы можете использовать его в качестве основы для вашего следующего проекта, если хотите. Здесь я хочу сосредоточиться именно на том, что мне нужно было сделать, чтобы интегрировать Spring Boot и JDBI.

build.gradle

Во-первых, мне пришлось избавиться от этих спящих библиотек, поэтому я исключил их build.gradle файла build.gradle :

 configurations { all*.exclude group: "org.hibernate", module: "hibernate-entitymanager" all*.exclude group: "org.apache.tomcat", module: "tomcat-jdbc" } 

Взамен я добавил нужные мне зависимости:

 dependencies { compile( ... "org.springframework.boot:spring-boot-starter-jdbc", "org.springframework.boot:spring-boot-starter-data-jpa", "org.jdbi:jdbi:2.71", "joda-time:joda-time:2.8.1", ... ) runtime( "mysql:mysql-connector-java:5.1.38", "com.zaxxer:HikariCP:2.4.3" ) } 

Я также добавил joda-time, чтобы объекты DateTime могли сериализоваться в / из БД. Лично я предпочитаю это по Java 8 Дата и Время, потому что Interval , PeriodFormatter или PeriodType не доступны в Java-8.

application.properties

Затем я добавил конфигурацию свойств доступа к данным в application.properties . Spring Boot позволяет нам использовать общие свойства spring.datasource.* Вместо любых специфичных для jdbi свойств.

 spring.datasource.url=jdbc:mysql://localhost/services?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password= spring.datasource.driver-class-name=com.mysql.jdbc.Driver 

Обратите внимание, что я использовал MySQL в качестве БД, но его можно заменить любым драйвером, который вы хотите использовать, конечно.

сервисПрикладной

Затем я установил для часового пояса JVM значение по умолчанию UTC, чтобы все объекты DateTime были установлены в UTC. Это помогает значительно сериализовать в / из БД и не случайно применять преобразования часового пояса, когда они не должны быть. Во-вторых, мы регистрируем JodaModule, чтобы мы могли сериализовать экземпляры.

 @PostConstruct public void postConstruct() { // set the JVM timezone to UTC TimeZone.setDefault(TimeZone.getTimeZone("UTC")); } @Bean public Module jodaModule() { return new JodaModule(); } 

PersistenceConfiguration

В качестве последнего шага я создал конфигурацию Spring Boot, которая будет фактически отображать JDBI, а также отображать объекты даты / времени. Полный код можно найти в PersistenceConfiguration.java, но я выделю здесь код ключа:

 @Configuration public class PersistenceConfiguration { @Autowired DataSource dataSource; @Bean public DBI dbiBean() { DBI dbi = new DBI(dataSource); dbi.registerArgumentFactory(new DateTimeArgumentFactory()); dbi.registerArgumentFactory(new LocalDateArgumentFactory()); dbi.registerColumnMapper(new JodaDateTimeMapper()); return dbi; } ... /** * DBI argument factory for converting joda DateTime to sql timestamp */ public static class DateTimeArgumentFactory implements ArgumentFactory<DateTime> { @Override public boolean accepts(Class<?> expectedType, Object value, StatementContext ctx) { return value != null && DateTime.class.isAssignableFrom(value.getClass()); } @Override public Argument build(Class<?> expectedType, final DateTime value, StatementContext ctx) { return new Argument() { @Override public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException { long millis = value.withZone(DateTimeZone.UTC).getMillis(); statement.setTimestamp( position, new Timestamp(millis), getUtcCalendar()); } }; } } /** * A {@link ResultColumnMapper} to map Joda {@link DateTime} objects. */ public static class JodaDateTimeMapper implements ResultColumnMapper<DateTime> { @Override public DateTime mapColumn(ResultSet rs, int columnNumber, StatementContext ctx) throws SQLException { final Timestamp timestamp = rs.getTimestamp(columnNumber, getUtcCalendar()); if (timestamp == null) { return null; } return new DateTime(timestamp.getTime()); } @Override public DateTime mapColumn(ResultSet rs, String columnLabel, StatementContext ctx) throws SQLException { final Timestamp timestamp = rs.getTimestamp(columnLabel, getUtcCalendar()); if (timestamp == null) { return null; } return new DateTime(timestamp.getTime()); } } } 

Autowire и использование JDBI

Вот и все, теперь вы можете автоматически подключать и использовать объект DBI чтобы делать все, что позволяет JDBI .

 @RestController @RequestMapping("/test") @Slf4j public class TestResource { @Autowired DBI dbi; @RequestMapping(method = RequestMethod.GET) public String get() { String name; Handle handle = dbi.open(); try { name = handle.createQuery("select description from my_test") .map(StringMapper.FIRST) .first(); } finally { handle.close(); } return name; } } 

Лично я склонен использовать JDBI SQL Object API, который я нахожу простым и удобным способом для запроса данных, но вы можете читать документы и использовать то, что подходит именно вам.

Выполнение кода

Убедитесь, что на вашем компьютере установлены Java 8 , Git и MySQL . Проект включает в себя упаковщик Gradle, поэтому вам не нужно устанавливать Gradle.

Выполните следующее в командной строке:

 # pull the code from git git clone https://github.com/dhagge/spring-boot-jdbi-seed.git # build the project, create the database schema and seed some test data ./gradlew build ./gradlew dbUpdate ./gradlew dbSeed 

Теперь вы можете запустить сервис Spring Boot и вызвать тестовый сервис REST:

 ./gradlew bootRun # now curl the service (from a new command prompt) curl localhost:9090/test 

Вы увидите ответ Hello there, this is a test! на самом деле это данные, полученные из таблицы my_test в базе данных через класс TestResource .

Полный исходный код можно найти по адресу spring-boot-jdbi-seed , пожалуйста, разветвляйтесь и используйте его на досуге!

Резюме

Spring Boot и Dropwizard — отличные фреймворки для микросервисов, но, на мой взгляд, каждый оставляет желать лучшего. В частности, в Dropwizard отсутствует первоклассная поддержка внедрения зависимостей (DI), а Spring Boot поставляется со встроенным Hibernate, что может быть не лучшим выбором для ваших нужд (по крайней мере, обычно это не для меня). Выше я показал, как мы можем объединить некоторые из лучших миров, интегрируя JDBI со Spring Boot. Я был очень счастлив использовать эту комбинацию технологий для некоторых важных проектов, и я надеюсь, что вы тоже!