[JPA]/실전! Querydsl

[QueryDSL] 프로젝트 환경 설정

쿠릉쿠릉 쾅쾅 2022. 4. 24. 00:59
728x90

 

 

프로젝트 생성

 


 

Querydsl 설정과 검증

Querydsl 사이트 : http://querydsl.com/

MVN Repository : https://mvnrepository.com/artifact/com.querydsl

📌 build.gradle

// querydsl 추가
buildscript {
    ext {
        queryDslVersion = "5.0.0"
    }
}

plugins {
    id 'org.springframework.boot' version '2.6.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'

    //querydsl 추가
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"

    id 'java'
}

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

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'

    //querydsl 추가
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
    implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
    
    // implementation 'com.querydsl:querydsl-jpa'
    // implementation 'com.querydsl:querydsl-apt'

    

    //테스트에서 lombok 사용
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'

    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}


//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가 끝

querydsl 설정을 추가한다. 

해당 위치에 Hello 라는 엔티티를 하나 생성해보자

📌 Hello 엔티티

package study.querydsl.entity;

@Entity
@Getter @Setter
public class Hello {

    @Id @GeneratedValue
    private Long id;
}

참고로 Hello 엔티티를 아예 안만들고 그냥  Tasks → other → compileQuerydsl을 실행시켜도 된다.

그리고나서 Gradle에서 Tasks → other → compileQuerydsl 을 더블 클릭한다.

성공 표시가 뜰 것이다.

build → generated → study → querydsl → entity → QHello 가 생겨있는 것을 볼 수 있다.
querydsl 폴더가 생긴 것이 중요하다.
엔티티를 정의한 후 빌드를 하면 알아서 Q엔티티를 생성해준다.
참고로 generated 하위 폴더에 있는 Q 파일들은 깃에 올라가면 안된다. 왜냐하면 Q 파일들은 프로젝트를 계속 만들어내는 동안에 바뀔 수 있기 때문이다. 그러므로 깃 이그노어를 해줘야 한다.
다행히 build 폴더 내부 파일들은 자동으로 깃 이그노어가 되어있다.

Querydsl이 잘 작동하는지 검증하도록 하겠다.

📌 QuerydslApplicationTests

package study.querydsl;

@SpringBootTest
@Transactional
class QuerydslApplicationTests {

	@Autowired
	EntityManager em;

	@Test
	void contextLoads() {
		Hello hello = new Hello();
		em.persist(hello);

		// 최신 버전에서는 JPA쿼리 팩토리 사용을 권장한다.
		JPAQueryFactory query = new JPAQueryFactory(em);

		/* Q파일 생성하는 방법 1  */
		// 생성자의 파라미터로 별칭(alias)을 넣는다.
//		QHello qHello = new QHello("h");

		/* Q파일 생성하는 방법 2  */
		QHello qHello = QHello.hello;


		// 엔티티를 사용하지 않고 쿼리와 관련된 것들은 다 Q타입을 넣어야 한다.
		Hello result = query
				.selectFrom(qHello)
				.fetchOne();

		assertThat(result).isEqualTo(hello);
		assertThat(result.getId()).isEqualTo(hello.getId());

	}
}

쿼리와 관련된 것들은 다 Q타입으로 생성해야 한다.
Q class 객체를 생성하는 방법은 2가지가 있다.
- 1. new 연산자로 생성 (파라미터로 별칭을 줘야한다.)
- 2. Q 파일의 static 메서드로 생성하기

Q파일 내부 로직에 이미 생성자를 만들 수 있는 메서드가 존재한다. 그것을 이용하면 된다.

 


 

 

라이브러리 살펴보기

  • 핵심 라이브러리
    • 스프링 MVC
    • JPA, 하이버네이트
    • 스프링 데이터 JPA
    • Querydsl

 

1. querydsl-apt

Q파일 생성 라이브러리

 

2. Querydsl-JPA

쿼리와 연관된 라이브러리

 

3. spring-boot-starter-web

기본적인 웹 프로젝트

톰켓을 내장하고 있고, webmvc를 가지고 있다.

 

4. spring-boot-starter-data-jpa

최신 버전의 하이버네이트를 가지고 있다.
JDBC에서 HikariCP 라이브러리가 있는데 이것은 데이터베이스 커넥션 풀링 라이브러리다.

로깅을 찍는데 sl4f 인터페이스의 구현체로 logback을 사용한다.

 


 

스프링 부트 설정 - JPA, DB

📌 application.yml

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/querydsl
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true

logging.level:
  org.hibernate.SQL: debug

 


 

예제 도메인 모델과 동작 확인

 

1. 도메인 모델

📌 Member 엔티티

package study.querydsl.entity;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = PROTECTED)
@ToString(of = {"id", "username", "age"})
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;

    private int age;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "team_id")
    private Team team;


    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }

    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if (team!=null) {
            changeTeam(team);
        }
    }

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public Member(String username) {
        this.username = username;
    }
}

실무에서는 @Setter를 사용하지 않을 것을 권장한다. 지금은 연습이니 사용했다.
@ToString은 가급적 내부 필드만(연관관계 없는 필드만) 적용한다.

📌 Team 엔티티

package study.querydsl.entity;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {

    @Id @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public Team(String name) {
        this.name = name;
    }
}

 

2.  동작확인

📌 MemberTest

package study.querydsl.entity;

@SpringBootTest
@Transactional
class MemberTest {

    @Autowired
    EntityManager em;

    @Test
    void testEntity() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);

        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);

        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        // 초기화
        em.flush();
        em.clear();

        List<Member> members = em.createQuery("select m from Member m", Member.class)
                .getResultList();

        for (Member member : members) {
            System.out.println("member = " + member);
            System.out.println(" -> member.team = " + member.getTeam());
        }


    }
}
    select
        member0_.member_id as member_i1_1_,
        member0_.age as age2_1_,
        member0_.team_id as team_id4_1_,
        member0_.username as username3_1_ 
    from
        member member0_
    
member = Member(id=3, username=member1, age=10)

    select
        team0_.team_id as team_id1_2_0_,
        team0_.name as name2_2_0_ 
    from
        team team0_ 
    where
        team0_.team_id=?
 -> member.getTeam() = Team(id=1, name=teamA)
 
 // --------------------------------------------------
 
 member = Member(id=4, username=member2, age=20)
 -> member.getTeam() = Team(id=1, name=teamA)
 
  // --------------------------------------------------
 
 member = Member(id=5, username=member3, age=30)
 
     select
        team0_.team_id as team_id1_2_0_,
        team0_.name as name2_2_0_ 
    from
        team team0_ 
    where
        team0_.team_id=?
 -> member.getTeam() = Team(id=2, name=teamB)
 
  // --------------------------------------------------
  
  member = Member(id=6, username=member4, age=40)
 -> member.getTeam() = Team(id=2, name=teamB)

 

 

 


👀 참고 자료

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...

www.inflearn.com

 

728x90