[Querydsl 2편] DTO, Entity, Test 클래스 생성 및 ERD (JPAQueryFactory.java)

반응형
728x90
반응형

실전! Querydsl 강의 정리 2편

 

DTO 클래스 생성

MemberDto.java
package study.querydsl.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class MemberDto {
    private String username;
    private int age;

    public MemberDto() {
    }
}

 

MemberTeamDto.java
package study.querydsl.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Data;

/**
 * MemberTeamDto - 조회 최적화용 DTO 추가
 */
@Data
public class MemberTeamDto {
    private Long memberId;
    private String username;
    private int age;
    private Long teamId;
    private String teamName;
}

 

 

Entity 클래스 생성

Member.java
package study.querydsl.entity;

import lombok.*;
import javax.persistence.*;

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
/* ToString 생성 (메서드를 자동으로 만들어주는데, "team" 은 들어가면 안된다. 무한루프를 타게된다.) */
@ToString(of = {"id", "username", "age"})
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;

    private int age;

    /**
     * N:1 관계
     * 연관관계의 주인 : Member.team
     * Member 테이블에 team_id(FK) 생성
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    /**
     * 생성자 1
     * @param username
     */
    public Member(String username) {
        this(username, 0);
    }

    /**
     * 생성자 2
     * @param username
     * @param age
     */
    public Member(String username, int age) {
        this(username, age, null);
    }

    /**
     * 생성자 3
     * @param username
     * @param age
     * @param team
     */
    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if (team != null) {
            changeTeam(team);
        }
    }

    /**
     * Team 변경
     * @param team
     */
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}

 

Team.java
package study.querydsl.entity;

import lombok.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@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;

    /**
     * team.members 는 연관관계 주인이 아니다.
     */
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    /**
     * 생성자
     * @param name
     */
    public Team(String name) {
        this.name = name;
    }
}

 

▶ 추가 완료

 

▶ ERD

 

▶ 테이블 생성 쿼리

create table member (
    member_id bigint not null,
    age integer not null,
    username varchar(255),
    team_id bigint,
    primary key (member_id)
)
    
create table team (
    team_id bigint not null,
    name varchar(255),
    primary key (team_id)
)

alter table member 
add constraint FKcjte2jn9pvo9ud2hyfgwcja0k 
foreign key (team_id) 
references team

 

 

Test 클래스 생성

QuerydslTest.java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {
    @PersistenceContext
    EntityManager em;

    JPAQueryFactory queryFactory;
    
    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
    }
}

1) Test 클래스에서의 @Transactional

테스트 메서드를 수행 후, 관련 데이터들을 모두 rollback 수행한다.

@Transactional
public class QuerydslBasicTest {

 

2) JPAQueryFactory 생성자를 호출하여 셋팅한다.

public JPAQueryFactory(final EntityManager entityManager) {
    this.entityManager = () -> entityManager;
    this.templates = null;
}

 

3) @PersistenceContext

JPA 스펙에서 제공하는 기능으로, 영속성 컨텍스트를 주입하는 표준 애노테이션이다.

만약 위 어노테이션을 사용하지 않으면 아래와 같이 수동으로 등록해줘야한다.

https://devfunny.tistory.com/455?category=876301 

 

JPA 처음 적용해보기 (with persistence.xml, EntityManagerFactory)

JPA 처음 적용해보기 Spring, SpringBoot 프레임워크가 아닌 기본 Java 프로젝트의 main 함수로 JPA를 구현해보자. resources/META-INF/persistence.xml <?xml version="1.0" encoding="UTF-8"?> Member Entity pa..

devfunny.tistory.com

EntityManagerFactory emf = Persistence.createEntityManagerFactory("emf");

...

public void test() {
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    
    tx.begin();
    
    ...
    
    tx.commit();
}

1) EntityManagerFactory

JEntityManagerFactory를 어플리케이션이 로딩되는 시점에 생성해야한다.

EntityManager 인스턴스를 관리하는 것으로, 생성시 커넥션 풀을 함께 생성한다.

 

2) EntityManager

엔티티를 관리하는 역할을 수행하는 클래스이다. 

클라이언트의 요청이 올때 마다 (즉, thread가 하나씩 생성될 때마다) EntityManager를 생성한다.

클라이언트의 요청이 들어올 때 생성했다가 요청이 끝나면 닫는다.

 

 

 

JPAQueryFactory

QueryDSL을 사용하여 쿼리를 Build 하기 위해서는 JPAQueryFactory가 필요하다.

JPQLQuery 인터페이스가 queryDSL 동적 쿼리 생성의 기준이 되는 인터페이스이고, JPAQuery는 JPQLQuery를 구현한 클래스이다.

 

JPAQueryFactory.java
package com.querydsl.jpa.impl;

import org.jetbrains.annotations.Nullable;
import javax.persistence.EntityManager;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.JPQLQueryFactory;
import com.querydsl.jpa.JPQLTemplates;

import java.util.function.Supplier;

/**
 * Factory class for query and DML clause creation
 *
 * @author tiwe
 *
 */
public class JPAQueryFactory implements JPQLQueryFactory  {

    @Nullable
    private final JPQLTemplates templates;

    private final Supplier<EntityManager> entityManager;

    public JPAQueryFactory(final EntityManager entityManager) {
        this.entityManager = () -> entityManager;
        this.templates = null;
    }

    public JPAQueryFactory(JPQLTemplates templates, final EntityManager entityManager) {
        this.entityManager = () -> entityManager;
        this.templates = templates;
    }

    public JPAQueryFactory(Supplier<EntityManager> entityManager) {
        this.entityManager = entityManager;
        this.templates = null;
    }

    public JPAQueryFactory(JPQLTemplates templates, Supplier<EntityManager> entityManager) {
        this.entityManager = entityManager;
        this.templates = templates;
    }

    ...

}

 

JPQLQueryFactory.java
public interface JPQLQueryFactory extends QueryFactory<JPQLQuery<?>> {

    /**
     * Create a new DELETE clause
     *
     * @param path entity to delete from
     * @return delete clause
     */
    DeleteClause<?> delete(EntityPath<?> path);
    
    ...
}

▶ JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까?

  • 동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다.
  • 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제에 안전하다.

 

@PersistenceContext로 주입받은 EntityManager

  • 스프링 컨테이너가 초기화되면서 @PersistenceContext으로 주입받은 EntityManager를 Proxy로 감싼다.
  • EntityManager 호출 시 마다 위의 Proxy를 통해 EntityManager를 생성하여 스레드 안전성을 보장한다.

 

JPAQueryFactory 는 Spring Bean으로 등록하여 사용 가능하다.

@Configuration
public class QuerydslConfig {

    @PersistenceContext
    private EntityManager em;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(em);
    }
}

 

 

Test 데이터 추가

@SpringBootTest
@Transactional
public class QuerydslBasicTest {
    @PersistenceContext
    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);
    }
    
    ...
}
INSERT TEST DATA
member1 teamA
member2 teamA
member3 teamB
member4 teamB

 

 

application.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb # http://localhost:8080/h2-console
    username: sa
    password:
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create  # table drop -> create
    properties:
      hibernate:
        # show_sql: true # system.out.println()
        format_sql: true # query pretty
        use_sql_comments: true # execute Jpql show
  profiles:
    active: local
logging.level:
  org.hibernate.SQL: debug # hibernate execute query log
# org.hibernate.type: trace # binding parameter log

 

 

Querydsl 메서드 정리 포스팅 바로가기

준비중

 

 

 

 

Referece.

https://jaeho214.tistory.com/73

https://batory.tistory.com/497

 

 

반응형

Designed by JB FACTORY