Представляем Spring Data JDBC

Автор оригинала: Jens Schauder
  • Перевод

В предстоящий релиз Spring Data под кодовым именем Lovelace мы собираемся включить новый модуль: Spring Data JDBC.


Идея Spring Data JDBC заключается в том, чтобы предоставить доступ к реляционным базам данных без использования всей сложности JPA.


JPA предлагает такие функции, как ленивая загрузка (lazy loading), кеширование и отслеживание изменений (dirty tracking). Не смотря на то, что эти фичи очень крутые, если они, конечно, вам действительно нужны, они могут сильно усложнить понимание логики доступа к данным.


Мехнизм ленивой загрузки может внезапно выполнить ресурсоемкие запросы, или и вовсе упасть с исключением. Кэширование может встать на вашем пути когда вы решите сравнить две версии entity, а вкупе с отслеживанием изменений помешает понять — в какой же момент реально выполнятся все операции с базой данных?


Spring Data JDBC фокусируется на гораздо более простой модели. Не будет кеширования, отслеживания изменений, или ленивой загрузки. Вместо этого, SQL запросы будут выполнены тогда и только тогда, когда вы вызваете метод репозитория. Возвращаемый результат будет полностью загружен в память после выполнения метода. Не будет и механизма "сесии" или прокси-объектов для entities. И все это должно сделать Spring Data JDBC более простым и понятным инструментов для доступа к данным.


Разумеется, такой упрощенный подход выливается в целый ряд ограничений, о которых мы поговорим в следующих постах. Грядущий релиз это самая первая версия библиотеки, у нас есть много планов и замыслов, которые мы хотим реализовать, но мы вынуждены их отложить, чтобы дать вам возможность начать пользоваться Spring Data JDBC как можно раньше.


Пример


Для начала, нам нужно определить entity:


class Customer {
    @Id
    Long id;
    String firstName;
    LocalDate dob;
}

Обратите внимание, что мы не определяем ни геттеров, ни сеттеров. Вы, конечно, можете их добавить, если хотите. В сущности, единственное требование к entity — это иметь поле аннотированное аннотацией Id (но именно org.springframework.data.annotation.Id, а не javax.persistence one).

Дальше, нужно определить репозиторий. Самый простой способ это сделать — расширить интерфейс CrudRepository.


interface CustomerRepository extends CrudRepository<Customer, Long> {}

Наконец, нужно сконфигурировать ApplicationContext чтобы реализация этого интерфейса была созданы автоматически:


@Configuration
@EnableJdbcRepositories (1)
public class CustomerConfig extends JdbcConfiguration { (2)

    @Bean
    NamedParameterJdbcOperations operations() { (3)
        return new NamedParameterJdbcTemplate(dataSource());
    }

    @Bean
    PlatformTransactionManager transactionManager() { (4)
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    DataSource dataSource(){ (5)
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("create-customer-schema.sql")
                .build();
    }
}

Давайте разберем конфигурацию более детально.


  1. EnableJdbcRepositories активирует автоматическое создание репозиториев. Для того, чтобы это работало, нужно предоставить несколько дополнительных бинов, для чего и потребуется остальная часть нашего класса конфигурации.
  2. Т.к. конфигурационный класс расширяет JdbcConfiguration, несколько бинов будут добавлены в контекст автоматически. Их так же можно перекрыть, если нужно изменить поведение Spring Data JDBC. Но в данном примере мы оставим поведение по-умолчанию.
  3. Очень важный компонент это NamedParameterJdbcOperations, который используется для выполнения запросов к базе.
  4. Менеджер транзакций, строго говоря, не обязателен. Но без него не будет и поддержки транзакций, а это мало кому понравится, правда?
  5. Spring Data JDBC не использует DataSource напрямую, но TransactionManager и NamedParameterJdbcOperation требуют его наличия в контексте, поэтому мы и определяем нужный бин.

Это все, что нужно, чтобы начать работать со Spring Data JDBC. Теперь напишем тест, чтобы посмотреть, как это все работает:


@RunWith(SpringRunner.class)
@Transactional
@ContextConfiguration(classes = CustomerConfig.class)
public class CustomerRepositoryTest {

    @Autowired CustomerRepository customerRepo;

