권장되지 않는 방법이다.
- 비동기 식으로 쿼리를 날려 받아오는 식으로 동작하며 이는 백그라운드에 있는 Thread Pool 에 Task 를 맡긴 후 콜백으로 결과 값을 받는 방식이다.
- 테스트 코드 작성이 힘들며 테스트 코드가 지저분해 진다.
- 컴퓨팅 파워가 받쳐주는 현재 시점에서는 DB 튜닝이 어플리케이션의 속도를 판가름한다.
- 쓰레드가 분리되면 다른 스레드에서 조작하는 데이터를 감지하지 못한다.
- 즉 기존의 데이터는 가져올 수 있으나, insert와 select 동작이 언제 완료될지 모르는 상황에서 서로 조작하는 데이터를 감지할 수 없어 문제가 일어나는 것이다.
Async 설정방법
- @Configuration 클래스에 @EnableAsync 어노테이션을 붙인다.
- 백그라운드에서 돌게 되는 Thread Pool 을 위해 Executor 빈을 만든다.
- 프록시를 사용하기 때문에 public 메서드를 선언하며 메서드에 @Async 어노테이션을 붙이고 백그라운드 쓰레드풀의 이름을 속성 값으로 준다.
@SpringBootApplication
@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)
@Import(SoominRegistrar.class)
@EnableAsync
public class DatajpaApplication {
public static void main(String[] args) {
SpringApplication.run(DatajpaApplication.class, args);
}
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
@Async("threadPoolTaskExecutor")
Future<List<Comment>> findByCommentContainsIgnoreCase(String keyword, Pageable pageable);
Future
- Future<T> 는 Java5 : 논블로킹 콜을 할수 있으나 get()을 호출시 블로킹 된다.
- CompletableFuture<T> 는 Java 8
- ListenableFuture 는 Spring 에서 제공 : addCallback() 메서드로 콜백함수를 직접 정의할 수 있다.
@Test
@Rollback(false)
public void testAsyncQuery() throws InterruptedException {
this.createComment("Spring Data JPA", 100);
this.createComment("Hibernate Spring", 55);
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "likeCount"));
ListenableFuture<List<Comment>> future = asyncQueryRepository.findByCommentContainsIgnoreCase("Spring", pageRequest);
System.out.println("==========================");
System.out.println("is Done?"+ future.isDone());
System.out.println("==========================");
future.addCallback(new ListenableFutureCallback<List<Comment>>() {
@Override
public void onFailure(Throwable ex) {
System.out.println(ex);
}
@Override
public void onSuccess(@Nullable List<Comment> result) {
System.out.println("--------------------------------------");
List<Comment> comments = null;
try {
comments = comments = future.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(comments.size());
comments.forEach(System.out::println);
System.out.println("--------------------------------------");
}
});
Thread.sleep(5000);
}
private void createComment(String comment, int likeCount){
Comment newComment = new Comment();
newComment.setComment(comment);
newComment.setLikeCount(likeCount);
asyncQueryRepository.save(newComment);
}
select 쿼리가 먼저 돌아가고 있다..
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
==========================
is Done?false
==========================
Hibernate:
select
comment0_.id as id1_1_,
comment0_.comment as comment2_1_,
comment0_.like_count as like_cou3_1_,
comment0_.post_id as post_id4_1_
from
comment comment0_
where
upper(comment0_.comment) like upper(?) escape ?
order by
comment0_.like_count desc limit ?
2020-11-15 23:44:18.012 TRACE 11368 --- [lTaskExecutor-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [%Spring%]
2020-11-15 23:44:18.013 TRACE 11368 --- [lTaskExecutor-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [CHAR] - [\]
--------------------------------------
0
--------------------------------------
Hibernate:
insert
into
comment
(comment, like_count, post_id, id)
values
(?, ?, ?, ?)
2020-11-15 23:44:22.460 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Spring Data JPA]
2020-11-15 23:44:22.462 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [100]
2020-11-15 23:44:22.465 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2020-11-15 23:44:22.466 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [1]
Hibernate:
insert
into
comment
(comment, like_count, post_id, id)
values
(?, ?, ?, ?)
2020-11-15 23:44:22.471 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Hibernate Spring]
2020-11-15 23:44:22.471 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [55]
2020-11-15 23:44:22.471 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2020-11-15 23:44:22.471 TRACE 11368 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [2]
바로 insert 작업을 flushing 시키려면 JpaRepository 가 제공하는 flush()를 사용해야한다.
- 쿼리 순서는 정확하나 별도 쓰레드에서는 데이터 조작을 감지하지 못하기 때문에 결과 값은 0 이다.
public interface AsyncQueryRepository extends JpaRepository<Comment,Long> {
@Async("threadPoolTaskExecutor")
ListenableFuture<List<Comment>> findByCommentContainsIgnoreCase(String keyword, Pageable pageable);
}
////TEST
@Test
@Rollback(false)
public void testAsyncQuery() throws InterruptedException {
this.createComment("Spring Data JPA", 100);
this.createComment("Hibernate Spring", 55);
asyncQueryRepository.flush();
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "likeCount"));
ListenableFuture<List<Comment>> future = asyncQueryRepository.findByCommentContainsIgnoreCase("Spring", pageRequest);
System.out.println("==========================");
System.out.println("is Done?"+ future.isDone());
System.out.println("==========================");
future.addCallback(new ListenableFutureCallback<List<Comment>>() {
@Override
public void onFailure(Throwable ex) {
System.out.println(ex);
}
@Override
public void onSuccess(@Nullable List<Comment> result) {
System.out.println("--------------------------------------");
List<Comment> comments = null;
try {
comments = comments = future.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(comments.size());
comments.forEach(System.out::println);
System.out.println("--------------------------------------");
}
});
Thread.sleep(5000);
}
private void createComment(String comment, int likeCount){
Comment newComment = new Comment();
newComment.setComment(comment);
newComment.setLikeCount(likeCount);
asyncQueryRepository.save(newComment);
}
insert 후 select 쿼리를 쏘지만 역시 결과 값은 0 이다.
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
comment
(comment, like_count, post_id, id)
values
(?, ?, ?, ?)
2020-11-15 23:50:29.794 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Spring Data JPA]
2020-11-15 23:50:29.795 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [100]
2020-11-15 23:50:29.797 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2020-11-15 23:50:29.798 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [1]
Hibernate:
insert
into
comment
(comment, like_count, post_id, id)
values
(?, ?, ?, ?)
2020-11-15 23:50:29.803 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Hibernate Spring]
2020-11-15 23:50:29.804 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [55]
2020-11-15 23:50:29.805 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2020-11-15 23:50:29.805 TRACE 17164 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [2]
==========================
is Done?false
==========================
Hibernate:
select
comment0_.id as id1_1_,
comment0_.comment as comment2_1_,
comment0_.like_count as like_cou3_1_,
comment0_.post_id as post_id4_1_
from
comment comment0_
where
upper(comment0_.comment) like upper(?) escape ?
order by
comment0_.like_count desc limit ?
2020-11-15 23:50:30.263 TRACE 17164 --- [lTaskExecutor-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [%Spring%]
2020-11-15 23:50:30.263 TRACE 17164 --- [lTaskExecutor-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [CHAR] - [\]
--------------------------------------
0
--------------------------------------
어플리케이션 성능 최적화는 DB 에 달려있기 때문에 이렇게 하나 저렇게하나 결국 DB 관련 문제에 봉착하게 된다.
- 메인쓰레드가 놀지않게 되고, 자원을 효율적으로 관리하는 것은 좋은 방법이다. 그러나 다이나믹한 성능향상을 보여주지는 못한다.
- 어플리케이션 성능에 큰 영향을 주는 것은 DB 쿼리에서 발견되는 것 이기 때문에 성능 최적화는 SQL 질의 횟수를 줄이는 것이고, 필요한 데이터만을 가져오는 것이다.
쓴다면 Spring5에 들어온 WebFlux 기능을 사용해야한다.
- Spring Reactive 의 WebFlux 를 지원하는 JDBC 가 존재하지 않는다.
- MongoDB 같은 NoSQL 은 Reactive 를 지원하기 때문에 비동기 기능을 십분 활용할 수 있게 된다.
NoSQL 도 배우고, Spring Reactive 나중에 함 봐야겠다. 비동적으로 접근하는 걸 보면 너무너무 경탄할 것 같다.
'springframework > Spring Data JPA' 카테고리의 다른 글
Spring Data Common : 공통 리포지토리 커스터마이징 (0) | 2020.11.16 |
---|---|
Spring Data Common : 커스텀 리포지토리 생성 (0) | 2020.11.16 |
Spring Data Common : 쿼리 작성 (0) | 2020.11.15 |
Spring Data Common : 쿼리 작성 키워드 (0) | 2020.11.15 |
Spring Data Common : Null 처리 (0) | 2020.11.15 |