22 February, 2012

Упрощаем работу с 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-методами.
  • save - сохраняет или обновляет переданную сущность.
  • findOne - ищет сущность по первичному ключу.
  • findAll - возвращает коллекцию всех сущностей
  • count - возвращает количество сущностей
  • delete - удаляет сущность
  • exists - проверяет, существует ли сущность с данным первичным ключом
Полный список методов, объявленный в 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 поддерживает следующие операторы:
  • Between
  • IsNotNull
  • NotNull
  • IsNull
  • Null
  • LessThan
  • LessThanEqual
  • GreaterThan
  • GreaterThanEqual
  • NotLike
  • Like
  • NotIn
  • In
  • Near
  • Within
  • Regex
  • Exists
  • IsTrue
  • True
  • IsFalse
  • False
  • Not
Такой внушительный список открывает простор для фантазии, так что можно составить сколь угодно сложный запрос. Если необходимо, чтобы в результатах поиска было несколько сущностей, то необходимо называть метод findAllByBlahBlah

Поддержка 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. Рекомендуется к использованию в своих проектах.

2 comments:

  1. Где можно найти описание операторов Near, Within и Regex? В доке по spring-data-jpa я их не вижу. Не там смотрю? Или они не поддерживаются в JPA и могут быть использованы только с NoSQL БД, типа MongoDb?

    ReplyDelete
  2. 1. Разве в SQL есть операторы Near, Regex, Within?
    2. Для монги есть отдельный проект - Spring Data MongoDB, который поддерживает вышеперечисленный операторы.
    В документации всё описано - http://static.springsource.org/spring-data/data-mongodb/docs/current/reference/html/#mongo.query

    ReplyDelete