실전! 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
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
'Coding > JPA' 카테고리의 다른 글
[Querydsl 3편] Querydsl 메서드 정리 (1) | 2022.08.12 |
---|---|
[Querydsl 1편] SpringBoot2.7 + Querydsl5.0 설정하기 (Entity, Dto class to QClass) (0) | 2022.08.11 |
[JPA 프로그래밍] 5. 다양한 연관관계 매핑 (0) | 2022.06.19 |
[JPA 프로그래밍] 4. 연관관계 매핑 (0) | 2022.06.18 |
[JPA 프로그래밍] 3. 엔티티 매핑 (0) | 2022.06.18 |