    @Test
    public void createSimpleCustomer() {

        Customer customer = new Customer();
        customer.dob = LocalDate.of(1904, 5, 14);
        customer.firstName = "Albert";

        Customer saved = customerRepo.save(customer);

        assertThat(saved.id).isNotNull();

        saved.firstName = "Hans Albert";

        customerRepo.save(saved);

        Optional<Customer> reloaded = customerRepo.findById(saved.id);

        assertThat(reloaded).isNotEmpty();

        assertThat(reloaded.get().firstName).isEqualTo("Hans Albert");
    }
}

Аннотация @Query


Только со стандартными методами CRUD репозитория из класса CrudRepository далеко не уедешь. Мы намеренно решили отложить автоматическую генерацию запроса — популярную фичу Spring Data, когда SQL запрос генерится на основе имени метода — на будущие релизы. А на текущий момент, вы можете просто использовать уже знакомую аннотацию @Query чтобы точно указать, какой SQL запрос должен быть выполнен.


@Query("select id, first_name, dob from customer where upper(first_name) like '%' || upper(:name) || '%' ")
List<Customer> findByName(@Param("name") String name);

Если вы хотите изменять или удалять данные в запросе, можете добавить к методу аннотацию @Modifying.


Давайте напишем тест, чтобы посмотреть, как работает наш новый метод.


@Test
public void findByName() {

    Customer customer = new Customer();
    customer.dob = LocalDate.of(1904, 5, 14);
    customer.firstName = "Albert";

    Customer saved = customerRepo.save(customer);

    assertThat(saved.id).isNotNull();

    customer.id= null; (1)
    customer.firstName = "Bertram";

    customerRepo.save(customer);

    customer.id= null;
    customer.firstName = "Beth";

    customerRepo.save(customer);

    assertThat(customerRepo.findByName("bert")).hasSize(2); (2)
}

  1. Т.к. связь между Java объектов и записью в базе только по полю Id и типу, то установка Idв null и сохранение этого объекта создаст новую запись.
  2. В запросе мы используем case-sensitive like, и поэтому мы находим « Albert » и « Bertram », но не « Beth ».

В завершение


Конечно, о Spring Data JDBC можно рассказать гораздо больше, и мы обязательно расскажем в следующих статьях.


А пока, вы можете изучить код примеров, документацию, и, разумеется, исходный код. Если у вас появятся вопросы — не стесняйтесь их задавать на StackOverflow. А если вы нашли баг или хотите запросить новую фичу — пожалуйста, создайте тикет.

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 34

    –1
    Мы долго упорно трудились, чтобы JPA работал нормально и быстро, но куча народу не осилила в ORM и не умеет его готовить. По этому мы дадим упрощающий интерфейс к JDBC. Тут я делаю фейспалм.
      +2

      Может немного не в тему, но мне почему-то кажется что это отличная возможность для проектов которые тянут данные не только из своей БД но ещё и из сторонних (к примеру большое реляционное хранилище), когда есть требования к написанию конкретных запросов (т.к. в том же Oracle можно легко "засуспендить" огромное количество пользователей "сожрав temp", при этом разделение ресурсов не всегда оправдано).

        0
        Чем JPA мешает вытянуть данные «не только из своей БД»? И как вообще подключение к БД влияет на выбор ORM?
          +1

          Я немного не про то, я про необходимость написания текста конкретных запросов, который гарантированно не будет изменён.
          У нас есть постоянная "проблема" со сменой планов выполнения запросов по усмотрению планировщика, на больших объёмах это может быть весьма критично (от "запрос не завершиться в течении близжайших недель, до "закончится темповое пространство за 30 секунд и все запросы встанут), поэтому многие запросы вручную прибиты хинтами т.к. человек, иногда, знает немного больше про данные и особенности работы БД чем планировщик. При этом эти с большой вероятностью будут написаны не разработчиком приложения под Spring и их нужно будет использовать с минимальными затратами.

