programing

spring-data-jpa 및 spring-mvc로 데이터베이스 행 필터링

oldcodes 2023. 10. 6. 21:58
반응형

spring-data-jpa 및 spring-mvc로 데이터베이스 행 필터링

저는 스프링-mvc 프로젝트가 있는데, 스프링-data-jpa를 데이터 접근에 사용하고 있습니다.이라는 도메인 개체가 있습니다.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 메서드를 추가하여 쿼리합니다.예를 들어 방법을 추가하고 싶습니다.findByLastNameLikeAndFirstNameLikeAndShipNameLike.

하지만 외국 키를 필터링해야 하는데 어떻게 해야 하는지 모르겠습니다.그래서 나의Travel가 있습니다.period에 대한 외부 키인 속성.Period도메인 개체, 사용자가 선택할 때 드롭다운으로 사용해야 합니다.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부터 계속.Deinum의 훌륭한 답변, 그리고 이를 실제로 실행한 후 질문/답변의 완성도를 위해 몇 가지 의견을 드리고자 합니다.

  1. 구현하는 대신JpaSpecificationExecutor당신은 실행해야 합니다.JpaSpecificationExecutor<Travel>유형 확인 경고를 피할 수 있습니다.

  2. 이 질문에 대한 kostja의 훌륭한 답변 Really dynamic JPA CriteriaBuilder를 확인해 주세요. 올바른 필터를 사용하려면 이를 구현해야 합니다.

  3. Criteria API에 대한 최고의 문서는 http://www.ibm.com/developerworks/library/j-typesafejpa/ 이었습니다.이것은 다소 긴 읽기이지만 완전히 권장합니다. 이를 읽은 후 Root 및 CriteriaBuilder에 대한 대부분의 질문에 답했습니다. :)

  4. 재사용Travel체함)다를 가 포함되어 있어 할 수 .Like- 대신에 나는 a를 사용했습니다.TravelSearch검색해야 할 필드가 들어 있는 개체입니다.

업데이트 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;
}

내가 도와줬기를!

.@RequestParam모든 검색 필드를 개체에 저장합니다(여행 개체를 재사용할 수도 있음).다를 데할 수 두 .

  1. 을 합니다.JpaSpecificationExecutor그리고 a를 씁니다.Specification
  2. 을 합니다.QueryDslPredicateExecutor그리고 QueryDSL을 사용하여 술어를 작성합니다.

으로.JpaSpecificationExecutor

.JpaSpecificationExecutoro대로TravelRepository이것은 당신에게 줄 것입니다.findAll(Specification)및할 수 finder다를정합니다.

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

에 수 .Specification기본적으로 쿼리를 구축합니다.이에 대해서는 Spring Data JPA 설명서를 참조하십시오.

를 만 만들면 .Specification사용 가능한 필드를 기반으로 쿼리를 구성합니다.쿼리는 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.
    }
}

를 .findAllmethod (나는 자유롭게 그것을 조금 치웠습니다.

@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";
}

으로.QueryDslPredicateExecutor

.QueryDslPredicateExecutoro대로TravelRepository이것은 당신에게 줄 것입니다.findAll(Predicate)및할 수 finder다를정합니다.

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

으로 합니다.TravelQueryDSL을 사용하여 술어를 만들 개체입니다.

@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);
    }
}

이 보그 포스트도 참조하십시오.

언급URL : https://stackoverflow.com/questions/20280708/filtering-database-rows-with-spring-data-jpa-and-spring-mvc

반응형