Если вы используете JPA, я рекомендую использовать Spring Data JPA и API спецификации . Когда я впервые взглянул на Spring Boot и Spring Data, единственным реальным подходом, который я увидел, был продвинутый Spring Data Rest, который на мой вкус был слишком волшебным c.
Лично мне нравится, когда контроллеры вызывают методы в сервисах (многократно используемый слой бизнес-логики c, который в основном является шаблоном Facade). Эти сервисы в конечном итоге вызывают репозитории Spring Data.
Рассмотрим объект User
public class User{
String firstName;
String lastName;
}
Допустим, у нас есть UserController
, обрабатывающий наши REST-запросы для ресурса User
. Этот контроллер преобразует запрос с UserSearchCriteria
в данные Spring Page<User>
import org.springframework.data.domain.Page;
public class UserController{
@Autowired
UserService userService;
@RequestMapping(path = "/users", method = RequestMethod.GET)
Page<User> getAll(HttpServletRequest request, UserSearchCriteria searchCriteria){
return userService.findAllUsers(searchCriteria);
}
}
UserSearchCriteria
- это типы поисковых параметров, которые ваш клиент передает в виде параметров запроса, таких как GET /users?firstName=mista&lastName=henry
, которые автоматически преобразуются в UserSearchCriteria
поля.
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.PageRequest;
public class UserSearchCriteria {
UserSearchCriteria(){
super();
sort = "lastName";
}
Integer size;
Integer page;
String sort;
String sortDir;
String firstName;
String lastName;
Sort buildSort(){
return new Sort(new Sort.Order(Sort.Direction.ASC, sort).ignoreCase());
}
PageRequest toPageRequest(){
if(size == null){
size = Integer.MAX_VALUE; // may or may not be a good idea for your usecase
}
return new PageRequest(page, size, buildSort());
}
}
PageRequest
и Sort
являются частью проекта Spring Data. В моих проектах я позволил сортировку и разбиение по страницам, существующим в объекте AbstractSearchCriteria
, для более легкого повторного использования, которое расширяют все мои критерии поиска, но его проще продемонстрировать, как указано выше.
В слое UserService
я делегирую свой репозиторий (я также могу проверить требования к доступу, установить значения по умолчанию и т. Д. c.
import org.springframework.data.domain.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Transactional
public class UserService{
@Autowired
UserRepository userRepository;
Page<User> findAllUsers(UserSearchCriteria userSearchCriteria){
if(userSearchCriteria == null){
userSearchCriteria = new UserSearchCriteria();
}
return userRepository.findAll(UserSearchSpecification.findByCriteria(userSearchCriteria), userSearchCriteria.toPageRequest());
}
}
В UserSearchSpecification
используется Specification
API для динамического добавления поиска к вашему вызову JPA, который я считаю более чистым, чем прямое использование Criteria
API.
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
public class UserSearchSpecification{
public static Specification<User> findByCriteria(final UserSearchCriteria searchCriteria){
return new Specification<User>() {
@Override
Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
// if firstName in criteria, do an uppercase prefix match
if(searchCriteria.firstName != null){
predicates.add(
cb.like(
cb.upper(root.get("firstName")),
"%" + searchCriteria.lastName.toUpperCase()
)
);
}
// if lastName in criteria, do an uppercase prefix match
if(searchCriteria.lastName != null){
predicates.add(
cb.like(
cb.upper(root.get("lastName")),
"%" + searchCriteria.lastName.toUpperCase()
)
);
}
if(predicates.size() > 0){
return cb.and(predicates.toArray());
}else{
return null;
}
}
}
}
}
Это позволяет вам легко проверять переданные в параметрах остальные вызовы ( свойства UserSearchSpecification
) и динамически создавать ограничения для вызова базы данных. В конце я выбрал and
вместе для каждого предиката, но вы можете делать все, что захотите. Вы также можете проверить равенство вместо , больше / меньше, et, c.
Наконец, обратите внимание, что эта спецификация передается в UserRepository
в методе UserService.findAllUsers
. Это репозиторий Spring Data:
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface UserRepository extends PagingAndSortingRepository<User, Long>, JpaSpecificationExecutor<User>{
}
В Spring Data вам часто нужно расширять только определенные репозитории в интерфейсе, как здесь. Spring Data может обрабатывать все остальное под капотом. Важно отметить расширение PagingAndSortingRepository
, которое обрабатывает PA. аспект сортировки и сортировки, а также JpaSpecificationExecutor
, который принимает Specification
объекты и генерирует правильный запрос.
Это может показаться большим количеством кода, но это очень хорошо масштабируется для меня по мере роста базы кода. Как только у вас есть спецификация, вы можете легко добавить новые поля к вашему SearchCriteria
объекту и добавить новые ограничения в Спецификацию, проверив новые поля и создав Predicate
.