Лирическое отступление
Любое современное приложение не обходится без уровня бизнес-логики, а именно без хранения данных в каких-либо базах данных: реляционных, графовых, документо-ориентированных (NoSQL) и т.д. Когда мы были молодыми и красивыми мы кодили, используя перфокарты JDBC.


Прошло время и человечество познакомилось с ORM и JPA - инструментами, позволяющими оградить программиста от таких нудных вещей, как отображение данных на объекты, ручной настройки подключений и нативного SQL !

В рамках данной статьи предлагается обзор популярного проекта SpringData, который по своей сути является следующим уровнем абстракции в решении задач доступа к данным и позволяет в разы сократить количество Java-кода и конфигов.
Шаблон проекта
Для начала создадим проект со следующей структурой:
project
+ src
+ main
+ java
+ com
+ tuneit
+ spring
+ domain
+ config
+ repository
+ App.java
+ resources
+ app.properties
+ pom.xml
Стоит упомянуть, что в этом приложении мы будем использовать Jameson SpringBoot, позволяющий избежать трудозатрат на настройку проекта. Содержимое нашего pom.xml в таком случае примет вид (приношу глубочайшие извинения за использование MySQL):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tuneit</groupId>
<artifactId>spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<addResources>true</addResources>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.3</version>
</dependency>
</dependencies>
</project>
App класс проекта будет содержать следующий код для запуска SpringBoot-приложения:
@SpringBootApplication
public class App {
public static void main(String[] args) throws Exception {
SpringApplication.run(App.class, args);
}
}
Вся конфигурация проекта в нашем случае будет вынесена в файл app.properties, содержимое которого представлено в следующем листинге:
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.platform=mysql
spring.datasource.url=jdbc:mysql://localhost:3306/spring
spring.datasource.username=your_username
spring.datasource.password=your_password
Все настройки по работе с базой данных будут находиться в одном Java-конфиге:
@Configuration
@EnableJpaRepositories(basePackages = "com.tuneit.spring.repository")
@EnableAutoConfiguration
@PropertySource("classpath:app.properties")
@EnableTransactionManagement
public class DataConfig {
private static final String PERSISTENCE_UNIT_NAME = "spring_domain";
private static final String DOMAIN_ROOT_PACKAGE = "com.tuneit.spring.domain";
@Value("${spring.jpa.database-platform}")
private String hibernateDialect;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory () {
LocalContainerEntityManagerFactoryBean emf =
new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
emf.setPersistenceProviderClass(HibernatePersistenceProvider.class);
emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emf.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
emf.setPackagesToScan(DOMAIN_ROOT_PACKAGE);
emf.setJpaProperties(jpaProperties());
return emf;
}
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(entityManagerFactory().getObject());
return manager;
}
@Bean
public Properties jpaProperties() {
Properties jpaProps = new Properties();
jpaProps.put("hibernate.dialect", hibernateDialect);
jpaProps.put("hibernate.hbm2ddl.auto", "create");
return jpaProps;
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
На этом, пожалуй, и вся конфигурация! Далее начинается самое интересное. В нашем приложении мы используем SpringData с Hibernate'ом в качестве backend'а. Предлагаю, перед тем как мы займемся "магией", перейти к определению сущности:
@Entity
public class User extends AbstractPersistable<Long> {
@Column(unique = true)
private String name;
private String password;
/* методы доступа к полям опущены */
}
Абстрактный класс AbstractPersistable позволяет избежать нудной работы по описанию идентификатора сущности и методов доступа к нему.
На первый взгляд, можно сказать что мы имеем дело с уже хорошо знакомым JPA c некоторыми "плюшками" в настройке DataSource. Однако все меняется, когда мы приступаем к описанию DAO...
А где собственно реализация?
DAO для сущности, определенной выше, примет следующий вид:
public interface UsersRepository extends CrudRepository<User, Long> { }
Этот интерфейс следует поместить в пакет com.tuneit.spring.repository. И для того чтобы работать с БД этого уже достаточно!

Для тестирования всего этого дела можно написать тест, примерный вид которого представлен в следующем листинге:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataConfig.class)
@Transactional
public class JpaRepositoryTest {
@Autowired
private UsersRepository userRepository;
@Before
@Rollback(false)
public void setUp() {
User user = new ....
userRepository.save(user);
}
@Test
public void chackCountUsers() {
Assert.assertEquals(1L, userRepository.count());
}
}
Этот репозиторий можно использовать в любом Spring-бине. Как это прекрасно :)
А сейчас давайте немного заглянем во внутренности. Во-первых, наш репозиторий, содержит набор "типичных" методов для работы с нашей сущностью: выборка всех, выборка по ID, сохранение, удаление и т.д. Все эти методы перечислены в интерфейсе CrudRepository, реализация которого генерируется Spring'ом в runtim'е при старте приложения (создании контекста). Однако вопрос остается открытым: откуда берется реализация этих методов?
Для того чтобы дать ответ давайте внесем еще некоторое "заклинание" в наш репозиторий:
User findUserByName(String name);
если в наш тест мы внесем строку, которая использует этот метод, то к нашему удивлению все заработает:
@Test
public void chackFindUser() {
User user = userRepository.findUserByName("springUser");
}
Название описанного нами метода уже дает некоторую подсказку о том, как Spring формирует прокси-объекты с необходимой реализацией. Аннотацией @EnableJpaRepository мы указали Spring'у где находятся наши DAO. Далее он сканирует название методов и формирует запросы соответствующие этим названиям. Перечень ключевых слов, которые, которые могут быть использованы при написании собственных методов можно посмотреть тут.
В этот момент вы смело можете заявить о проблемах, которые неотрывно связаны с кастомизацией в высокоуровневых фреймворках. Однако здравствуйте, тут есть богатый набор инструментов, которые позволяют вам отказаться от тех или иных возможностей SpringData. Во-первых, вы можете наследоваться от интерфейса Repository, который вовсе не содержит методов, вы можете аннотировать методы с помощью @Query с указанием своего запроса на JPQL и т.д. Что касается "плюшек", которые есть в SpringData "из коробки", то для выборки с сортировкой и/или страничной организации выборки есть интерфейс PagingAndSortingRepository.
В этой статье рассматривается лишь верхушка айсберга. SpringData поставляется с поддержкой таких репозиториев как MongoRepository, GraphRepository и к тому же никто не мешает вам написать собственную реализацию.