๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
[JAVA]/JPA

JPA Specification์„ ์ด์šฉํ•˜์—ฌ ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

by ํŒกํŽ‘ํ 2024. 1. 31.
728x90

๐Ÿค” ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰์ด๋ž€?

<๋‹ค๋‚˜์™€>

  • ๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰์ด๋ž€ ์นดํ…Œ๊ณ ๋ฆฌ ๊ฒ€์ƒ‰, ์ƒ์„ธ ๊ฒ€์ƒ‰ ๋“ฑ๊ณผ ๊ฐ™์ด ์‡ผํ•‘๋ชฐ ๋“ฑ์—์„œ ์—ฌ๋Ÿฌ ํ•„ํ„ฐ ์กฐ๊ฑด์œผ๋กœ ๊ฒ€์ƒ‰๋œ ๊ฒฐ๊ณผ๋ฅผ ์ถ•์†Œ์‹œ์ผœ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ฒฐ๊ด๊ฐ’๋งŒ์„ ๋„์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค.
  • ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์„๊นŒ ๊ณ ๋ฏผํ•ด๋ณด๋ฉด ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ท€๊ฒฐ๋˜๋Š”๋ฐ ๋ฐ”๋กœ '๋™์  ์ฟผ๋ฆฌ'์ด๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ํ•„ํ„ฐ๋ง์— ๋งž์ถฐ ์กฐํšŒ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ ค์ฃผ๋ฉด ๋˜๋Š” ๊ฒƒ์ธ๋ฐ ๊ฒฐ๊ตญ ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ฟผ๋ฆฌ๋ฌธ์ด ๋‹ฌ๋ผ์ง€๊ฒŒ ๋œ๋‹ค๋Š” ์ด์•ผ๊ธฐ์ด๋ฏ€๋กœ Jpa์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ๋ฉ”์„œ๋“œ์™€ ๊ฐ™์ด ์ •ํ•ด์ง„ ์ฟผ๋ฆฌ์— value๋งŒ ๋‹ค๋ฅด๊ฒŒ ๋ณด๋‚ด๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

๐Ÿ“œ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉ๋  View

  • member ํ…Œ์ด๋ธ”๊ณผ team ํ…Œ์ด๋ธ”์—์„œ ์ถ”์ถœํ•˜์—ฌ ๋งŒ๋“  view์ด๋‹ค.
  • view๋‚˜ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ๊ธ€์„ ์ฐธ๊ณ ํ•˜์ž.

 

๐Ÿ’ก ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ• ์„ธ ๊ฐ€์ง€

๋ฌธ์ž์—ด ์ฟผ๋ฆฌ ๋งŒ๋“ค๊ธฐ

String jpql = "select * from TestView v"; // JPQL ๊ธฐ๋ณธ ์ฟผ๋ฆฌ

if (memberName != null) {
    jpql += " where v.memberName = :memberName"; // ๋™์ ์œผ๋กœ ์กฐ๊ฑด ์ถ”๊ฐ€
}

// EntityManager๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JPQL ์ฟผ๋ฆฌ ์‹คํ–‰
TypedQuery<TestView> query = entityManager.createQuery(jpql, View.class);

if (memberName != null) {
    query.setParameter("memberName", memberName); // ๋™์ ์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
}

List<TestView> results = query.getResultList();
  • ๋ง ๊ทธ๋Œ€๋กœ ๋ฌธ์ž์—ด๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด์–ด @query ์• ๋„ˆํ…Œ์ด์…˜์— ์‹ฃ๋Š” ๊ฒƒ์ด๋‹ค.
  • jpql์„ ์˜ˆ๋กœ ๋“ ๋‹ค๋ฉด ์œ„์™€ ๋น„์Šทํ•œ ๋กœ์ง์ด ๋‚˜์˜ค๋Š”๋ฐ ์ง๊ด€์ ์ด์ง€๋งŒ ์—ฐ์‚ฐ์ด ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ๋ณต์žกํ•ด์งˆ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ณ  ๋…ธ๊ฐ€๋‹ค ์ž‘์—…์ด ์‹ฌํ•ด์ง€๊ณ  ๋ฌธ์ž์—ด์„ ๋‹ค๋ฃจ๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ˆ˜์— ์‰ฝ๊ฒŒ ๋…ธ์ถœ๋˜๋ฉฐ ์œ ์ง€๋ณด์ˆ˜์—๋„ ์–ด๋ ค์›€์ด ์žˆ๋‹ค.

 

