복붙노트

[SPRING] spring-data-jpa 및 spring-mvc를 사용하여 데이터베이스 행 필터링

SPRING

spring-data-jpa 및 spring-mvc를 사용하여 데이터베이스 행 필터링

데이터 액세스를 위해 spring-data-jpa를 사용하는 spring-mvc 프로젝트가 있습니다. Travel이라는 도메인 객체가있어 최종 사용자가 여러 필터를 적용 할 수 있습니다.

이를 위해 다음 컨트롤러를 구현했습니다.

@Autowired
private TravelRepository travelRep;

@RequestMapping("/search")  
public ModelAndView search(
        @RequestParam(required= false, defaultValue="") String lastName, 
        Pageable pageable) {  
    ModelAndView mav = new ModelAndView("travels/list");  
    Page<Travel> travels  = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
    PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");
    mav.addObject("page", page);
    mav.addObject("lastName", lastName);
    return mav;
}

이것은 잘 작동합니다 : 사용자는 여행을 필터링하는 데 사용할 수있는 성 입력 상자가있는 양식이 있습니다.

lastName 너머에는 내 Travel 도메인 객체가 필터링 할 속성이 훨씬 많습니다. 이 속성이 모두 문자열이라면 @RequestParams로 추가하고 spring-data-jpa 메서드를 추가하여 이러한 쿼리를 수행 할 수 있다고 생각합니다. 예를 들어, findByLastNameLikeAndFirstNameLikeAndShipName과 같은 메소드를 추가 할 수 있습니다.

그러나 외래 키를 필터링해야 할 때 어떻게해야하는지 모르겠습니다. 따라서 내 Travel에는 Period 도메인 객체에 대한 외래 키인 기간 속성이 있습니다.이 속성은 사용자가 기간을 선택하기위한 드롭 다운으로 가져와야합니다.

내가 원하는 것은 마침표가 null 인 경우입니다. lastName으로 필터링 된 모든 여행을 검색하려고하며 마침표가 null이 아닌 경우 lastName으로 필터링 된이 기간의 모든 여행을 검색하려고합니다.

내 저장소에 두 가지 방법을 구현하고 내 컨트롤러에 if를 사용하면이 작업을 수행 할 수 있음을 알고 있습니다.

public ModelAndView search(
       @RequestParam(required= false, defaultValue="") String lastName,
       @RequestParam(required= false, defaultValue=null) Period period, 
       Pageable pageable) {  
  ModelAndView mav = new ModelAndView("travels/list");  
  Page travels = null;
  if(period==null) {
    travels  = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
  } else {
    travels  = travelRep.findByPeriodAndLastNameLike(period,"%"+lastName+"%", pageable);
  }
  mav.addObject("page", page);
  mav.addObject("period", period);
  mav.addObject("lastName", lastName);
  return mav;
}

if를 사용하지 않고이 작업을 수행 할 수있는 방법이 있습니까? 내 여행에는 기간뿐만 아니라 드롭 다운을 사용하여 필터링해야하는 다른 속성이 있습니다! 당신이 이해할 수있는 것처럼, 모든 콤보를 고려해야하기 때문에 더 많은 드롭 다운을 사용해야 할 때 복잡성이 기하 급수적으로 증가 할 것입니다.

03/12/13 업데이트 : M. Derium의 탁월한 답변에서 계속해서 실제로 구현 한 후 질문 / 답변의 완성을 위해 몇 가지 의견을 제시하고자합니다.

업데이트 10/05/15 : @ priyank의 요청에 따라 TravelSearch 객체를 구현 한 방법은 다음과 같습니다.

public class TravelSearch {
    private String lastName;
    private School school;
    private Period period;
    private String companyName;
    private TravelTypeEnum travelType;
    private TravelStatusEnum travelStatus;
    // Setters + Getters
}

이 객체는 TravelSpecification에서 사용되었습니다 (대부분의 코드는 도메인과 관련이 있지만 예제로 남겨두고 있습니다).

public class TravelSpecification implements Specification<Travel> {
    private TravelSearch criteria;


    public TravelSpecification(TravelSearch ts) {
        criteria= ts;
    }