            +1
            Это в JPA можно сделать без особых проблем.
          0
          Фишка в том что JPA слой позволяет спокойно работать напрямую с БД когда это надо :) Так что ну такое. Иногда может быть полезно, но это и раньше можно было делать. Тот же baltis к примеру.
          0
          У меня был случай, когда Spring JPA пишет в базу в 30 раз медленнее, чем чистый JDBC.
            0
            Это вполне легко там можно получить если думать что ORM это же про объекты.
            0
            Я вижу тут профит в возможности безболезненного рефакторинга кода использующего Plain JDBC или Spring JDBC и, соответственно, в вовлечении его в Spring Data
              0
              Проще сразу на SpringData переехать. По затратам будет тоже самое.
              0
              Речь идёт не про «осилить» или нет, а просто о предоставлении очень простого механизма доступа к реляционным данным. По сути, Spring JDBC + SingleColumnRowMapper + базовое управление зависимостями (в виде aggregate roots из философии DDD) и все.

              Часто всей мощи Hibernate не нужно, например в каком-то микросервисе.
                0
                Я как-то там не вижу сильно большого числа упрощений относительно SpringData. Точно так же надо добавлять POJO объект, точно так же надо добавить интерфейс, точно так же надо будет добавлять методы. Более того так-как DSL нет Query придется писать на любой чих.
                  0
                  Упрощения не в части API, это все остаётся таким же. Упрощение в том, что нет JPA и JPA провайдера, типа Hibernate.
                    0
                    А чем он мешает то? Память жрет на кеш?
                      0
                      Не мешает, но усложняет доступ к данным механизмами вроде кеширования, отслеживания изменений, сессионности, ленивой загрузки и т.п. В этом и суть нового модуля — надо все из Hibernate — берём Spring Data JPA. Не надо, но хочется Spring Data для реляционных баз — берём Spring Data JDBC.
                        0
                        Аналогичное было в spring 2.5 я про template если что. Тут немного row mapper обмазали. Ну разве что в микросервисах использовать. В любых сервисах где больше одной связки, уже ну такое будет. Много заката солнца в ручную.
              0
              Ошибся веткой.
                –1
                JDBI reinvented
                  0
                  Это статья какой-то пятилетней давности? В чем прикол?

                  «Мы намеренно решили отложить автоматическую генерацию запроса — популярную фичу Spring Data, когда SQL запрос генерится на основе имени метода — на будущие релизы.» — это ваще что?
                  0
                  Что на счет вызова хранимых процедур и возврата курсора?
                    0

                    Вызов хранимых процедур не должен быть проблемой, по сути такой же результат возвращается. А вот с курсорами за счет использования стандартного маппера уже сложнее. Возможно придется перекрывать дефолтную функциональность.

                    0
                    @Query("select id, first_name, dob from customer where upper(first_name) like '%' || upper(:name) || '%' ")
                    List<Customer> findByName(@Param("name") String name);
                    


                    Тут есть одна неприятная проблема — компилятор не проверяет корректность выражения, даже названия колонок и таблицы. В условном JOOQ это не так.
                      0

                      Да, это верно, хотя актуально и для JPA, если не брать в расчет Criteria / Metamodel API. Вообще, задача проверки статической корректности работы с базой крайне сложная, поэтому чаще всего ее делегируют в рантайм (тесты). Тот же JOOQ не сможет гарантировать что схема БД соответствует сгенерированным классам.

                        0
                        В некотором роде, можно улучшить шанс на корректность классов против схемы БД, используя в паре с JOOQ какой-то механизм для миграций. Например, Flyway.

                        P.S. Надеюсь, когда-нибудь в Java появится возможность использовать нестроковые константы в аннотациях, но это будет совсем другая история. Всё API Спринга сразу бы преобразилось. Чего только стоят Аспекты
                          0

                          Да, Flyway и прочие помогают, но их ареал обитания все равно рантайм. Корректность классов против схемы БД нельзя проверить на этапе компиляции иногда чисто технически. Ведь не факт, что с разработческой машины или CI, где выполняется компиляция, есть доступ к базе, где схема может быть другой.


                          PS Справедливости ради, тут Hibernate может сильно помочь, у него есть режим проверки соответствия схемы модели на старте приложения.


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

                      0
                      без автоматической генерации запроса на основе имени метода репозитория как-то совсем грустно, постоянно писать SQL запрос задолбаться можно
                        0

                        Я почти уверен, автоматическая генерация еще будет, просто не в этой версии.

                        0
                        Есть ли какое-нибудь решение с пагинацией и сортировкой?
                          0

                          Немного промахнулся, см. ниже — есть базовый класс репозитория PagingAndSortingRepository

                        0

                        Да, PagingAndSortingRepository (это не шутка, если что).

                          0

                          del

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое