ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA Specification를 사용하여 조인,검색기능 만들기
    JPA 2023. 12. 25. 15:21
    반응형

    이 포스트는 Specification의 사용법만 설명한다. 기타 페이징 적용, response방법 등은 생략한다

     

    JPA Specification란? 

    1. JPA Specification은 Java Persistence API (JPA)에서 제공하는 기능 중 하나로, 동적으로 쿼리를 생성하는 방법을 제공 

    2. JPA는 자바 어플리케이션에서 관계형 데이터베이스와 상호 작용할 수 있게 해주는 API 

    3. JpaRepository 상속받을때 JpaSpecificationExecutor도 같이 속받으면 된다.

     

     

     

    테스트를 진행하려면 아래의 파일들이 필요하다.

     

    AccountSpecification.java

    public class AccountSpecification {
    
        //거래처명 검색
        public static Specification<Account> likeAccountName(String accountName) {
            return new Specification<Account>() {
                private static final long serialVersionUID = 1L;
                @Override
                public Predicate toPredicate(Root<Account> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                    // 2) like
                    return criteriaBuilder.like(root.get("accountName"), "%" + accountName + "%");
                }
            };
        }
    
        //본인 부서
        public static Specification<Account> equalDepartmentCode(Integer departmentCode) {
            return (Specification<Account>) (root, query, builder) -> {
                if (departmentCode == null) {
                    return null;
                }
                Join<Account, Employee> empJoin = root.join("employee", JoinType.INNER); //사원테이블과 조인 (emplyCode) 기준
                Join<Employee, Department> deptJoin = empJoin.join("department", JoinType.INNER); // 부서테이블과 조인 (departmentCode 기준)
                return builder.equal(deptJoin.get("departmentCode"), departmentCode); // 부서 코드 일치 조건
            };
        }
        
    }

     

    거래처테이블 Account.java (Entity)

    @Entity
    @Table(name = "tb_account")
    @NoArgsConstructor(access = PROTECTED)
    @Getter
    @EntityListeners(AuditingEntityListener.class)
    public class Account {
    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        private Long accountCode;				//거래처코드
    
        @Column(nullable = false)
        private String accountNumber;			//거래처연락처
        
        @Column(nullable = false)
        private String accountName; 			//거래처명
    
        @JoinColumn(name = "emplyCode")		//직원테이블 관계 설정
        private Employee employee;
    }

     

    사원(회원)테이블 Employee.java(Entity)

    @Entity
    @Table(name = "tb_employee")
    @Getter
    @NoArgsConstructor(access = PROTECTED)
    @EntityListeners(AuditingEntityListener.class)
    public class Employee {
    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        private Long emplyCode;  //사원코드
    
        private String emplyName; //사원이름
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "departmentCode")
        private Department department;  //부서코드
        
    }

     

    부서 테이블 Department.java(Entity)

    @Entity
    @NoArgsConstructor(access = PROTECTED)
    @Getter
    @Table(name = "tb_department")
    public class Department {
    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        private Long departmentCode;  //부서코드
        @Column(nullable = false)
        private String departmentName;  //부서이름
    }

     

    <!-- 내용 검색 콤보박스 -->
    <slect id="schType" name="schType">
        <option value="">전체</option>
        <option value="accountName">거래처명</option>
        <option value="accountNumber">거래처번호</option>
    </slect>
    <!-- 검색어 키워드 -->
    <input type="text"  name="schText" id="schText" placeholder="검색어를 입력하세요.">
    
    <!-- 부서 검색 콤보박스 -->
    <slect id="departmentCode" name="departmentCode">
        <option value="">전체</option>
        <option value="1">영업부</option>
        <option value="2">개발부</option>
    </slect>

     

    AccountService.java

    @Transactional(readOnly = true)
    public Page<AccountListResponse> getFindAll(final Integer page, Integer departmentCode, String schType, String schText) {
    
        Specification<Account> spec = (root, query, criteriaBuilder) -> null;
    
        //1. 검색타입이 거래처명일때
        if ("accountName".equals(schType)) {
            spec = spec.and(AccountSpecification.likeAccountName(schText));
        }
        //2. 부서코드가 0이면 전체, 0이 아니면 선택한 부서코드로 조회
        if(departmentCode != 0) {
            spec = spec.and(AccountSpecification.equalDepartmentCode(departmentCode));
        }
    
        Page<Account> accountList = accountRepository.findAll(spec, getPageable(page));
    
        return accountList.map(account -> AccountListResponse.from(account));
    }

    서비스 자바 내용중 if의 조건이 충족하면 spec에 and키워드로 조건이 누적이 된다.

     

     

    -- 검색조건테스트

    1. 거래처명을 '테슬라' 라는 키워드로 검색하기 (테이블에 있는 컬럼) 

    -- SQL 결과

    select
        account0_.account_code as account_1_0_,
        account0_.account_name as account_4_0_,
        account0_.account_number as account_5_0_
    from
        tb_account account0_ 
    where
        account0_.account_name like '%테슬라%'
    order by
        account0_.account_code desc limit 10

     

     

    2. 거래처명을 '테슬라' 라는 키워드로 검색하고 부서코드 1로 조회하기(조인 후 다른 테이블의 컬럼검색)

    select
        account0_.account_code as account_1_0_,
        account0_.account_name as account_4_0_,
        account0_.account_number as account_5_0_
    from
        tb_account account0_ 
    inner join
        tb_employee employee1_ 
        on account0_.emply_code=employee1_.emply_code 
    inner join
        tbl_department department2_ 
        on employee1_.department_code=department2_.department_code 
    where
        department2_.department_code=1 
        and (
            account0_.account_name like '%테슬라%'
        ) 
    order by
        account0_.account_code desc limit 10

     

     

    거래처명을 테슬라로 검색하고 부서코드를 1로 조회했다.

    AccountService.java에서 if문의 조건이 전부 성립하여 spec에 쿼리가 누적되어 SQL이 완성되었다.

     

    반응형

    댓글

Designed by Tistory.