В предстоящий релиз 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();
}
}
Давайте разберем конфигурацию более детально.
EnableJdbcRepositories
активирует автоматическое создание репозиториев. Для того, чтобы это работало, нужно предоставить несколько дополнительных бинов, для чего и потребуется остальная часть нашего класса конфигурации.- Т.к. конфигурационный класс расширяет
JdbcConfiguration
, несколько бинов будут добавлены в контекст автоматически. Их так же можно перекрыть, если нужно изменить поведение Spring Data JDBC. Но в данном примере мы оставим поведение по-умолчанию. - Очень важный компонент это
NamedParameterJdbcOperations
, который используется для выполнения запросов к базе. - Менеджер транзакций, строго говоря, не обязателен. Но без него не будет и поддержки транзакций, а это мало кому понравится, правда?
- 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)
}
- Т.к. связь между Java объектов и записью в базе только по полю
Id
и типу, то установкаId
вnull
и сохранение этого объекта создаст новую запись. - В запросе мы используем case-sensitive like, и поэтому мы находим « Albert » и « Bertram », но не « Beth ».
В завершение
Конечно, о Spring Data JDBC можно рассказать гораздо больше, и мы обязательно расскажем в следующих статьях.
А пока, вы можете изучить код примеров, документацию, и, разумеется, исходный код. Если у вас появятся вопросы — не стесняйтесь их задавать на StackOverflow. А если вы нашли баг или хотите запросить новую фичу — пожалуйста, создайте тикет.
Автор: alek_sys