๐Ÿ“ƒ ๋ช…์„ธ

๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„(DDD)๋Š” ๋ช…์„ธ(SPECIFICATION)๋ผ๋Š” ๊ฐœ๋…์„ ์†Œ๊ฐœํ•˜๋Š”๋ฐ, ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋Š” JPA Criteria๋กœ ์ด ๊ฐœ๋…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•œ๋‹ค.

๋ช…์„ธ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ํ•ต์‹ฌ ๋‹จ์–ด๋Š” ์ˆ ์–ด(predcate) ์ธ๋ฐ ๋‹จ์ˆœํžˆ ์ฐธ์ด๋‚˜ ๊ฑฐ์ง“์œผ๋กœ ํ‰๊ฐ€๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  AND, OR ๊ฐ™์€ ์—ฐ์‚ฐ์ž๋กœ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ์ œ์•ฝ ์กฐ๊ฑด ํ•˜๋‚˜ํ•˜๋‚˜๋ฅผ ์ˆ ์–ด๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋Š” org.springframework.data.jpa.domain.Specification ํด๋ž˜์Šค๋กœ ์ •์˜ํ–ˆ๋‹ค.

Specification์€ ์ปดํฌ์ง€ํŠธ ํŒจํ„ด์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์–ด ์—ฌ๋Ÿฌ Specification์„ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰์กฐ๊ฑด์„ ์กฐ๋ฆฝํ•ด ์ƒˆ๋กœ์šด ๊ฒ€์ƒ‰์กฐ๊ฑด์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋ช…์„ธ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด JpaSpecificationExecutor ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์œผ๋ฉด ๋œ๋‹ค.

//JpaSpecificationExecutor ์ƒ์†public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order>{...}
//JpaSpecificationExecutor ์ธํ„ฐํŽ˜์ด์Šคpublic interface JpaSpecificationExecutor<T>{    T findOne(Specification<T> spec);    List<T> findAll(Specification<T> spec);    Page<T> findAll(Specification<T> spec, Pageable pageable);    List<T> findAll(Specification<T> spec, Sort sort);    long count(Specification<T> spec);}

JpaSpecificationExecutor์˜ ๋ฉ”์†Œ๋“œ๋“ค์€ Specification์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„์„œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

//๋ช…์„ธ ์‚ฌ์šฉ ์ฝ”๋“œimport static org.springframework.data.jpa.domain.Specifications.*; //whereimport static org.tmkim.jpashop.domain.spec.OrderSpec.*;public List<Order> findOrders(String name){    List<Order> result = orderRepository.findAll(where(memberName(name)).and(isOrderStatus()));}

Specification๋Š” ๋ช…์„ธ๋“ค์„ ์กฐ๋ฆฝํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ํด๋ž˜์Šค๋‹ค.(where(), and(), or(), not() ๋ฉ”์†Œ๋“œ๋ฅผ ์ œ๊ณต)

findAll()์„ ๋ณด๋ฉด ํšŒ์› ์ด๋ฆ„ ๋ช…์„ธ(memberName)์™€ ์ฃผ๋ฌธ ์ƒํƒœ ๋ช…์„ธ(isOrderStatus)๋ฅผ and๋กœ ์กฐํ•ฉํ•ด์„œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

๋ช…์„ธ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๋•Œ import static์„ ์ ์šฉํ•˜๋ฉด ์ฝ๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค.

//OrderSpec ๋ช…์„ธ ์ •์˜ ์ฝ”๋“œpublic class OrderSpec{    public static Specification<Order> memberName(final String membeName){        return new Specification<Order>(){            public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder){                if(StringUtils.isEmpty(memberName))                    return null;                Join<Order, Member> m = root.join("member", JoinType.INNER); //ํšŒ์›๊ณผ ์กฐ์ธ                return builder.equal(m.get("name"), memberName);            }        }    }    public static Specification<Order> isOrderStatus(){        return new Specification<Order>(){            public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder){                return builder.equal(root.get("status"), OrderStatus.ORDER);            }        }    }}

๋ช…์„ธ๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด Specification ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.(์—ฌ๊ธฐ์„  ํŽธ์˜์ƒ ๋ฌด๋ช… ํด๋ž˜์Šค ์‚ฌ์šฉ)

๋ช…์„ธ๋ฅผ ์ •์˜ํ•  ๋•Œ๋Š” toPredicate(...) ๋ฉ”์†Œ๋“œ๋งŒ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๋Š”๋ฐ JPA Criteria์˜ Root, CriteriaQuery, CriteriaBuilder ํด๋ž˜์Šค๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์–ด์ง€๋Š”๋ฐ,

์ด ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์„ ํ™œ์šฉํ•ด์„œ ์ ์ ˆํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋œ๋‹ค.