BackEnd/Spring Boot

JPA 응용 방법 _ Specification_2

Raconer 2023. 4. 18. 23:01
728x90

Specification lambda

앞서 작성된 "JPA 응용 방법 _ Specification" 글에서는 Specification을 구현하기 위해 implements를 사용하였습니다. 하지만 이번 글에서는 여러 개의 Specification을 하나의 클래스에서 중첩하여 사용하는 방법을 설명하려고 합니다.

사용 Entity 및 Repository

  1. 여러 조건 선언 하기
    import java.time.LocalDateTime;
      import org.springframework.data.jpa.domain.Specification;
      import com.mysql.jpa.model.entity.User;

    // 사용자 이름 테이블 
      public class UserNameSpec {
        public static Specification<User> nameLike(String name) {
          return (root, query, db) -> db.like(root.get("name"), "%" + name + "%");
          }

        public static Specification<User> createdAfter(LocalDateTime dt) {
            return (root, query, cb) -> cb.greaterThan(root.get("createDate"), dt);
        }
    }
  1. 실제 적용 및 사용
    // 단일 조건 
      public List<User> findWithSpecLambda(String name) {
        // 이름이 Like하는 Specification을 생성
        Specification<User> spec = UserNameSpec.nameLike(name);
        // 생성한 Specification을 사용하여 데이터 조회
        return this.userRepository.findAll(spec);
    }

    // 다건 사용 _ 방법 _ 1
    public List<User> findBySpecComb(String name) {

        // 이름이 일치하는 Specification 생성
        Specification<User> nameSpec = UserNameSpec.nameLike(name);

        // 1시간 이내 생성된 사용자인지 확인하는 Specification 생성
        Specification<User> afterSpec = UserNameSpec.createdAfter(LocalDateTime.now().minusHours(1));

        // 위 두 가지 Specification을 조합한 Specification 생성
        Specification<User> compositeSpec = nameSpec.and(afterSpec);

        return this.userRepository.findAll(compositeSpec);
    }
    // 다건 사용 _ 방법 _ 2
    public List<User> findBySpecComb2(String name) {
        // 이름이 일치하고, 1시간 이내 생성된 사용자인지 확인하는 Specification 생성 및 조합하여 생성
        Specification<User> comboSpec = UserNameSpec.nameLike(name)
                .and(UserNameSpec.createdAfter(LocalDateTime.now().minusHours(1)));

        // 생성한 Specification을 사용하여 데이터 조회
        return this.userRepository.findAll(comboSpec);
    }


    // 조건 사용 방법 2 (if 사용)
    public List<User> optionalCombo(String name, LocalDateTime dateTime) {
        // Specification 초기화
        Specification<User> spec = Specification.where(null);
        // 이름이 입력되었다면, 이름이 일치하는 Specification 조합
        if (name != null && !name.trim().isEmpty()) {
            spec = spec.and(UserNameSpec.nameLike(name));
        }
        // 생성 일자가 입력되었다면, 1시간 이내 생성된 사용자인지 확인하는 Specification 조합
        if (dateTime != null) {
            spec = spec.and(UserNameSpec.createdAfter(dateTime));
        }

        // 생성한 Specification을 사용하여 데이터 조회
        return this.userRepository.findAll(spec);
    }
    // 조건 사용 방법 2 (Build 사용) _ 3. Builder 사용시 필요한 코드 확인 필요
    public List<User> optionalComboBuild(String name, LocalDateTime dt) {
        // SpecBuilder 클래스를 사용하여 조건을 빌드
        // .ifHasText : 이름이 입력되었다면, 이름이 일치하는 Specification 조합
        // .ifNotNull : 생성 일자가 입력되었다면, 1시간 이내 생성된 사용자인지 확인하는 Specification 조합
        Specification<User> spec = SpecBuilder.builder(User.class).ifHasText(name, str -> UserNameSpec.nameLike(name))
                .ifNotNull(dt, value -> UserNameSpec.createdAfter(value)).toSpec();

        return this.userRepository.findAll(spec);
    }
  1. Builder 사용시 필요한 코드 _ 중요 코드
  import java.util.ArrayList;
  import java.util.List;
  import java.util.function.Function;

  import org.apache.logging.log4j.util.Supplier;
  import org.springframework.data.jpa.domain.Specification;
  import org.springframework.util.StringUtils;

  public class SpecBuilder {
      public static <T> Builder<T> builder(Class<T> type) {
          return new Builder<T>();
      }

      public static class Builder<T> {
          private List<Specification<T>> specs = new ArrayList<>();

          private void addSpec(Specification<T> spec) {
              if (spec != null) {
                  specs.add(spec);
              }
          }

          public Builder<T> and(Specification<T> spec) {
              addSpec(spec);
              return this;
          }

          public Builder<T> ifHasText(String str, Function<String, Specification<T>> specSupplier) {
              if (StringUtils.hasText(str)) {
                  addSpec(specSupplier.apply(str));
              }
              return this;
          }

          public Builder<T> ifTrue(Boolean cond, Supplier<Specification<T>> specSupplier) {
              if (cond != null && cond.booleanValue()) {
                  addSpec(specSupplier.get());
              }
              return this;
          }

          public <V> Builder<T> ifNotNull(V value, Function<V, Specification<T>> specSupplier) {
              if (value != null) {
                  addSpec(specSupplier.apply(value));
              }
              return this;
          }

          public Specification<T> toSpec() {
              Specification<T> spec = Specification.where(null);
              for (Specification<T> s : specs) {
                  spec = spec.and(s);
              }
              return spec;
          }
      }
  }
728x90

'BackEnd > Spring Boot' 카테고리의 다른 글

jasypt 란?  (0) 2023.05.01
mysql-connector-j와 mysql-connector-java  (0) 2023.04.30
JPA 응용 방법 _ Specification_1  (0) 2023.04.17
JPA 간단 사용법  (0) 2023.04.17
Bean Life Cycle  (0) 2023.04.16