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

habr.png

В предстоящий релиз 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 {}

Наконец, нужно сконфигурировать 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 reloaded = customerRepo.findById(saved.id);

        assertThat(reloaded).isNotEmpty();

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

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

@Query("select id, first_name, dob from customer where upper(first_name) like '%' || upper(:name) || '%' ")
List 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. А если вы нашли баг или хотите запросить новую фичу — пожалуйста, создайте тикет.

© Habrahabr.ru