Oleg Atamanenko

thoughts about programming

Упрощаем работу с JPA при помощи Spring Data JPA

Введение

Уже прошло несколько лет с тех пор, как появился JPA. Работа с EntityManager увлекательна, но разработчики пишут красивый API, а подробности работы с базой данных скрывают. При этом частая проблема - дублирование имплементации, когда из одного DAO в другой у нас плавно перекочёвывает один и тот же код, в лучшем случае этот код переносится в абстрактный базовый DAO. Spring Data коренным образом решает проблему - при его использовании остаётся только API на уровне интерфейсов, вся имплементация создаётся автоматически с использованием AOP.

История Spring Data

Несмотря на то, что проект только недавно достиг версии 1.0, у него достаточно богатая история - раньше он развивался в рамках проекта Hades.

Объявление DAO-интерфейса

Итак, для начала нам необходимо объявить DAO-интерфейс, в котором мы будем объявлять методы для работы с сущностью.

public interface UserRepository extends CrudRepository<User, Long> {
}

Данного кода достаточно для обычного DAO с CRUD-методами.

Полный список методов, объявленный в CrudRepository можно посмотреть в javadoc.

В случае, если нам нужны не все методы, то есть возможность произвести наследование от интерфейса Repository и перенести в наследника только те методы из интерфейса CrudRepository, которые нужны.

Поддержка сортировки и постраничного просмотра

Очень часто требующаяся функциональность - это возможность возвращать только часть сущностей из БД, например, для реализации постраничного просмотра в пользовательском интерфейсе. Spring Data и тут хорош и предоставляет нам возможность добавить такую функциональность в наш DAO. Для этого достаточно добавить объявление следующего метода в наш DAO-интерфейс:
 Page<User> findAll(Pageable pageable);

Интерфейс Pageable инкапсулирует в себе сведения о номере запрашиваемой страницы, размере страницы, а также требуемой сортировке.

Ищем данные

Как правило, на обычных CRUD-ах DAO не заканчиваются и часто требуются дополнительные методы, которые возвращают только те сущности, которые удовлетворяют заданным условиям. На мой взгляд, Spring Data сильно упрощает жизнь в данной области.

Например, нам нужен методы для поиска пользователя по логину и по его e-mail адресу:

 User findByLogin(String login);
 User findByEmail(String email);

Все просто.

В случае, если нужны более сложные условия для поиска, то и это тоже реализовано.

Spring Data поддерживает следующие операторы:

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

Если необходимо, чтобы в результатах поиска было несколько сущностей, то необходимо называть метод findAll

Поддержка Spring MVC

Это часть основана на официальной документации. Представьте, что вы разрабатываете веб-приложение с использованием Spring MVC. Тогда вам необходимо будет загружать сущность из базы данных используя параметры HTTP-запроса. Это может выглядеть следующим образом:
@Controller
@RequestMapping("/users")
public class UserController {

  private final UserRepository userRepository;

  public UserController(UserRepository userRepository) {
    userRepository = userRepository;
  }

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") Long id, Model model) {
    
    // Do null check for id
    User user = userRepository.findOne(id);
    // Do null check for user
    // Populate model
    return "user";
  }
}

Во-первых, вы объявляете зависимость на DAO, а во-вторых всегда вызываете метод findOne() для загрузки сущности. К счастью, Spring позволяет нам преобразовывать строковые значения из HTTP-запросов в любой нужный тип используя либо PropertyEditor, либо ConversionService.

Если вы используете Spring версии 3.0 и выше, то вам необходимо добавить следующую конфигурацию:

<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService" class="….context.support.ConversionServiceFactoryBean">
  <property name="converters">
    <list>
      <bean class="org.springframework.data.repository.support.DomainClassConverter">
        <constructor-arg ref="conversionService" />
      </bean>
    </list>
  </property>
</bean>

Если же вы используете Spring более старой версии, то вам необходима вот такая конфигурация:

<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="….web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
      </property>
    </bean>
  </property>
</bean>

После данных изменений в конфигурации можно переписать контроллер следующим образом:


@Controller
@RequestMapping("/users")
public class UserController {

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {

    // Do null check for user
    // Populate model
    return "userForm";
  }
}

Обратите внимание на то, как упростился код и как мы красиво избавились от его дублирования.

Документация

На данный момент документации по проекту не так уж и много, но, тем не менее, она есть:

Заключение

Spring Data значительно упрощает жизнь при использовании JPA. Рекомендуется к использованию в своих проектах.