Querydsl

  • Querydsl์€ ์ž๋ฐ” ๊ธฐ๋ฐ˜์˜ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ SQL๊ณผ ์œ ์‚ฌํ•œ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ปดํŒŒ์ผ ์‹œ์ ์— ํƒ€์ž… ์ฒดํฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์œผ๋ฉฐ IDE์˜ ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋‚˜์˜ ๊ฒฝ์šฐ์—๋Š” ํ˜„์žฌ ์‚ฌ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์™ธ๋ถ€ ๋ชจ๋“ˆ๋กœ ๋ณ„๋„์˜ ์˜์กด์„ฑ ์ถ”๊ฐ€๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋Š” ์ ์—์„œ ๋ฐฐ์ œํ–ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ์–ธ์  ๊ฐ€ ํ•„์š”์— ์˜ํ•ด Querydsl์ด ๋„์ž…๋œ๋‹ค๋ฉด ์ ๊ทน์ ์œผ๋กœ ๋ฆฌํŒฉํ† ๋ง์— ์ž„ํ•  ์ƒ๊ฐ์ด๋‹ค.

 

CreteriaQuery & JPA Specification

CreteriaQuery

  • CriteriaQuery๋Š” JPA์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ฟผ๋ฆฌ ์ž‘์„ฑ์„ ์œ„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.
  • CriteriaQuery๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹  ์ฝ”๋“œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๊ฐ์ฒด ์ง€ํ–ฅ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. 
  • CriteriaQuery๋Š” CriteriaBuilder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑํ•˜๊ณ  select, from, where, orderBy ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…, ์กฐ๊ฑด, ์ •๋ ฌ ๋“ฑ์„ ์„ค์ •ํ•œ๋‹ค. 

JPA Specification

  • Spring Data JPA์—์„œ๋Š” Specification์ด๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
    • ๋”ฐ๋ผ์„œ Querydsl์ฒ˜๋Ÿผ ๋ณ„๋„์˜ ์„ธํŒ…์ด ํ•„์š”์—†๋‹ค.
  • CriteriaQuery๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ CriteriaQuery๋ฅผ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค€๋‹ค.
  • Specification์„ ์‚ฌ์šฉํ•˜๋ฉด ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด์„ ๋ฉ”์„œ๋“œ๋กœ ์ •์˜ํ•˜๊ณ  ์ด๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ Spring Data JPA๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์น˜๋ช…์ ์ธ ๋‹จ์ ์ด ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ ์›ํ•˜๋Š” ์ปฌ๋Ÿผ์ด๋‚˜ ์—ฐ์‚ฐ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ์ปฌ๋Ÿผ ์ „์ฒด๋ฅผ ์ „๋ถ€ ๊ฐ€์ ธ์™€์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ํ•„์š”ํ•œ ์ปฌ๋Ÿผ์ด๋‚˜ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋งŒ์„ ๊ฐ€์ ธ์˜ค๋„๋ก view์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„ ๋ณด์ธ๋‹ค.
    • ์ผ๋ฐ˜ ํ…Œ์ด๋ธ”์— ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ๊นŒ์ง€ ๋ชจ๋‘ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
  • ์œ„์˜ ๋‚ด์šฉ์„ ํ† ๋Œ€๋กœ ๋‚˜๋Š” JPA Specification์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

 

๐Ÿ“œ View

@Getter
@Entity
@Table(name = "test_view")
@Immutable // ์ฝ๊ธฐ ์ „์šฉ ์—”ํ‹ฐํ‹ฐ์ž„์„ ๋ช…์‹œ
public class View {

