null

SpringData или "бывает же магия в коде"

Лирическое отступление

Любое современное приложение не обходится без уровня бизнес-логики, а именно без хранения данных в каких-либо базах данных: реляционных, графовых, документо-ориентированных (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 и к тому же никто не мешает вам написать собственную реализацию.

Назад

Коротко о себе:

Работаю программистом в компании Tune IT и инженером на кафедре Вычислительной техники в Университете ИТМО.

Ничего не найдено. n is 0