1. 벌크연산
JPA 기본에서 JPQL문의 createQuery 뒤에 .executeUpdate() 구문을 넣어주면 벌크 연산이 가능하다고 배웠었다. 벌크 연산 후 em.flush, em.clear를 실행하거나 spring data jpa에서는 @Modifying을 적용해서 영속성 컨텍스트가 동기화되도록 만들어줘야 한다는 것도 배웠다.
(https://whitepro.tistory.com/439)
querydsl에서는 .execute()를 넣어주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
|
@Test
public void bulkUpdate() {
//28살 이하의 member1, member2의 이름이 "비회원"으로 변경
long count = jpaQueryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
em.flush();
em.clear();
}
|
cs |
2. SQL function 호출
SQL에서 제공하는 function 기능도 사용할 수 있다. 다만, 사용하는 DataBase의 Dialect에서 제공하는 function들만 사용할 수 있다. intelliJ에서 [Shift - Shift] 키를 눌러 전체 검색 - classes 탭 - H2Dialect를 검색해볼 수 있다. 여기서 org.hibernate.dialect에 들어가서, 아래쪽으로 내려보면 많은 function들이 등록되어 있는 것을 확인할 수 있는데, 여기서 등록된 function들만 쓸 수 있다. 아니면 H2Dialect를 상속받아서 직접 function을 만들어주어야 한다.
예제 코드는 다음과 같다. member.username을 조회하고 결과에서 "member" 문자열 부분을 "M"으로 변환하는 replace function을 적용해본다. Expressions.stringTemplate 구문을 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Test
public void sqlFunction() {
List<String> result = jpaQueryFactory
.select(Expressions.stringTemplate(
"function('replace', {0}, {1}, {2})",
member.username, "member", "M"
))
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
|
cs |
3. API 개발
querydsl을 사용해서 API에 적용하는 방법을 배운다. 그런데, 그것보다 부가적인 내용들이 더 중요한 내용인 것 같다.
테스트용 환경 설정 파일 application.yml 구분
application.yml 파일에 "profiles.active :" 부분을 추가로 설정해준다. 로컬 서버 전용은 local 이라고 이름 지어 설정해주자.
1
2
3
4
5
6
7
8
9
10
|
spring:
profiles:
active: local
datasource:
url: jdbc:h2:tcp://localhost/~/querydsl
username: sa
password:
driver-class-name: org.h2.Driver
...
|
cs |
그리고 해당 파일을 복사하고, "active : test" 로 지정하여 test/resources 디렉토리를 새로 생성한 곳에 넣어준다. 이렇게 하면 로컬 서버와 테스트에서의 환경설정을 분리 적용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
|
spring:
profiles:
active: test
datasource:
url: jdbc:h2:tcp://localhost/~/querydsl
username: sa
password:
driver-class-name: org.h2.Driver
...
|
cs |
서버 실행 시 테스트 데이터 삽입
서버가 실행될 때마다 테스트용 데이터가 자동으로 삽입되도록 설정해본다. study/querydsl/controller/InitMember.class를 생성한다.
@Profile
이 어노테이션으로 어떤 환경설정 파일을 적용할지를 설정해줄 수 있다.
@Component 및 @PostConstruct 적용
initMember.class도 @Component 어노테이션을 달아주어서 해당 클래스를 Bean으로 스캔되도록 만들어준다. 그리고 내부에 static class인 InitMemberService를 따로 정의하여 여기서 EntityManager 호출, @Transactional로 데이터 삽입을 해준다. 이런 기능을 @PostConstruct가 적용된 init() 메서드에서 호출한다.
@PostConstruct와 @Transactional 분리 적용
@PostConstruct가 적용된 init() 메서드에서 바로 @Transactional 및 데이터 생성을 하지 않는 이유는 @Transactional 등 AOP는 해당 빈의 후 처리기(post processor)가 완전히 적용되어 스프링 애플리케이션 컨텍스트의 초기화가 완료되어야 적용되기 때문이다.(참조2, 3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@Profile("local")
@Component
@RequiredArgsConstructor
public class InitMember {
private final InitMemberService initMemberService;
@PostConstruct
public void init() {
initMemberService.init();
}
@Component
static class InitMemberService {
@PersistenceContext
private EntityManager em;
@Transactional
public void init() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
IntStream.range(0, 100).boxed()
.forEach(i -> {
Team selectedTeam = i % 2 == 0 ? teamA : teamB;
em.persist(new Member("member" + i, i, selectedTeam));
});
}
}
}
|
cs |
이제 main 메서드를 실행하면 insert 쿼리가 local 환경에서 실행되는 것을 확인할 수 있다.
search API
강의에서는 where 조건절에 검색어를 동적으로 입력할 수 있도록 만들고, request를 보내어 시험해본다. 대부분 아는 내용이므로 기록만 하고 넘어간다.
MemberController.class
1
2
3
4
5
6
7
8
9
10
11
|
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberJpaRepository memberJpaRepository;
@GetMapping("/v1/members")
public List<MemberTeamDto> searchMemberV1(MemberSearchCondition condition) {
return memberJpaRepository.search(condition);
}
}
|
cs |
MemberSearchCondition.class
클라이언트로부터 받아온다고 가정하는 search 조건 파라미터이다.
1
2
3
4
5
6
7
8
9
|
@Data
public class MemberSearchCondition {
private String username;
private String teamName;
private Integer ageGoe;
private Integer ageLoe;
}
|
cs |
MemberTeamDto.class
검색 결과로 나타낼 DTO를 @QueryProjection을 이용하여 실습했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Data
@NoArgsConstructor
public class MemberTeamDto {
private Long memberId;
private String username;
private Integer age;
private Long teamId;
private String teamName;
@QueryProjection
public MemberTeamDto(Long memberId, String username, Integer age, Long teamId, String teamName) {
this.memberId = memberId;
this.username = username;
this.age = age;
this.teamId = teamId;
this.teamName = teamName;
}
}
|
cs |
MemberJpaRepository.class
where 조건절에 검색 조건을 삼항연산자 방식으로 BooleanExpression으로 반환한다. Predicate가 아니라 BooleanExpression으로 사용하면 나중에 BooleanExpression끼리 조합이 가능한 장점이 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public List<MemberTeamDto> search(MemberSearchCondition condition) {
return queryFactory
.select(new QMemberTeamDto(
member.id,
member.username,
member.age,
team.id,
team.name
))
.from(member)
.leftJoin(member.team, team)
.where(usernameEq(condition),
teamNameEq(condition),
ageGoe(condition),
ageLoe(condition))
.fetch();
}
public BooleanExpression usernameEq(MemberSearchCondition condition) {
String username = condition.getUsername();
return username != null ? member.username.eq(username) : null;
}
public BooleanExpression teamNameEq(MemberSearchCondition condition) {
String teamName = condition.getTeamName();
return teamName != null ? team.name.eq(teamName) : null;
}
public BooleanExpression ageGoe(MemberSearchCondition condition) {
Integer ageGoe = condition.getAgeGoe();
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
public BooleanExpression ageLoe(MemberSearchCondition condition) {
Integer ageLoe = condition.getAgeLoe();
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
|
cs |
참조
1. 인프런_실전! Querydsl_김영한 님 강의
https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard
2. inflearn 김영한님 질의 응답
https://www.inflearn.com/questions/26902
3. StackOverflow
https://stackoverflow.com/questions/17346679/transactional-on-postconstruct-method
In the @PostConstruct (as with the afterPropertiesSet from the InitializingBean interface) there is no way to ensure that all the post processing is already done, so (indeed) there can be no Transactions. The only way to ensure that that is working is by using a TransactionTemplate.
'Programming-[Backend] > JPA' 카테고리의 다른 글
[TIL][에러] SQL-Server - hibernate; JPA 적용 Sort, Paging 시 데이터 누락, 중복 문제 (0) | 2021.12.18 |
---|---|
[Querydsl][작성중] 6. 실무 활용 - 사용자 정의 Repository (0) | 2021.12.09 |
[Querydsl] 4. 중급 문법 - Projections, BooleanBuilder (0) | 2021.12.06 |
[Querydsl]3. 기본 문법 - 서브쿼리, Case, 상수 문자 더하기 (0) | 2021.12.05 |
[Querydsl]2. 기본 문법 (0) | 2021.12.03 |