    @Id
    private Long memberId;

    private String playerName;

    private int age;

    @Enumerated(EnumType.STRING)
    private Sex sex;

    private String teamName;

    private String city;
}
  • ๋ทฐ์™€ ๋งคํ•‘๋œ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์ด๋‹ค.
  • ์ผ๋ฐ˜ ํด๋ž˜์Šค์— ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ๊ณผ ์–ด๋– ํ•œ ์ฐจ์ด๋„ ์—†๋‹ค.

 

๐Ÿ“œ ViewRepository

public interface ViewRepository extends JpaRepository<View, Long>, JpaSpecificationExecutor<View> {

}
  • JPA Specification ์‚ฌ์šฉ์„ ์œ„ํ•ด JpaSpecificationExecutor<View>์˜ ์ƒ์†์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

๐Ÿ“œ ViewSpecification

public class ViewSpecification {

    public static Specification<View> likePlayerName(String playerName) {
        return (root, query, CriteriaBuilder) -> CriteriaBuilder.like(root.get("playerName"), "%" + playerName + "%");
    }

    public static Specification<View> likeTeamName(String teamName) {
        return (root, query, CriteriaBuilder) -> CriteriaBuilder.like(root.get("teamName"), "%" + teamName + "%");
    }

    public static Specification<View> rangeAge(int min,  int max) {
        return (root, query, CriteriaBuilder) -> CriteriaBuilder.between(root.get("age"), min, max);
    }

    public static Specification<View> equalsSex(Sex sex) {
        return (root, query, CriteriaBuilder) -> CriteriaBuilder.equal(root.get("sex"), String.valueOf(sex));
    }

    public static Specification<View> equalsCity(String city) {
        return (root, query, CriteriaBuilder) -> CriteriaBuilder.equal(root.get("city"), city);
    }
}
  • ๋งจ ์œ„ likePlayerName() ๋ฉ”์„œ๋“œ ์œ„์ฃผ๋กœ ์„ค๋ช…ํ•ด ๋ณด๊ฒ ๋‹ค.
    • ๋‚˜๋จธ์ง€ ๋ฐฉ์‹๋„ ๊ฑฐ์˜ ๋น„์Šทํ•˜๋‹ค.
    • Enum ํƒ€์ž…์˜ ๊ฒฝ์šฐ equalsSex() ๋ฉ”์„œ๋“œ์™€ ๊ฐ™์ด ์ŠคํŠธ๋ง ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•ด ์ค˜์•ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • public static Specification<View> likePlayerName(String playerName):
    • ์ด ๋ฉ”์„œ๋“œ๋Š” playerName์— ๋Œ€ํ•œ ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•˜๋Š” Specification ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • View๋Š” ๊ฒ€์ƒ‰ ๋Œ€์ƒ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์ด๋‹ค.
  • (root, query, CriteriaBuilder) -> CriteriaBuilder.like(root.get("playerName"), "%" + playerName + "%"):
    • ์ด ๋ถ€๋ถ„์€ Specification์˜ ์‹ค์ œ ๊ตฌํ˜„์„ ์ •์˜ํ•˜๋Š” ๋žŒ๋‹ค ํ‘œํ˜„์‹์ด๋‹ค.
    • root๋Š” ์ฟผ๋ฆฌ์˜ ๋ฃจํŠธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” Root ๊ฐ์ฒด์ด๋‹ค.
    • query๋Š” CriteriaQuery ๊ฐ์ฒด๋กœ ์ฟผ๋ฆฌ ์ž‘์„ฑ์— ์‚ฌ์šฉ๋œ๋‹ค.
    • CriteriaBuilder๋Š” CriteriaQuery๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋นŒ๋” ๊ฐ์ฒด์ด๋‹ค.
    • CriteriaBuilder.like() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ƒ‰์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
      • ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ equal(), between() ๋“ฑ SQL ์กฐ๊ฑด๋ฌธ์— ํ•ด๋‹นํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
    • root.get("playerName")๋Š” root ์—”ํ‹ฐํ‹ฐ์—์„œ "playerName" ์†์„ฑ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
    • CriteriaBuilder.like() ๋ฉ”์„œ๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•ด๋‹น ์†์„ฑ์„ ์ „๋‹ฌํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๊ฒ€์ƒ‰์–ด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
      • "%" + playerName + "%"๋Š” ๊ฒ€์ƒ‰์–ด ์•ž๋’ค์— "%"๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ์ž‘์„ฑ๋œ ์ฝ”๋“œ๋กœ ์„œ๋น„์Šค๋‹จ์—์„œ likePlayerName ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ playerName์— ๋Œ€ํ•œ ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด ๋ฉ”์„œ๋“œ๋Š” Specification ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ํ•ด๋‹น Specification์„ JPA Specification์„ ์ง€์›ํ•˜๋Š” ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌํ•˜์—ฌ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