    @Override
    public Predicate toPredicate(Root<Travel> root, CriteriaQuery<?> query, 
            CriteriaBuilder cb) {
        Join<Travel, Candidacy> o = root.join(Travel_.candidacy);

        Path<Candidacy> candidacy = root.get(Travel_.candidacy);
        Path<Student> student = candidacy.get(Candidacy_.student);
        Path<String> lastName = student.get(Student_.lastName);
        Path<School> school = student.get(Student_.school);

        Path<Period> period = candidacy.get(Candidacy_.period);
        Path<TravelStatusEnum> travelStatus = root.get(Travel_.travelStatus);
        Path<TravelTypeEnum> travelType = root.get(Travel_.travelType);

        Path<Company> company = root.get(Travel_.company);
        Path<String> companyName = company.get(Company_.name);

        final List<Predicate> predicates = new ArrayList<Predicate>();
        if(criteria.getSchool()!=null) {
            predicates.add(cb.equal(school, criteria.getSchool()));
        }
        if(criteria.getCompanyName()!=null) {
            predicates.add(cb.like(companyName, "%"+criteria.getCompanyName()+"%"));
        }
        if(criteria.getPeriod()!=null) {
            predicates.add(cb.equal(period, criteria.getPeriod()));
        }
        if(criteria.getTravelStatus()!=null) {
            predicates.add(cb.equal(travelStatus, criteria.getTravelStatus()));
        }
        if(criteria.getTravelType()!=null) {
            predicates.add(cb.equal(travelType, criteria.getTravelType()));
        }
        if(criteria.getLastName()!=null ) {
            predicates.add(cb.like(lastName, "%"+criteria.getLastName()+"%"));
        }
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));

    }
}

마지막으로 여기 내 검색 방법이 있습니다.

@RequestMapping("/search")  
public ModelAndView search(
        @ModelAttribute TravelSearch travelSearch,
        Pageable pageable) {  
    ModelAndView mav = new ModelAndView("travels/list");  

    TravelSpecification tspec = new TravelSpecification(travelSearch);

    Page<Travel> travels  = travelRep.findAll(tspec, pageable);

    PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");

    mav.addObject(travelSearch);

    mav.addObject("page", page);
    mav.addObject("schools", schoolRep.findAll() );
    mav.addObject("periods", periodRep.findAll() );
    mav.addObject("travelTypes", TravelTypeEnum.values());
    mav.addObject("travelStatuses", TravelStatusEnum.values());
    return mav;
}

희망은 내가 도왔다!

해결법

  1. ==============================

    1.우선 @RequestParam의 사용을 중단하고 모든 검색 필드를 객체에 넣어야합니다 (아마도 Travel 객체를 다시 사용할 수 있습니다). 그런 다음 동적으로 쿼리를 작성하는 데 사용할 수있는 두 가지 옵션이 있습니다.

    우선 @RequestParam의 사용을 중단하고 모든 검색 필드를 객체에 넣어야합니다 (아마도 Travel 객체를 다시 사용할 수 있습니다). 그런 다음 동적으로 쿼리를 작성하는 데 사용할 수있는 두 가지 옵션이 있습니다.

    먼저 TravelRepository에 JpaSpecificationExecutor를 추가하면 findAll (Specification) 메소드가 생기고 커스텀 파인더 메소드는 삭제할 수 있습니다.

    public interface TravelRepository extends JpaRepository<Travel, Long>, JpaSpecificationExecutor<Travel> {}
    

    그런 다음 기본적으로 쿼리를 작성하는 사양을 사용하는 리포지토리에 메서드를 만들 수 있습니다. 이에 대해서는 Spring Data JPA 문서를 참조하십시오.

    당신이해야 할 유일한 일은 스펙을 구현하고 사용 가능한 필드를 기반으로 쿼리를 빌드하는 클래스를 생성하는 것입니다. 쿼리는 JPA Criteria API 링크를 사용하여 빌드됩니다.

    public class TravelSpecification implements Specification<Travel> {
    
        private final Travel criteria;
    
        public TravelSpecification(Travel criteria) {
            this.criteria=criteria;
        }
    
        public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
            // create query/predicate here.
        }
    }
    

    그리고 마지막으로 새로운 findAll 메소드를 사용하도록 컨트롤러를 수정해야합니다 (약간 자유롭게 정리했습니다).

    @RequestMapping("/search")  
    public String search(@ModelAttribute Travel search, Pageable pageable, Model model) {  
    Specification<Travel> spec = new TravelSpecification(search);
        Page<Travel> travels  = travelRep.findAll(spec, pageable);
        model.addObject("page", new PageWrapper(travels, "/search"));
        return "travels/list";
    }
    

    먼저 TravelRepository에 QueryDslPredicateExecutor를 추가하면 findAll (Predicate) 메소드가 생기고 커스텀 파인더 메소드는 삭제할 수 있습니다.

    public interface TravelRepository extends JpaRepository<Travel, Long>, QueryDslPredicateExecutor<Travel> {}
    

    다음으로 QueryDSL을 사용하여 술어를 빌드하기 위해 Travel 오브젝트를 사용하는 서비스 메소드를 구현한다.

    @Service
    @Transactional
    public class TravelService {
    
        private final TravelRepository travels;
    
        public TravelService(TravelRepository travels) {
            this.travels=travels;
        }
    
        public Iterable<Travel> search(Travel criteria) {
    
            BooleanExpression predicate = QTravel.travel...
            return travels.findAll(predicate);
        }
    }
    

    이 블로그 게시물을 참조하십시오.

  2. from https://stackoverflow.com/questions/20280708/filtering-database-rows-with-spring-data-jpa-and-spring-mvc by cc-by-sa and MIT license