2016-07-22 3 views
2

Работа с данными весны jpa и спецификациями, у меня есть требование реализовать функцию фильтра/поиска весной mvc. Бэкэнд получает объект (ReportTemplateBean), который в основном является компонентом с некоторыми полями, которые представляют фильтры в интерфейсе.Динамические запросы в Spring Data JPA. Рефакторинг

public class ReportTemplateBean implements Serializable { 

private static final long serialVersionUID = -3915391620260021813L; 

private Long id; 

private String property; 

private String city; 

private String state; 

private String zipCode; 

private String propertyStatus; 

private String realEstateRep; 
//more code 

У нас есть контроллер

@RequestMapping(value = "/search", method = RequestMethod.GET) 
@ResponseBody 
public ReportBean search(@AuthenticationPrincipal ActiveUser activeUser, 
    @ModelAttribute("templateForm") ReportTemplateBean template, 
    Pageable pageable) throws GenericException { 
LOGGER.info("Pulling report requested"); 

ReportBean report = reportService.searchProperties(template, 
    pageable.getPageNumber(), pageable.getPageSize()); 

return report; 
} 

Службы

@Override 
@Transactional(readOnly = true, timeout = 20) 
public ReportBean searchProperties(ReportTemplateBean template, 
    Integer pageNumber, Integer pageSize) throws GenericException, 
    TransactionTimedOutException { 
LOGGER.info("searchProperties({})", template); 

try { 
    // pageNumber = (pageNumber == null ? 0 : pageNumber); 
    // pageSize = (pageSize == null ? 10 : pageSize); 
    ReportTemplate t = reportTemplateMapper.beanToEntity(template); 
    List<PropertyBean> beans = new ArrayList<PropertyBean>(); 
    PropertySpecification spec = new PropertySpecification(t); 
    Page<Property> properties = propertyRepository.findAll(spec, 
     new PageRequest(pageNumber, pageSize, Sort.Direction.ASC, 
      "name")); 

И тогда он строит запрос динамически, но используя длинную цепочку IF, что мне не нравится это. Это спецификация.

@SuppressWarnings("unchecked") 
@Override 
public Predicate toPredicate(Root<Property> root, CriteriaQuery<?> query, 
    CriteriaBuilder cb) { 
Path<String> propertyName = root.get(Property_.name); 
Path<String> city = root.get(Property_.city); 
Path<String> state = root.get(Property_.state); 
Path<String> zipCode = root.get(Property_.zipCode); 
final List<Predicate> orPredicates = new ArrayList<Predicate>(); 
final List<Predicate> andPredicates = new ArrayList<Predicate>(); 
if (template.getProperty() != null 
    && template.getProperty().length() > 0) { 
    andPredicates.add(cb.equal(propertyName, template.getProperty())); 
} 
if (template.getCity() != null && template.getCity().length() > 0) { 
    andPredicates.add(cb.equal(city, template.getCity())); 
} 
if (template.getState() != null && template.getState().length() > 0) { 
    andPredicates.add(cb.equal(state, template.getState())); 
} 
if (template.getZipCode() != null && template.getZipCode().length() > 0) { 
    andPredicates.add(cb.equal(zipCode, template.getZipCode())); 
} 
if (template.getRealEstateRep() != null) { 
    Join<Property, User> pu = null; 
    if (query.getResultType().getName().equals("java.lang.Long")) { 
    pu = (Join<Property, User>) root.fetch(Property_.createdBy); 
    } else { 
    pu = root.join(Property_.createdBy); 
    } 
    Path<Long> userId = pu.get(User_.id); 
    andPredicates.add(cb.equal(userId, template.getRealEstateRep())); 
} 
if (template.getProjectType() != null 
    && template.getProjectType().length() > 0) { 
    Join<Property, Project> pp = null; 
    if (query.getResultType().getName().equals("java.lang.Long")) { 
    pp = root.join(Property_.projects); 
    } else { 
    pp = (Join<Property, Project>) root.fetch(Property_.projects); 
    } 
    Path<String> projectType = pp.get(Project_.projectName); 
    andPredicates.add(cb.equal(projectType, template.getProjectType())); 
} 
//more IF's 
return query.getRestriction(); 
} 

Как вы можете заметить Спецификацию кажется уродливым и, кроме того, что SONAR жалуется цикломатической Сложностью этого метода (что хорошо).

Вопрос в том, как я могу реорганизовать спецификацию (IF), чтобы быть более OO-кодом ?. Спасибо заранее. UPDATE - Я хотел бы использовать/реализовать что-то вроде новой функции в Spring Data JPA (Query by Example) Кажется, что если вы передаете Бин ExampleMatcher класс будет игнорировать нулевое значение в полях боба, который почти что Я ищу. Игнорировать нулевые и пустые значения.

+0

Вы хотите решение только с использованием Спецификации? У меня была аналогичная проблема, но я решил написать запрос с помощью аннотации @Query с дополнительными параметрами в моем репозитории. – amicoderozer

+0

Было бы неплохо использовать спецификацию, но не обязательно, могу ли я взглянуть на ваше решение? – TheProgrammer

ответ

0

Я пишу свое решение, чтобы дать вам другой вариант, но, как я уже сказал в комментарии, я не использую Спецификацию, и мне интересно узнать, знает ли кто-нибудь другой способ делать динамические запросы в весеннем jpa.

Вы можете написать свой собственный запрос с аннотацией @Query внутри интерфейса @Repository. В вашем случае (при условии, ReportTemplateBean ваша сущность и ее первичный ключ из Long типа) было бы как:

@Repository 
public interface ReportTemplateRepo extends JpaRepository<ReportTemplateBean, Long>{ 

    @Query("SELECT rb FROM ReportBeanTemplate rb JOIN ExampleTable et WHERE et.idTemplate = rb.id AND (:id is null OR :id = rb.id) AND (:city is null OR :city = rb.city) AND (:state is null OR :state = rb.state)") 
    public List<ReportTemplateBean> findTemplates(@Param("id") Long id, @Param("city") String city, @Param("state") String state); 
} 

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

Пример вызова метода (в вас классе обслуживания):

@Autowire 
ReportTemplateRepo templateRepo; 

public void invocation(ReportTemplateBean template){ 
    List<ReportTemplateBean> templateRepo.findTemplates(
     template.getId(), template.getCity(), template.getState()); 
    } 

Это единственный способ я нашел, чтобы сделать этот вид запроса.

+0

А если у вас есть отношения? Я имею в виду, у моего компонента есть отношения OneToOne и OneToMany. Может ли ваше решение справиться с этим? Как управлять тогда, когда null? Благодарю. – TheProgrammer

+0

Я не тестировал, потому что у меня все еще нет отношений OneToMany в моем проекте, но посмотрите на этот вопрос http://stackoverflow.com/questions/38614247/default-sort-on-a-spring-data -jpa-хранилищу метод-с-настраиваемого-запросов и-pageab. Он использует отношения и запрос с динамическими параметрами, и он говорит, что он работает. Но я не знаю, как управлять такими отношениями, когда он равен нулю – amicoderozer

+0

Спасибо @amicoderozer, эта информация очень полезна. – TheProgrammer