๐Ÿ“œ ๊ธฐํƒ€ ๋ฉ”์„œ๋“œ

// ์—ฐ๋ น๋Œ€ ๊ทธ๋ฃน ๊ฒ€์ƒ‰
public static Specification<View> containsAgeGroup(String[] ageGroup) {
        return (root, query, CriteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            for (String age : ageGroup) {
                predicates.add(CriteriaBuilder.equal(root.get("ageGroup"), age));
            }
            return CriteriaBuilder.or(predicates.toArray(new Predicate[0]));
        };
    }

// ์ƒ๋…„์›”์ผ ๊ธฐ๊ฐ„ ๊ฒ€์ƒ‰
public static Specification<FilterMember> betweenBirth(LocalDateTime startDate, LocalDateTime endDate) {
    return (root, query, CriteriaBuilder) -> CriteriaBuilder.between(root.get("birth"), startDate, endDate);
}
  • ์‹ค์ œ๋กœ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์ง€๋งŒ ์–ด๋Š ์—ฐ๋ น๋Œ€์— ํฌํ•จ๋˜๋Š”์ง€ ํ™•์ธํ•  ๋•Œ, ์ƒ๋…„์›”์ผ ๊ธฐ๊ฐ„์œผ๋กœ ๊ฒ€์ƒ‰ํ•  ๋•Œ๋Š” ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

๐Ÿ“œ ViewController

@Slf4j
@RestController
@RequiredArgsConstructor
public class SearchController {

    private final ViewRepository viewRepository;
    
