๐ ๊ฐ์
ํ๋ก์ ํธ ์งํ ์ค
๊ด๋ฆฌ์ ํ์ด์ง์์ ์ฌ๋ฌ ์กฐ๊ฑด์ผ๋ก ๊ธฐ์ ์ ๊ฒ์ํด์ผ ํ๋ API๊ฐ ํ์ํ๋ค.
๋์ ์ฟผ๋ฆฌ๋ฅผ ์ ์ฐ๊ณ
๋จ์ํ if๋ฌธ์ผ๋ก ๋ถ๊ธฐ์ฒ๋ฆฌํด์ ์ผ์ผ์ด ์ฟผ๋ฆฌ๋ฌธ์ ๋ ๋ฆฌ๋๋ก ํ ์ ์๊ฒ ์ง๋ง
service ๋ repository ๋จ ๋ฉ์๋๋ ์ฝ๋๊ฐ ์๋นํ ๋์ด๋๊ธฐ ๋๋ฌธ์ ์ง์ ๋ถํด์ง๋ค.
์กฐ๊ฑด์ด 4๊ฐ์ง์ฌ์ repository์ ํด๋น API๋ฅผ ์ํ ์กฐํ ๋ฉ์๋๊ฐ 16๊ฐ๋ ํ์ํ๋ค.
ํน์ฌ๋ ์กฐ๊ฑด์ด ์ถ๊ฐ๋๋ฉด ์ ์ ๊ด๋ฆฌํ๊ธฐ๋ ์ด๋ ค์์ง ๊ฒ์ด๋ค.
์ด๋ฌํ ๋จ์ ์ ํด๊ฒฐํ๊ธฐ ์ํด JPA ๋์ ์ฟผ๋ฆฌ๋ฅผ ํ์ฉํ๊ธฐ๋ก ํ๋ค.
JPA์์ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ณดํต JPQL, Criteria, Specification, QueryDSL ์ด๋ ๊ฒ 4๊ฐ์ง๊ฐ ์๋ค.
๋๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก JPA๊ฐ ์๋ MyBatis๋ ๊ฒ์ ๊ธฐ๋ฅ ์์ฒด๋ฅผ ์ปดํฌ๋ํธ๋ก ๋นผ๋ ๋ฐฉ๋ฒ๋ ์๋ค๊ณ ํ์ง๋ง
๋ JPA๋ง ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ ์์ ๋ฐฉ๋ฒ๋ง ๊ธฐ๋กํ๋ค.
๐ ๋์ ์ฟผ๋ฆฌ ์ฌ์ฉํ๊ธฐ
โญ JPQL
// Repository
public Page<Company> findWithFilters(
final Pageable pageable,
final FindCompanyAdminRequest request
) {
String jpql = "select c from Company c";
List<String> whereFilter = new ArrayList<>();
if (StringUtils.hasText(request.getType())) {
whereFilter.add("c.type = :type");
}
if (StringUtils.hasText(request.getCity())) {
whereFilter.add("c.city = :city");
}
if (StringUtils.hasText(request.getDistrict())) {
whereFilter.add("c.district = :district");
}
if (request.getStatus() != null) {
whereFilter.add("c.status = :status");
}
if (!whereFilter.isEmpty()) {
jpql += " where " + String.join(" and ", whereFilter);
}
TypedQuery<Company> query = entityManager.createQuery(jpql, Company.class);
if (StringUtils.hasText(request.getType())) {
query.setParameter("type", request.getType());
}
if (StringUtils.hasText(request.getCity())) {
query.setParameter("city", request.getCity());
}
if (StringUtils.hasText(request.getDistrict())) {
query.setParameter("district", request.getDistrict());
}
if (request.getStatus() != null) {
query.setParameter("status", request.getStatus());
}
List<Company> companies = query.setFirstResult((int) pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
return new PageImpl<>(companies, pageable, companies.size());
}
// Service
Page<Company> companies = companyRepository.findWithFilters(generatePageDesc(pageNo, LARGE_PAGE_SIZE, criterion), request);
๋์ ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ์ง ์์ ๋์ ๋น๊ตํด์ Repository์ ๋ฉ์๋์ Service์ ์ฝ๋๊ฐ ์ค์ด๋ค์๋ค.
ํ์ง๋ง ์ฌ์ ํ ๋จ์ ์ ์กด์ฌํ๋ค.
๊ฐ ์กฐ๊ฑด์ ๋ฐ๋ผ์ if ๋ฌธ์ ์ฌ์ฉํ ๋ฐฉ์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ณ ์์ง๋ง, ๋ฌธ์์ด์ ํตํด์ ํ๊ณ ์๋ค.
๋ฌธ์์ด์ ํตํด์ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค๊ฒ ๋๋ฉด ์์ฑํ ๋ฌธ์์ด ์ฟผ๋ฆฌ ์ค ๋์ด์ฐ๊ธฐ ํน์ ์ํ๋ฒณ์ ์ค๋ฅ๊ฐ ์์ ๊ฒฝ์ฐ
์ด๋ฅผ ์ปดํ์ผ ๋จ๊ณ์์ ์ก์์ฃผ์ง ๋ชปํ๋ค๋ ๋จ์ ์ด ์กด์ฌํ๋ค.
์ฆ, ์ฟผ๋ฆฌ๋ฅผ ๋์ ์ผ๋ก ์์ฑํ๋ค ๋ณด๋ ๊ฐ๋ฐ์๊ฐ ์ค์ํ ์ ์๋ ํฌ์ธํธ๋ ๋ ๋์ด๋ฌ์ง๋ง
์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ธฐ ์ ๊น์ง๋ ์ค๋ฅ๋ฅผ ์ฐพ์ ์ ์๋ค.
โญ Criteria
Criteria๋ JPQL์ ๋ฌธ์์ด์ด ์๋ ์๋ฐ ์ฝ๋๋ก ์์ฑํ๋๋ก ๋์์ฃผ๋ API๋ค.
๋ฌธ๋ฒ์ ์ธ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ๋จ๊ณ์์ ์ก์ ์ ์์ผ๋ฏ๋ก
JPQL๋ณด๋ค ์ข ๋ ์์ ํ๊ฒ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋๋ก ๋๋๋ค.
// CompanyRepositoryCustomImpl
public Page<Company> findWithFilters(
final Pageable pageable,
final FindCompanyAdminRequest request
) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Company> cq = cb.createQuery(Company.class);
Root<Company> root = cq.from(Company.class);
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.hasText(request.getType())) {
predicates.add(cb.equal(root.get("type"), request.getType()));
}
if (StringUtils.hasText(request.getCity())) {
predicates.add(cb.equal(root.get("city"), request.getCity()));
}
if (StringUtils.hasText(request.getDistrict())) {
predicates.add(cb.equal(root.get("district"), request.getDistrict()));
}
if (request.getStatus() != null) {
predicates.add(cb.equal(root.get("status"), request.getStatus()));
}
cq.where(cb.and(predicates.toArray(new Predicate[0])));
cq.select(root);
List<Company> companies = entityManager.createQuery(cq)
.setFirstResult((int) pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
return new PageImpl<>(companies, pageable, companies.size());
}
JPQL์ ์ด์ฉํด์ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๊ฐ๋จํด์ก๋ค.
๋ํ ๋ฌธ์์ด์ ํตํด์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ง ์์ ๊ฐ๋ฐ์๊ฐ ์ค์ํ ์ ์๋ ํฌ์ธํธ๋ ์ค์๋ค.
์ด์ฒ๋ผ JPA์์ ์ ๊ณตํ๋ Critera๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ๋์ ์ฟผ๋ฆฌ๋ฅผ ๋ ๊ฐ๋จํ๊ณ ์์ ํ๊ฒ ์์ฑํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค.
ํ์ง๋ง ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ธฐ ์ํ ์ฝ๋๊ฐ ์ด๋ ค์์ก๋ค.
๊ทธ๋ฆฌ๊ณ ์์ฑํด์ผ ํ๋ Predicate๊ฐ ๋ง์์ง๋ค๋ฉด ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์์ง๊ณ
์ง๊ด์ ์ผ๋ก ์ดํดํ๊ธฐ ํ๋ค๋ค๋ ๋จ์ ์ด ๋ฐ์ํ๋ค.
โญ Specification
Specification์ Spring Data JPA์์ ์ ๊ณตํ๋ API๋ก, ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ์ ์ฐํ๊ฒ ์์ฑํ ์ ์๊ฒ ํด์ค๋ค.
JPA๋ Criteria์ ์ฌ์ฉ์ฑ์ ๋์ด๊ธฐ ์ํด์ Specification์ผ๋ก ํด๊ฒฐ๋ฒ์ ์ ๊ณตํ๊ณ ์๋ค.
Specification์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Repository์์ JpaSpecificationExecutor์ ์์ ๋ฐ์์ผ ํ๋ค.
// Repository
public interface CompanyRepository extends JpaRepository<Company, Long>, JpaSpecificationExecutor<Company> {
Page<Company> findWithFilters(final Specification<Company> spec, final Pageable pageable);
}
// Service
Page<Company> companies = companyRepository.findWithFilters(CompanySpec.findWith(request), generatePageDesc(pageNo, LARGE_PAGE_SIZE, criterion));
๊ธฐ์กด์ JPA์ ๋ฌ๋ผ์ง ์ ์ Specification<Company> spec ์ ๋ฐ์์ ์ฌ์ฉํ๊ณ ์๋ค.
Specification์ ์ด์ฉํด์ ๋์ ์ฟผ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์๊ฒ ๋์๋ค.
// CompanySpec
public static Specification<Company> findWith(final FindCompanyAdminRequest request) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (request.getType() != null && !request.getType().isEmpty()) {
predicates.add(cb.equal(root.get("type"), request.getType()));
}
if (request.getCity() != null && !request.getCity().isEmpty()) {
predicates.add(cb.equal(root.get("city"), request.getCity()));
}
if (request.getDistrict() != null && !request.getDistrict().isEmpty()) {
predicates.add(cb.equal(root.get("district"), request.getDistrict()));
}
if (request.getStatus() != null) {
predicates.add(cb.equal(root.get("status"), request.getStatus()));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
CompanySpec ํด๋์ค๋ฅผ ๋ง๋ค๊ณ ์ ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
Criteria๋ง ํ์ฉํ๋ ๊ฒ๋ณด๋ค ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๋จํด์ง ๋ชจ์ต์ ๋ณผ ์ ์๋ค.
CriteriaQuery ์์ฑ์ ์ํ ์ค๋น ์ฝ๋๋ฅผ ์์ฑํ์ง ์์๋ ๋๊ณ ,
JPA๋ฅผ ํ์ฉํ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ ์ด์ ๋ณด๋ค ๋ณด๊ธฐ ์ข๊ณ ์ฌ์ด ์ฝ๋๊ฐ ์์ฑ๋์๋ค.
ํ์ง๋ง ์ฌ์ ํ Criteria๋ฅผ ํตํ ๋์ ์ฟผ๋ฆฌ ์์ฑ ๋ฐฉ์์ ๋ณต์กํ๊ณ ์ฅํฉํ๋ค.
๋ฌด์๋ณด๋ค ์ฝ๋์ ํํ๊ฐ SQL๊ณผ๋ ๊ฑฐ๋ฆฌ๊ฐ ์๋ค.
โญ QueryDSL
QueryDSL์ ํ์ ์์ ์ฑ์ ์ ๊ณตํ๊ณ , ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ์ง๊ด์ ์ผ๋ก ์์ฑํ ์ ์๊ฒ ํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ Criteria์ ๋ณต์กํจ์ ๊ฐ์ ํ ์ ์๋ JPQL ๋น๋ ์ญํ ์ ํ๋ ์คํ ์์ค ํ๋ก์ ํธ๋ค.
๋ฌธ์์ด์ด ์๋ ์๋ฐ ์ฝ๋๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ์์๋ ๋ถ๊ตฌํ๊ณ ,
๋ ์ฝ๊ณ ๊ฐ๊ฒฐํ๋ฉฐ ํํ๋ SQL๊ณผ ๋น์ทํ๊ฒ ๊ฐ๋ฐํ ์ ์๋ค๋ ๊ฒ์ด QueryDSL์ ์ฅ์ ์ด๋ค.
// CompanyRepositoryCustomImpl
public Optional<Page<Company>> findWithFilters(
final Pageable pageable,
final FindCompanyAdminRequest request
) {
List<Company> companies = jpaQueryFactory
.selectFrom(company)
.where(
toContainsType(request.type()),
toContainsCity(request.city()),
toContainsDistrict(request.district()),
eqCompanyStatus(CompanyStatus.valueOf(request.companyStatus()))
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
return Optional.of(new PageImpl<>(companies, pageable, companies.size()));
}
private BooleanExpression toContainsType(final String type) {
if (!StringUtils.hasText(type)) {
return null;
}
return company.type.contains(type);
}
private BooleanExpression toContainsCity(final String city) {
if (!StringUtils.hasText(city)) {
return null;
}
return company.city.contains(city);
}
private BooleanExpression toContainsDistrict(final String district) {
if (!StringUtils.hasText(district)) {
return null;
}
return company.district.contains(district);
}
private BooleanExpression eqCompanyStatus(final CompanyStatus companyStatus) {
if (companyStatus == null) {
return null;
}
return company.companyStatus.eq(companyStatus);
}
Criteria์ ๋น๊ตํ์ ๋ ๋ถํ์ํ Predicate๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ด ์์ด ๋ ๊ฐ๊ฒฐํ๊ณ ๊ฐ๋ ์ฑ์ด ์ข์์ก๋ค.
๊ทธ๋ฆฌ๊ณ ๋ SQL์ค๋ฝ๊ฒ ์ฝ๋๊ฐ ์์ฑ๋์๋ค.
Where ๋ฌธ์ ์ด๋ค ์กฐ๊ฑด๋ค์ด ๋ค์ด๊ฐ๋์ง ํ์ธํ ์ ์์ด ํจ์ฌ ๊ฐ๋ ์ฑ์ด ์ข์์ก๋ค.
์ด์ฒ๋ผ QueryDSL ์ ํตํด์ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐฉ์์ด Specification์ ๋นํด์ ๊ฐ๋ ์ฑ์ด ๋ ์ข๋ค.
ํ์ง๋ง QueryDSL ์๋ ๋จ์ ์ด ์๋ ๊ฒ์ ์๋๋ค.
Criteria๋ณด๋ค๋ ์ฝ์ง๋ง, SQL ์์ฑ์ ์ํด์ ๋ฌ๋ ์ปค๋ธ๊ฐ ํ์ํ๋ค.
๋ํ Q Class์ ์์กดํ์ฌ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ๋ ๋จ์ ์ด๋ค.
Q Class๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ด
์ด๋ค ๋ฒ์ ๊ณผ ๋น๋ ๋ฐฉ์์ ์ฌ์ฉํ๋๊ฐ์ ๋ฐ๋ผ์ ์ ๊ฒฝ์ ์ฐ๊ณ ๋ค๋ฅด๊ฒ ์ค์ ํด ์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฒ์์ Q ํด๋์ค import๊ฐ ์ ๋์ด์ ๋ง์ด ๋นํฉํ๊ณ ๋ฐฉ๋ฒ์ ์ฐพ๋๋ผ ๊ณ ์ํ๋ค ใ .
๐ ๊ฒฐ๋ก
์ง๊ธ๊น์ง ์ฌ๋ฌ ์กฐ๊ฑด์ผ๋ก ์กฐํํ๋ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ๊ธฐ ์ํด
JPA์์ ์ ํํ ์ ์๋ ๋์๋ค์ ๋ํด์ ๊ธฐ๋กํ๋ค.
์ด๋ฐ์๋ Service ๋ ์ด์ด์์ if ๋ฌธ์ ํตํด์ ๊ฐ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๊ฒ์ด ์ง๊ด์ ์ด๊ณ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ํธํ๋ค.
ํ์ง๋ง ๊ฒ์์ ๋ํ ์กฐ๊ฑด์ด ์ ์ฐจ ๋์ด๋จ์ ๋ฐ๋ผ์ ๋์ ์ฟผ๋ฆฌ์ ํ์์ฑ์ ๋๊ผ์ต๋๋ค.
์ต์ข ์ ์ผ๋ก Querydsl์ ํ์ฉํด์ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์ ํํ๋ค.
๊ทธ ์ด์ ๋ SQL ๋ฌธ์ ์์ฑํจ์ ์์ด์ ์ปดํ์ผ ํ์์ ์ค๋ฅ๋ฅผ ์ก์์ค ์ ์์ด์ผ ํ๊ณ ,
๋ฌด์๋ณด๋ค ๊ฐ๋ ์ฑ์ด ์ค์ํ๋ค๊ณ ๋๊ผ๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ๋ -