JPA

JPA Specification를 사용하여 조인,검색기능 만들기

dev.mk 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이 완성되었다.

 

반응형