Querydsl - (1)

bp.chys·2020년 6월 14일
0

JPA

목록 보기
11/15

김영한님의 실전! Querydsl 강의내용을 정리한 글입니다.

querydsl 설정

의존성 추가

plugins {
    id 'org.springframework.boot' version ‘2.2.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE' //querydsl 추가
++  id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
    id 'java'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'           implementation 'org.springframework.boot:spring-boot-starter-web'
++  implementation 'com.querydsl:querydsl-jpa'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'                    
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: ‘org.junit.vintage’, module: ‘junit-vintage-engine' }
}

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

Entity 생성 및 Q타입 생성

@Entity
@Getter @Setter
pulic class Hello {
    @Id
    @GeneratedValue
    private Long id;
}
  • intelliJ 사용 시
    : Gradle Tasks other compileQuerydsl
  • console 사용 시
    : ./gradlew clean compileQuerydsl

Q타입은 컴파일 시점에 자동 생성되므로 .gitignore에 포함하는 것이 좋다. 앞서 설정에서 생성 위치를 gradle builde 폴더 아래 생성했으므로 자동으로 ignore된다.

테스트

@SpringBootTest
@Transactional
class QuerydslApplicationTests {
  
    @Autowired
    EntityManager em;
    
    @Test
    void contextLoads() {
        Hello hello = new Hello(); 
        em.persist(hello);
        
        JPAQueryFactory query = new JPAQueryFactory(em); 
        QHello qHello = QHello.hello; //Querydsl Q타입 동작 확인

        Hello result = query 
                .selectFrom(qHello)
                .fetchOne(); 
                
        Assertions.assertThat(result).isEqualTo(hello);
        //lombok 동작 확인 (hello.getId())
        Assertions.assertThat(result.getId()).isEqualTo(hello.getId()); 
    }
}

스프링 부트에서 아무런 설정을 하지않으면 h2 DB를 메모리 모드로 JVM안에서 실행한다.

예제 도메인

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.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 = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
    
    public Member(String username) {
        this(username, 0);
    }
    
    public Member(String username, int age) {
        this(username, age, null);
    }
    
    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if(team != null) {
            changeTeam(team);
        }
    }
}
@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")
    List<Member> members = new ArrayList<>();
    
    public Team(String name) {
        this.name = name;
    }

테스트

@SpringBootTest
@Transactional
@Commit
public class MemberTest {
    
    @Autowired
    EntityManager em;
    
    JPAQueryFactory queryFactory;
    
    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
        
        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);
    }
    
    @Test
    public void startQuerydsl() {
        QMember m = new QMember("m");
        
        Member findMember = queryFactory
                .select(m)
                .from(m)
                .where(m.username.eq("member1"))
                .fetchOne();
                
        assertThat(findMember.getUsername()).isEqualTo("member1");
    }
}
  • @Transactional 어노테이션을 붙이면 영속성 컨텍스트를 사용할 수 있고, @Commit을 하면 마지막에 롤백하지않고 데이터를 계속 쌓을 수 있다.
  • EntityManager는 @Autowired로 스프링이 자동으로 주입해준다.
  • EntityManager를 통해 JPAQueryFactory를 생성한다.
  • querydsl은 java코드이기 때문에 컴파일 시점에 오류를 발견할 수 있다.
  • JPAQueryFactory는 필드로 제공해도된다.

JPAQueryFactory를 필드로 제공하면 동시성 문제?

동시성 문제는 EntityManager에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager를 접근해도 트랜잭션마다 별도의 영속성 컨텍스트를 제공하기 때문에 동시성 문제는 걱정하지 않아도 된다.

기본 Q-type의 활용

QMember qMember = new QMember("m"); // 별칭 직접 사용
QMember qMember = QMember.member;  // 기본 인스턴스 사용
  • 기본 인스턴스 사용을 static import와 사용하면 다음과 같이 간결하게 사용할 수 있다.
    @Test
    public void startQuerydsl2() {
        Member findMember = queryFactory
                .select(member)
                .from(member)
                .where(member.username.eq("member1"))
                .fetchOne();
                
        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

같은 테이블을 조인하는 경우가 아니면 기본 인스턴스를 사용하자.

profile
하루에 한걸음씩, 꾸준히

0개의 댓글