RE-Heat 개발자 일지

[JPA 활용2] [6] 스프링 데이터 JPA·Querydsl 소개 본문

백엔드/JPA

[JPA 활용2] [6] 스프링 데이터 JPA·Querydsl 소개

RE-Heat 2023. 9. 7. 20:57

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-API%EA%B0%9C%EB%B0%9C-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94/dashboard

 

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 - 인프런 | 강의

스프링 부트와 JPA를 활용해서 API를 개발합니다. 그리고 JPA 극한의 성능 최적화 방법을 학습할 수 있습니다., 스프링 부트, 실무에서 잘 쓰고 싶다면? 복잡한 문제까지 해결하는 힘을 길러보세요

www.inflearn.com

인프런 김영한 님의 강의를 듣고 작성한 글입니다.

 

[1] 스프링 데이터 JPA 소개

스프링 데이터 JPA는 JPA를 사용할 때 지루하게 반복되는 코드를 자동화해 준다. 

 

MemberRepositoryOld - 스프링 데이터 적용 전

@Repository
@RequiredArgsConstructor
public class MemberRepositoryOld {

    private final EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }

    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class) //JPQL from의 대상이 엔티티라는 게 좀 다름
                .getResultList();
    }

    public List<Member> findByName(String name) {
        return em.createQuery("select m From Member m where m.name= :name", Member.class)
                .setParameter("name", name)
                .getResultList();
    }
}

 

MemberRepository - 스프링 데이터 적용 후

public interface MemberRepository extends JpaRepository<Member, Long> {

    //select m from Member m where m.name = ?
    List<Member> findByName(String name);
}
  • 인터페이스로 만들고 JpaRepository를 상속한다.
  • JpaRepository<>라는 인터페이스는 기본적인 CRUD 기능을 제공한다.
    • save, findById 등 생각할 수 있는 다양한 기능을 제공한다.
    • 공통이 아닌 findByName()도 따로 넣어주면 where 조건에 name이 자동으로 들어간다.
  • 개발자는 인터페이스만 만들면 구현체는 스프링 데이터 JPA가 애플리케이션 실행시점에 주입해 준다.

 

 

[2] Querydsl 소개

Querydsl은 SQL(JPQL)과 모양이 유사하며 자바 코드로 동적 쿼리를 편리하게 생성할 수 있다.

 

■ Querydsl 적용 전 기존 동적쿼리

① 문자로 만든 동적 쿼리

  • 띄어쓰기 실수라도 하면 오류가 뜬다.

② JPA Criteria

  • 어떤 JPQL이 날아갈지 생각해 내기 쉽지 않다.

 

■ Querydsl 적용

① 세팅 방법

build.gradle 수정

//querydsl 추가
buildscript {
	dependencies {
		classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10")
	}
}

plugins {
	id 'org.springframework.boot' version '2.4.1'
	id 'io.spring.dependency-management' version '1.0.10.RELEASE'
	id 'java'
}

group = 'jpabook'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

//apply plugin: 'io.spring.dependency-management'
apply plugin: "com.ewerk.gradle.plugins.querydsl"

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-devtools'
	implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'
// implementation 'org.hibernate:hibernate-core:5.4.13.Final'

	implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.6'

	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'

	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	//추가
	testImplementation("org.junit.vintage:junit-vintage-engine") {
		exclude group: "org.hamcrest", module: "hamcrest-core"
	}

	//querydsl 추가
	implementation 'com.querydsl:querydsl-jpa'
	//querydsl 추가
	implementation 'com.querydsl:querydsl-apt'
}


//querydsl 추가
//def querydslDir = 'src/main/generated'
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
	library = "com.querydsl:querydsl-apt"
	jpa = true
	querydslSourcesDir = querydslDir
}

sourceSets {
	main {
		java {
			srcDirs = ['src/main/java', querydslDir]
		}
	}
}

compileQuerydsl{
	options.annotationProcessorPath = configurations.querydsl
}

configurations {
	querydsl.extendsFrom compileClasspath
}
  • generated된 Q파일이 어디에 위치할지 명시하는 등 세팅 작업이 조금 복잡한 편이다.

 

② Querydsl 적용 동적 쿼리

public List<Order> findAll(OrderSearch orderSearch) {
        QOrder order = QOrder.order;
        QMember member = QMember.member;

        JPAQueryFactory query = new JPAQueryFactory(em)
        
        return query
                .select(order)
                .from(order)
                .join(order.member, member)
                .where(statusEq(orderSearch.getOrderStatus()),
                        nameLike(orderSearch.getMemberName()))
                .limit(1000)
                .fetch();
    }

    private BooleanExpression statusEq(OrderStatus statusCond) {
        if (statusCond == null) {
            return null;
        }
        return order.status.eq(statusCond);
    }

    private BooleanExpression nameLike(String nameCond) {
        if (!StringUtils.hasText(nameCond)) {
            return null;
        }
        return member.name.like(nameCond);
    }
  • QOrder, QMember는 static화, JPAQueryFactory는 생성자 주입 방식으로 처리 가능

 

■ Querydsl 장점

  • 직관적인 문법
  • 컴파일 시점에 빠른 문법 오류 발견 : 자바 코드여서 가능함
  • 코드 자동완성 : 자바코드여서 IntelliJ에서 자동완성 기능 제공
  • 코드 재사용 : statusEq 등 재사용 가능한 데 역시 자바 코드여서 가능한 일이다. 
  • JPQL new 명령어와는 비교는 안될 정도로 깔끔한 DTO 조회를 지원한다.