 	@GetMapping("/views")
    public ResponseEntity<?> getSearchResults(
            @RequestParam(value = "playerName", required = false) String playerName,
            @RequestParam(value = "teamName", required = false) String teamName,
            @RequestParam(value = "minAge", required = false) Integer minAge,
            @RequestParam(value = "maxAge", required = false) Integer maxAge,
            @RequestParam(value = "sex", required = false) Sex sex,
            @RequestParam(value = "city", required = false) String city,
            Pageable pageable
    ) {

        Specification<View> spec = (root, query, criteriaBuilder) -> null;

        if (playerName != null) {
            spec = spec.and(ViewSpecification.likePlayerName(playerName));
        }

        if (teamName != null) {
            spec = spec.and(ViewSpecification.likeTeamName(teamName));
        }

        if (minAge != null) {
            spec = spec.and(ViewSpecification.rangeAge(minAge, maxAge));
        }

        if (sex != null) {
            spec = spec.and(ViewSpecification.equalsSex(sex));
        }

        if (city != null) {
            spec = spec.and(ViewSpecification.equalsCity(city));
        }

        Page<View> response = viewRepository.findAll(spec, pageable);

        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}
  • ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ปจํŠธ๋กค๋Ÿฌ์— ์„œ๋น„์Šค ๋กœ์ง์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค. 
  • @RequestParam ์• ๋„ˆํ…Œ์ด์…˜์„ ์ด์šฉํ•ด ํ•„์š”ํ•œ ๊ฐ’์„ ๋ฐ›๋Š”๋‹ค.
  • required์˜ deault๋Š” true์ธ๋ฐ false๋กœ ์„ค์ •ํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ์š”์ฒญ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค.
    • ์กฐ๊ฑด ๊ฒ€์ƒ‰์— ํฌํ•จ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
  • Specification<View> spec = (root, query, criteriaBuilder) -> null;
    • spec ๋ณ€์ˆ˜๋Š” Specification<View> ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋ณ€์ˆ˜์ด๋‹ค.
    • ์ดˆ๊ธฐํ™”๋ฅผ ์œ„ํ•ด ๋นˆ Specification์„ ํ• ๋‹นํ•œ๋‹ค.
      • ์–ด๋–ค ์กฐ๊ฑด ์—†์ด ์ „์ฒด ์กฐํšŒ๋„ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
    • ๋‹ค์Œ์œผ๋กœ if ๋ฌธ๋“ค์„ ํ†ตํ•ด ๊ฐ๊ฐ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜(playerName, teamName, minAge, sex, city)์˜ ๊ฐ’์ด null์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๊ณ  ํ•ด๋‹นํ•˜๋Š” ViewSpecification์„ spec์— ์ถ”๊ฐ€ํ•œ๋‹ค.
    • and ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ spec์— ๋งค๊ฐœ๋ณ€์ˆ˜ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
  • ๋งˆ์ง€๋ง‰์œผ๋กœ viewRepository.findAll(spec, pageable)์„ ํ˜ธ์ถœํ•˜์—ฌ spec์— ํ•ด๋‹นํ•˜๋Š” ๋™์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ Page<View> ํƒ€์ž…์˜ ๋ณ€์ˆ˜์ธ response์— ๋„ฃ๋Š”๋‹ค.
    • pageable์€ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด๋กœ, ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค.
    • ์œ„์— ํŒŒ๋ผ๋ฏธํ„ฐ์— pageable ํ•˜๋‚˜๋งŒ ์ถ”๊ฐ€ํ•ด๋„ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์œผ๋กœ page, size, sort์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ธฐ์ž…๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
    • ์ž์„ธํ•œ ๊ฑด ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด ๊ฒ€์ƒ‰ํ•ด ๋ณด์ž.

 

๐Ÿงช PostMan์œผ๋กœ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ธฐ

  • ์œ„์™€ ๊ฐ™์ด ํŒŒ๋ผ๋ฏธํ„ฐ์— ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋นผ๋ณด๋ฉด์„œ ํŠน์ • ์กฐ๊ฑด ๋ณ„๋กœ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๋‚˜์˜ด์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

๐Ÿ“œ ์ „์ฒด ์กฐํšŒ ์‹œ Json Response ์˜ˆ์‹œ

{
    "content": [
        {
            "memberId": 1,
            "playerName": "๋Š๊ทธ๋‘",
            "age": 39,
            "sex": "MAN",
            "teamName": "ํ† ํŠธ๋„˜",
            "city": "๋Ÿฐ๋˜"
        },
        {
            "memberId": 2,
            "playerName": "๋ฒค์ œ๋งˆ",
            "age": 23,
            "sex": "WOMAN",
            "teamName": "ํ† ํŠธ๋„˜",
            "city": "๋Ÿฐ๋˜"
        },
        
        ...
    ]
    "pageable": {
        "pageNumber": 0,
        "pageSize": 20,
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 2,
    "totalElements": 40,
    "first": true,
    "size": 20,
    "number": 0,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "numberOfElements": 20,
    "empty": false
}

 

 

 

์ฐธ๊ณ 

๋คผํŠผ

https://dev-setung.tistory.com/20

https://velog.io/@sierra9707/TIP-JPA์—์„œ-๋™์ -์ฟผ๋ฆฌ๋ฅผ-์ฒ˜๋ฆฌํ•˜๋Š”-๋ฐฉ๋ฒ•

https://bsssss.tistory.com/1280

https://groti.tistory.com/49

https://itecnote.com/tecnote/java-spring-data-jpa-specification-to-select-specific-columns/

728x90