DocFlavor flavor = DocFlavor.INPUT_STREAM.PDF;
FileInputStream fis = null;
try {
	fis = new FileInputStream("sample.pdf");
} catch (FileNotFoundException e) {
	e.printStackTrace();
}
	Doc doc = new SimpleDoc(fis, flavor, null);
	pdfService.createPdfLabel();
try {
	docPrintJob.print(doc, printRequestAttributeSet);
} catch (PrintException e) {
	e.printStackTrace();
}

생산 현장 프로젝트 중에 제품 생산시 바코드를 프린터로 출력해야하는 시나리오를 구현하기 위해 javax.print의 API를 개발했는데 아래와 같은 에러가 발생 하였다.

javax.print.PrintException: already printing
	at java.desktop/sun.print.UnixPrintJob.print(UnixPrintJob.java:317)
	at com.dhptec.waco.service.impl.PrintServiceImpl.printLabel(PrintServiceImpl.java:122)
	at com.dhptec.waco.controller.PrintController.print(PrintController.java:34)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

정확하게는 처음 시작하고 한 번은 잘 동작하고 두 번째부터 위와 같은 에러가 발생한다. 그런데 구글링을 해도 딱히 정확한게 없어서 API부터 다시 봤다.

javax.print.DocPrintJob public abstract void print(javax.print.Doc doc,
                           javax.print.attribute.PrintRequestAttributeSet attributes)
throws javax.print.PrintException
Prints a document with the specified job attributes. This method should only be called once for a given print job. Calling it again will not result in a new job being spooled to the printer. The service implementation will define policy for service interruption and recovery. When the print method returns, printing may not yet have completed as printing may happen asynchronously, perhaps in a different thread. Application clients which want to monitor the success or failure should register a PrintJobListener.
Print service implementors should close any print data streams (ie Reader or InputStream implementations) that they obtain from the client doc. Robust clients may still wish to verify this. An exception is always generated if a DocFlavor cannot be printed.

Params:
doc – the document to be printed. It must be a flavor supported by this PrintJob.
attributes – the job attributes to be applied to this print job. If this parameter is null then the default attributes are used.
Throws:
javax.print.PrintException – the exception additionally may implement an interface that more precisely describes the cause of the exception
FlavorException. If the document has a flavor not supported by this print job.
AttributeException. If one or more of the attributes are not valid for this print job.

내용인 즉, 이 메서드는 주어진 인쇄작업에 대해 한 번만 호출해야합니다. 다시 호출해도 새 작업이 프린터로 스풀링되지 않습니다. 그렇다면 DocPrintJob을 프린트 할 때마다 새로 생성해야된다는 뜻으로 풀이되어 아래 코드로 변경했다.

@Configuration
public class PrintConfig {

    @Autowired
    private LabelPrintJobAdapter labelPrintJobAdapter;

    @Bean
    public PrintService defaultPrintService(){
        return PrintServiceLookup.lookupDefaultPrintService();
    }

    @Bean
    @RequestScope // 추가한 내용
    public DocPrintJob docPrintJob() {
        PrintService defaultPrintService = defaultPrintService();
        DocPrintJob printJob = defaultPrintService.createPrintJob();
        printJob.addPrintJobListener(labelPrintJobAdapter);
        return printJob;
    }
}

PrintConfig.java에서 DocPrintJob을 singleton으로 주입받았는데 request scope으로 바꾸니 문제가 해결되었다.

역시 구글링이 대부분의 문제들은 해결되지만 안되면 처음부터 차근차근 해법을 찾아가는 것도 중요하다.

npx로 typescript 프로젝트를 만들었을때 vscode에서 위와 같은 에러가 발생했다.

구글링 중에 tsconfig.json의 내용을 바꾸라는 내용이 다수였는데 이 방법 보다 더 정확해 보이는 해결책을 찾았다.

stackoverflow.com/questions/50432556/cannot-use-jsx-unless-the-jsx-flag-is-provided

 

Cannot use JSX unless the '--jsx' flag is provided

I have looked around a bit for a solution to this problem. All of them suggest adding "jsx": "react" to your tsconfig.json file. Which I have done. Another one was to add "include: []", which I hav...

stackoverflow.com

cmd + shift + p -> select typescript version -> Use Workspace Version 4.x.x 로 변경하니 error가 사라졌다.

이걸 설정하기 전 typescript버전은 3.x.x였는데 4.x.x로 변경했더니 문제가 사라졌다.

'Programming > JavaScript' 카테고리의 다른 글

event.stopPropagation(), event.preventDefault () 이해하기  (0) 2014.10.20
[JAVASCRIPT] replace  (0) 2013.05.08

https://blog.cloudflare.com/http3-the-past-present-and-future/

 

HTTP/3: the past, the present, and the future

We are now happy to announce that QUIC and HTTP/3 support is available on the Cloudflare edge network. We’re excited to be joined in this announcement by Google Chrome and Mozilla Firefox, two of the leading browser vendors and partners in our effort to

blog.cloudflare.com

이제 TCP가 아닌 UDP로.... 벌써 크롬은 카나리 빌드도 사용 가능하다고...

 

이 내용을 잘 정리한 포스트 udp설명도 곁들여 있다.

https://evan-moon.github.io/2019/10/08/what-is-http3/

 

HTTP/3는 왜 UDP를 선택한 것일까?

는 의 세 번째 메이저 버전으로, 기존의 HTTP/1, HTTP/2와는 다르게 UDP 기반의 프로토콜인 을 사용하여 통신하는 프로토콜이다. HTTP/3와 기존 HTTP 들과 가장 큰 차이점이라면 TCP가 아닌 UDP 기반의 통��

evan-moon.github.io

 

'Programming > Browser' 카테고리의 다른 글

크롬 디버깅하기  (0) 2013.05.08

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

 

Primitive Data Types (The Java™ Tutorials > Learning the Java Language > Language Basics)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See JDK Release Notes for information about new fe

docs.oracle.com

먼저 java primitive type에 대한 oracle document를 찾아보았다.

Literal에 대한 소개와 Interger, Floating-point, Character & Stirng으로 나누어 설명하고 있다. 그리고 JAVA7부터 추가된 숫자 사이 "_"(underscore)의 사용법에 대해서도 언급하고 있다. 단순 가독성을 위한 것으로 숫자와 숫자 사이에만 올 수 있다.

이 글에서 언급하고자 하는 내용인 돈 계산에 쓰이면 안 되는 type에 대한 내용은 처음 부분에 명확히 나와 있다.

  • byte : byte 데이터 타입은 8 비트 부호 있는 2의 보수 정수입니다. 최소값은 -128이고 최대 값은 127입니다 (포함). byte 타입은 메모리 절약이 실제로 중요한 큰 배열로 메모리를 저장하는 데 유용 할 수 있습니다. 또한 그들의 한계가 코드를 명확하게하는 데 도움이 되는 int 대신 사용될 수도 있습니다. 변수의 범위가 제한되어 있다는 사실은 문서 형식으로 제공할 수 있습니다.
  • short : short 데이터 타입은 16비트 부호 있는 2의 보수 정수입니다. 최소값은 -32,768이고 최대 값은 32,767입니다 (포함). byte와 마찬가지로 동일한 지침이 적용됩니다. 실제로 메모리 절약이 중요한 상황에서 short를 사용하여 큰 배열로 메모리를 저장할 수 있습니다.
  • int : 기본적으로 int 데이터 타입은 32 비트 부호 있는 2의 보수 정수이며, 최소값은 -231 이고 최대 값은 231-1입니다. Java SE 8 이상에서 int를 사용하여 최소값 0과 최대값 232-1의 부호 없는 32 비트 정수를 나타낼 수 있습니다. int 타입을 부호없는 정수로 사용하려면 Integer 클래스를 사용하십시오. 자세한 내용은 숫자 클래스 섹션을 참조하십시오. compareUnsigned, divideUnsigned 같은 정적 메소드등이 첨가된 Integer는 부호 없는 정수에 대한 산술 연산을 지원하는 클래스입니다.
  • long : long 데이터 타입은 64비트 2의 보수 정수입니다. signed long의 최소값은 -263 이고 최대 값은 263-1입니다. Java SE 8 이상에서 long 데이터 타입을 사용하여 최소값 0과 최대값 264-1의 부호 없는 64 비트 길이를 나타낼 수 있습니다. Long 클래스에는 unsigned long에 대한 산술 연산을 지원하기 위해 compareUnsigned, divideUnsigned 등과 같은 메소드가 포함되어 있습니다.
  • float : float 데이터 형식은 single-precision 32-bit IEEE 754 부동 소수점입니다. 값 범위는 이 설명의 범위를 벗어나지만 Java 언어 사양의 부동 소수점 타입, 형식 및 값 섹션에 지정되어 있습니다. byte와 short의 권장 사항과 마찬가지로 메모리를 큰 부동 소수점 배열로 저장해야하는 경우 double대신 float을 사용합니다. 이 데이터 타입은 통화와 같은 정확한 값에 사용해서는 안됩니다. 이를 위해서는 대신 java.math.BigDecimal 클래스를 사용해야 합니다. 숫자 및 문자열 커버 및 Java 플랫폼에서 제공하는 기타 유용한 클래스. floatdoubleBigDecimal

  • double : double 데이터 타입은 double-precision 64-bit IEEE 754 부동 소수점입니다. 값 범위는 이 설명의 범위를 벗어나지 만 Java 언어 사양의 부동 소수점 타입, 형식 및 값 섹션에 지정되어 있습니다. 10 진수 값의 경우가 데이터 타입이 일반적으로 기본 선택입니다. 위에서 언급했듯이 double 타입은 통화와 같은 정확한 값에 사용해서는 안됩니다.

  • boolean : boolean 데이터 타입에는 두 가지 가능한 값만 있습니다 : true 및 false. 참 / 거짓 조건을 추적하는 간단한 플래그 데이터 타입을 사용하십시오. 이 데이터 타입은 1 bit의 정보를 나타내지만 "크기"는 정확하게 정의된 것이 아닙니다.
  • char : char 데이터 타입은 단일 16-bit 유니 코드 문자입니다. 최소값 '\u0000'(또는 0)과 최대 값 '\uffff'(또는 65,535 포함)을 갖습니다.

float와 double에 대한 설명에는 "통화와 같은 정확한 값에 사용해서는 안됩니다."(This data type should never be used for precise values, such as currency.)라고 언급되어 있다. 또한 BigDecimal을 사용하라고 대안까지 친절하게 제시하고 있다.

Floating-point type의 문제

floating-point type의 연산 문제는 아래와 같이 99%의 정확도가 문제다. 큰 숫자를 다룰 수 있지만 연산을 하면 근사값이 계산되기 때문이다.

double d1 = 3;
double d2 = 0.01;
System.out.println(d1 + d2);    // 3.8099999999999996


double d3 = 2.4;
double d4 = 0.8;
System.out.println(d3 / d4);    // 2.9999999999999996

결론은 통화와 같은 정확한 계산을 위해선 BigDecimal을 사용해야 한다.

'Programming > Java' 카테고리의 다른 글

java print api 주의 사항  (0) 2021.01.15
Lombok @Builder에서 method name 이용 패턴  (0) 2020.05.21
JVM Garbage Collection Basic  (0) 2019.05.10
Non-blocking, Blocking  (0) 2019.03.18
Asynchronous (VS Synchronous)  (0) 2019.03.18

 

  • 먼저 생각해 볼 수 있는 방법은 @EntityGraph로 한 번에 다 가져올 수 있습니다.
  • join fetch을 사용하여 한 번에 쿼리 할 수 있습니다.
  • hibernate의 default batch를 사용하는 방법이 있습니다.

3. Hibernate의 default batch를 사용하는 방법

spring boot를 사용하여 application.yml에 다음 항목을 설정하면 됩니다.

spring:

  jpa:

    properties:

      hibernate.default_batch_fetch_size: 1000

===== LAZY
Hibernate: 
    select
        post0_.id as id1_1_,
        post0_.title as title2_1_ 
    from
        post post0_
2020-06-15 15:32:53.655 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_1_] : [BIGINT]) - [1]
2020-06-15 15:32:53.659 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_1_] : [VARCHAR]) - [첫 포스트]
2020-06-15 15:32:53.660 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.660 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_1_] : [VARCHAR]) - [내가 2등~]
2020-06-15 15:32:53.667 TRACE 9690 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#1]
2020-06-15 15:32:53.667 TRACE 9690 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#4]
Hibernate: 
    select
        comments0_.post_id as post_id3_0_1_,
        comments0_.id as id1_0_1_,
        comments0_.id as id1_0_0_,
        comments0_.comment as comment2_0_0_,
        comments0_.post_id as post_id3_0_0_ 
    from
        comment comments0_ 
    where
        comments0_.post_id in (
            ?, ?
        )
2020-06-15 15:32:53.672 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2020-06-15 15:32:53.673 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2020-06-15 15:32:53.676 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [2]
2020-06-15 15:32:53.677 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-15 15:32:53.677 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [1]
2020-06-15 15:32:53.678 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [1]
2020-06-15 15:32:53.678 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [2]
2020-06-15 15:32:53.683 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [3]
2020-06-15 15:32:53.683 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [두번째야~]
2020-06-15 15:32:53.684 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [1]
2020-06-15 15:32:53.684 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [1]
2020-06-15 15:32:53.685 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [3]
2020-06-15 15:32:53.685 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [5]
2020-06-15 15:32:53.685 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [좋아요~]
2020-06-15 15:32:53.686 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [4]
2020-06-15 15:32:53.686 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.686 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [5]
2020-06-15 15:32:53.687 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [6]
2020-06-15 15:32:53.688 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [감사합니다.]
2020-06-15 15:32:53.688 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [4]
2020-06-15 15:32:53.689 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.694 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [6]
2020-06-15 15:32:53.695 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [7]
2020-06-15 15:32:53.696 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-15 15:32:53.696 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [4]
2020-06-15 15:32:53.696 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.697 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [7]
LAZY : [[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)]]

프록시를 통해서 연관된 entity의 실제 값을 가져오는 DB 쿼리를 in 절로 수정되어서 한 번에 쿼리가 됩니다.

그리고 @BatchSize 어노테이션을 class나 method, member variable에 각각 적용도 가능합니다. (실제 테스트 결과 @OneToMany에 적용했을 때만 동작했습니다. Entity class에 @BatchSize를 적용하는 방법은 좀 더 확인이 필요한 것 같습니다.)

@Entity
@BatchSize(size=100)
class Product {
    @OneToMany
    @BatchSize(size = 10) /
    Set<Product> getProducts() { ... };
}

 

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 2  (0) 2020.06.11
N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
  • 먼저 생각해 볼 수 있는 방법은 @EntityGraph로 한 번에 다 가져올 수 있습니다.
  • join fetch을 사용하여 한 번에 쿼리할 수 있습니다.
  • hibernate의 default fetch를 사용하는 방법이 있습니다.

앞서 살펴본 EntityGraph에 이어 join fetch을 살펴 보겠습니다.

2. Join Fetch를 사용하는 방법

먼저 살펴본 방법(2020/06/11 - [Programming/JPA] - N + 1 문제 해결 1)은 JpaRepository에서 원하는 쿼리로 바뀌도록 @EntityGraph를 이용하여 가이드 했다면 이번 방법은 JPQL을 사용하여 직접적으로 해결하는 방법입니다.

public interface PostRepository extends JpaRepository<Post, Long> {

    @Query("select p from Post p join fetch p.comments")
    List<Post> findAllWithComments();

}

해결편1에서 사용한 소스에 PostRepository만 바뀌었습니다. "join fetch p.comments"부분이 추가 되었습니다.

@SpringBootTest
class PostRepositoryTest {

    @Autowired
    private PostService postService;
    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    public void setup() {
        Post post = new Post();
        post.setTitle("첫 포스트");

        Comment comment1 = new Comment();
        comment1.setComment("첫 댓글~! ");
        comment1.setPost(post);
        post.getComments().add(comment1);

        Comment comment2 = new Comment();
        comment2.setComment("두번째야~");
        post.getComments().add(comment2);
        comment2.setPost(post);

        Post post2 = new Post();
        post2.setTitle("내가 2등~");

        Comment comment3 = new Comment();
        comment3.setComment("좋아요~");
        comment3.setPost(post2);
        post2.getComments().add(comment3);

        Comment comment4 = new Comment();
        comment4.setComment("감사합니다.");
        comment4.setPost(post2);
        post2.getComments().add(comment4);

        Comment comment5 = new Comment();
        comment5.setComment("다음글 기대할께요.");
        comment5.setPost(post2);
        post2.getComments().add(comment5);

        postRepository.save(post);
        postRepository.save(post2);
    }

    @Test
    void testNPlusOne() {
        // lazy loading
        System.out.println("===== LAZY");
        System.out.println("LAZY : " + postService.findAllComments());

        // Join fetch
        System.out.println("===== JoinFetch");
        System.out.println("JoinFetch : " + postService.findAllWithComments());
    }
}

결과는 아래와 같이 inner join으로 실행되었죠...

===== JoinFetch
Hibernate: 
    select
        post0_.id as id1_3_0_,
        comments1_.id as id1_2_1_,
        post0_.title as title2_3_0_,
        comments1_.comment as comment2_2_1_,
        comments1_.post_id as post_id3_2_1_,
        comments1_.post_id as post_id3_2_0__,
        comments1_.id as id1_2_0__ 
    from
        post post0_ 
    inner join
        comment comments1_ 
            on post0_.id=comments1_.post_id
2020-06-11 17:58:26.842 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [1]
2020-06-11 17:58:26.843 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [2]
2020-06-11 17:58:26.843 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_0_] : [VARCHAR]) - [첫 포스트]
2020-06-11 17:58:26.844 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-11 17:58:26.844 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 17:58:26.844 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [1]
2020-06-11 17:58:26.845 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [2]
2020-06-11 17:58:26.845 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [1]
2020-06-11 17:58:26.846 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [3]
2020-06-11 17:58:26.846 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [두번째야~]
2020-06-11 17:58:26.846 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 17:58:26.847 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [1]
2020-06-11 17:58:26.849 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [3]
2020-06-11 17:58:26.850 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 17:58:26.850 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [5]
2020-06-11 17:58:26.854 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_0_] : [VARCHAR]) - [내가 2등~]
2020-06-11 17:58:26.855 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [좋아요~]
2020-06-11 17:58:26.855 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [5]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [6]
2020-06-11 17:58:26.858 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [감사합니다.]
2020-06-11 17:58:26.858 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 17:58:26.859 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 17:58:26.859 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [6]
2020-06-11 17:58:26.861 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 17:58:26.862 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [7]
2020-06-11 17:58:26.863 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-11 17:58:26.863 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 17:58:26.863 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 17:58:26.864 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [7]
JoinFetch : [[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)]]

@EntityGraph를 사용한 1편에서는 left outer join으로 실행 되었던 것을 기억하시나요?

그렇습니다. 이 두 방법은 실행 방식이 다릅니다.

  • @EntityGraph : left outer join
  • join fetch : inner join

그런데 두 방법 모두 comments가 중복되어서 출력될 수 있습니다. 카테시안 곱이 되기 때문입니다.

Cartesian Product 해결

코너속의 코너

먼저 두 가지 방법이 있습니다.

  1. collection을 List가 아닌 Set으로 바꾸는 방법입니다.
    private Set<Comment> comments = new LinkedHashSet<>();
    (참고로 HashSet으로 하면 중복 제거는 되는데 순서가 보장이 안됩니다.)
  2. @Query에서 distinct를 사용하는 방법입니다.
    @Query("select distinct p from Post p join fetch p.comments")
    List<Post> findAllWithComments();

개인적으로는 List를 사용하는 것이 더 좋아 보입니다. 왜냐하면 JpaRepository의 interface들은 다 List이기 때문입니다. 하지만 상황에 따라 Set을 쓰지 않는다는 보장은 없으니 여러모로 알고 있는 것이 이로워 보입니다. 그리고 항상 느끼는 것이지만 발생된 query와 결과는 꼭 확인이 필요합니다.

 

github source : https://github.com/aloftcat/blog-code/tree/master/jpa/NPlusOne

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 3  (0) 2020.06.15
N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04

앞서 살펴본 N + 1 문제 원인은 @oneToMany 관계로 reference 하고 있는 collection 내용을 가져오려고 접근할 때 생깁니다. 변수에 접근할 때마다 그때그때 하나씩 쿼리가 날아가는 문제입니다.

그럼 이 문제를 해결하기 위해 어떻게 하는 것이 좋을까요?

  • 먼저 생각해 볼 수 있는 방법은 @EntityGraph로 한 번에 다 가져올 수 있습니다.
  • join fetch을 사용하여 한 번에 쿼리할 수 있습니다.
  • hibernate의 default fetch를 사용하는 방법이 있습니다.

1. @EntityGraph 사용하는 방법

그냥 @oneToMany를 똭 eager로 바꿔 버리면 다시는 lazy로 가져올 수는 없겠죠?^^; 그래서 필요할 때만 eager로 가져오고 평소에는 lazy로 가져오는 방법이 있습니다. 바로 EntityGraph입니다.

@Getter@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@ToString
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    private List<Comment> comments = new ArrayList<>();

}

@Setter@Getter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@ToString(exclude = "post")
public class Comment {

    @Id
    @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    private Post post;
}

public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(attributePaths = {"comments"})
    List<Post> findAllWithComments();
}

기존 소스에 findAllWithComments()를 추가하고 @EntityGraph와 가져올 대상(collection 변수명)을 지정했습니다.

public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(attributePaths = {"comments"})
    @Query("select p from Post p")
    List<Post> findAllWithComments();
}

@Service
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Transactional(readOnly = true)
    public List<String> findAllComments() {
        return getAllComments(postRepository.findAll());
    }

    @Transactional(readOnly = true)
    public List<String> findAllWithComments() {
        return getAllComments(postRepository.findAllWithComments());
    }

    /**
     * Lazy Load를 실행하기 위해 모든 comment 순회
     */
    private List<String> getAllComments(List<Post> posts) {
        return posts.stream()
                .map(a -> a.getComments().toString())
                .collect(Collectors.toList());
    }
}

@SpringBootTest
class PostRepositoryTest {

    @Autowired
    private PostService postService;
    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    public void setup() {
        Post post = new Post();
        post.setTitle("첫 포스트");

        Comment comment1 = new Comment();
        comment1.setComment("첫 댓글~! ");
        comment1.setPost(post);
        post.getComments().add(comment1);

        Comment comment2 = new Comment();
        comment2.setComment("두번째야~");
        post.getComments().add(comment2);
        comment2.setPost(post);

        Post post2 = new Post();
        post2.setTitle("내가 2등~");

        Comment comment3 = new Comment();
        comment3.setComment("좋아요~");
        comment3.setPost(post2);
        post2.getComments().add(comment3);

        Comment comment4 = new Comment();
        comment4.setComment("감사합니다.");
        comment4.setPost(post2);
        post2.getComments().add(comment4);

        Comment comment5 = new Comment();
        comment5.setComment("다음글 기대할께요.");
        comment5.setPost(post2);
        post2.getComments().add(comment5);

        postRepository.save(post);
        postRepository.save(post2);
    }

    @Test
    void testNPlusOne() {
        // lazy loading
        System.out.println("===== LAZY");
        System.out.println("LAZY : " + postService.findAllComments());

        // EntityGraph
        System.out.println("===== EntityGraph");
        System.out.println("EntityGraph : " + postService.findAllWithComments());
    }
}

결과는 left outer join으로 실행되었습니다. 이 부분은 다음 편에서 비교할 부분이라 체크해 두세요.

===== LAZY
Hibernate: 
    select
        post0_.id as id1_3_,
        post0_.title as title2_3_ 
    from
        post post0_
2020-06-11 02:01:04.011 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_] : [BIGINT]) - [1]
2020-06-11 02:01:04.015 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_] : [VARCHAR]) - [첫 포스트]
2020-06-11 02:01:04.015 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_] : [BIGINT]) - [4]
2020-06-11 02:01:04.016 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_] : [VARCHAR]) - [내가 2등~]
2020-06-11 02:01:04.021 TRACE 31191 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#1]
2020-06-11 02:01:04.022 TRACE 31191 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#4]
Hibernate: 
    select
        comments0_.post_id as post_id3_2_0_,
        comments0_.id as id1_2_0_,
        comments0_.id as id1_2_1_,
        comments0_.comment as comment2_2_1_,
        comments0_.post_id as post_id3_2_1_ 
    from
        comment comments0_ 
    where
        comments0_.post_id=?
2020-06-11 02:01:04.030 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-06-11 02:01:04.038 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [2]
2020-06-11 02:01:04.038 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-11 02:01:04.039 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 02:01:04.040 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [1]
2020-06-11 02:01:04.040 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [2]
2020-06-11 02:01:04.046 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [3]
2020-06-11 02:01:04.046 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [두번째야~]
2020-06-11 02:01:04.047 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 02:01:04.047 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [1]
2020-06-11 02:01:04.047 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [3]
Hibernate: 
    select
        comments0_.post_id as post_id3_2_0_,
        comments0_.id as id1_2_0_,
        comments0_.id as id1_2_1_,
        comments0_.comment as comment2_2_1_,
        comments0_.post_id as post_id3_2_1_ 
    from
        comment comments0_ 
    where
        comments0_.post_id=?
2020-06-11 02:01:04.074 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2020-06-11 02:01:04.076 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [5]
2020-06-11 02:01:04.076 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [좋아요~]
2020-06-11 02:01:04.077 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 02:01:04.077 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-11 02:01:04.078 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [5]
2020-06-11 02:01:04.078 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [6]
2020-06-11 02:01:04.079 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [감사합니다.]
2020-06-11 02:01:04.079 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 02:01:04.080 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-11 02:01:04.080 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [6]
2020-06-11 02:01:04.081 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [7]
2020-06-11 02:01:04.081 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-11 02:01:04.082 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 02:01:04.083 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-11 02:01:04.083 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [7]
LAZY : [[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)]]
===== EntityGraph
Hibernate: 
    select
        post0_.id as id1_3_0_,
        comments1_.id as id1_2_1_,
        post0_.title as title2_3_0_,
        comments1_.comment as comment2_2_1_,
        comments1_.post_id as post_id3_2_1_,
        comments1_.post_id as post_id3_2_0__,
        comments1_.id as id1_2_0__ 
    from
        post post0_ 
    left outer join
        comment comments1_ 
            on post0_.id=comments1_.post_id
2020-06-11 02:01:04.130 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [1]
2020-06-11 02:01:04.131 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [2]
2020-06-11 02:01:04.132 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_0_] : [VARCHAR]) - [첫 포스트]
2020-06-11 02:01:04.133 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-11 02:01:04.134 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 02:01:04.134 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [1]
2020-06-11 02:01:04.135 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [2]
2020-06-11 02:01:04.135 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [1]
2020-06-11 02:01:04.135 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [3]
2020-06-11 02:01:04.136 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [두번째야~]
2020-06-11 02:01:04.137 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 02:01:04.137 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [1]
2020-06-11 02:01:04.138 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [3]
2020-06-11 02:01:04.138 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 02:01:04.139 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [5]
2020-06-11 02:01:04.140 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_0_] : [VARCHAR]) - [내가 2등~]
2020-06-11 02:01:04.140 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [좋아요~]
2020-06-11 02:01:04.141 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 02:01:04.141 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 02:01:04.142 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [5]
2020-06-11 02:01:04.142 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 02:01:04.143 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [6]
2020-06-11 02:01:04.143 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [감사합니다.]
2020-06-11 02:01:04.143 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 02:01:04.144 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 02:01:04.145 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [6]
2020-06-11 02:01:04.145 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 02:01:04.146 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [7]
2020-06-11 02:01:04.146 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-11 02:01:04.147 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 02:01:04.147 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 02:01:04.147 TRACE 31191 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [7]
EntityGraph : [[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)]]

그리고 EntityGraph에는 EntityGraphType이 존재하는데요. 아래와 같이 두 가지가 있습니다. 아무런 설정하지 않으면 default는 FETCH로 동작합니다.

  • FETCH: entity graph에 명시한 attribute는 EAGER로 패치하고, 나머지 attribute는 LAZY로 패치
  • LOAD: entity graph에 명시한 attribute는 EAGER로 패치하고, 나머지 attribute는 entity에 명시한 fetch type이나 디폴트 FetchType으로 패치 (e.g. @OneToMany는 LAZY, @ManyToOne은 EAGER 등이 디폴트이다.)

github source : https://github.com/aloftcat/blog-code/tree/master/jpa/NPlusOne

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 3  (0) 2020.06.15
N + 1 문제 해결 2  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04

정말 많이 접하는 문제입니다. 왜 발생하고 처리하는 방법은 뭐가 있는지 정리하려 합니다.

우선 왜 이런 문제가 생기는지 정리합니다. 언제나 문제는 원인부터 알아야 올바르게 대처하니까요.

 

포스트(Post)와 댓글(Comment)와의 관계를 예로 작성해 보았습니다.

@Getter@Setter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@Entity
@ToString
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    private List<Comment> comments = new ArrayList<>();

}

@Setter@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@Entity
@ToString(exclude = "post")
public class Comment {

    @Id
    @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    private Post post;
}
public interface PostRepository extends JpaRepository<Post, Long> {
}

public interface CommentRepository extends JpaRepository<Comment, Long> {
}
@Slf4j
@Service
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Transactional(readOnly = true)
    public List<String> findAllComments() {
        return getAllComments(postRepository.findAll());
    }

    /**
     * Lazy Load를 실행하기 위해 모든 comment 순회
     */
    private List<String> getAllComments(List<Post> posts) {
        return posts.stream()
                .map(a -> a.getComments().toString())
                .collect(Collectors.toList());
    }
}

 

test code는 junit5을 사용하였습니다.

@SpringBootTest
class PostRepositoryTest {

    @Autowired
    private PostService postService;
    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    public void setup() {
        Post post = new Post();
        post.setTitle("첫 포스트");

        Comment comment1 = new Comment();
        comment1.setComment("첫 댓글~! ");
        comment1.setPost(post);
        post.getComments().add(comment1);

        Comment comment2 = new Comment();
        comment2.setComment("두번째야~");
        post.getComments().add(comment2);
        comment2.setPost(post);

        Post post2 = new Post();
        post2.setTitle("내가 2등~");

        Comment comment3 = new Comment();
        comment3.setComment("좋아요~");
        comment3.setPost(post2);
        post2.getComments().add(comment3);

        Comment comment4 = new Comment();
        comment4.setComment("감사합니다.");
        comment4.setPost(post2);
        post2.getComments().add(comment4);

        Comment comment5 = new Comment();
        comment5.setComment("다음글 기대할께요.");
        comment5.setPost(post2);
        post2.getComments().add(comment5);

        postRepository.save(post);
        postRepository.save(post2);
    }

    @Test
    void testNPlusOne() {
        System.out.println(postService.findAllComments());
    }
Hibernate: 
    select
        post0_.id as id1_3_,
        post0_.title as title2_3_ 
    from
        post post0_
2020-06-05 11:50:15.206 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_] : [BIGINT]) - [1]
2020-06-05 11:50:15.210 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_] : [VARCHAR]) - [첫 포스트]
2020-06-05 11:50:15.211 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_] : [BIGINT]) - [4]
2020-06-05 11:50:15.211 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_] : [VARCHAR]) - [내가 2등~]
2020-06-05 11:50:15.218 TRACE 4256 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#1]
2020-06-05 11:50:15.219 TRACE 4256 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#4]
Hibernate: 
    select
        comments0_.post_id as post_id3_2_0_,
        comments0_.id as id1_2_0_,
        comments0_.id as id1_2_1_,
        comments0_.comment as comment2_2_1_,
        comments0_.post_id as post_id3_2_1_ 
    from
        comment comments0_ 
    where
        comments0_.post_id=?
2020-06-05 11:50:15.228 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-06-05 11:50:15.239 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [2]
2020-06-05 11:50:15.240 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-05 11:50:15.240 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-05 11:50:15.242 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [1]
2020-06-05 11:50:15.242 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [2]
2020-06-05 11:50:15.250 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [3]
2020-06-05 11:50:15.250 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [두번째야~]
2020-06-05 11:50:15.250 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-05 11:50:15.251 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [1]
2020-06-05 11:50:15.251 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [3]
Hibernate: 
    select
        comments0_.post_id as post_id3_2_0_,
        comments0_.id as id1_2_0_,
        comments0_.id as id1_2_1_,
        comments0_.comment as comment2_2_1_,
        comments0_.post_id as post_id3_2_1_ 
    from
        comment comments0_ 
    where
        comments0_.post_id=?
2020-06-05 11:50:15.264 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2020-06-05 11:50:15.266 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [5]
2020-06-05 11:50:15.267 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-05 11:50:15.267 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-05 11:50:15.268 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-05 11:50:15.268 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [5]
2020-06-05 11:50:15.269 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [6]
2020-06-05 11:50:15.270 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [좋아요~]
2020-06-05 11:50:15.270 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [6]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [7]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [감사합니다.]
2020-06-05 11:50:15.272 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-05 11:50:15.272 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-05 11:50:15.272 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [7]
[[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=7, comment=감사합니다.), Comment(id=6, comment=좋아요~), Comment(id=5, comment=다음글 기대할께요.)]]

결과를 보면 처음 select 하나만 원했을텐데 이 후로 post의 수 많큼 select * from comment where post_id=1, select * from comment where post_id=4 이렇게 2번더 실행 되었습니다. 그래서 원하는 1번의 query가 아니라 n+1번 실행되어 N+1문제라고 합니다.

 

문제는 하위 entity가 처음 select에서 가져오지 않고 실제 사용될 때  하나씩 하나씩 따로 가져오게되어 N+1 문제가 발생합니다. 실제 변수를 사용하는 시점에 JPA가 해당 변수 하나만을 가져오는 쿼리가 실행이 되는 것이죠.

JPA는 fetchType과 무관하게 그때 그때 JPQL에 맞춰 쿼리를 실행합니다. 그래서 eager든 lazy든 n+1 문제는 동일하게 일어납니다.

 

 

 

 


나는 항상 오늘 하루만 생각을 해... 가 아니라 나는 변수 하나만을 생각해... 지금 그 변수를 당장 가져와야지라고 동작한다고 보시면 됩니다.

다음 포스트는 어떻게 처리할지 알아보겠습니다.

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 2  (0) 2020.06.11
N + 1 문제 해결 1  (0) 2020.06.11
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
jpa 복합키에서 auto increment  (0) 2019.06.27

JPA를 왜 써야 할까에 앞서 이전에 진행했던 프로젝트들의 형태를 먼저 짚고 넘어가면,

 

기본적으로 JPA를 사용하고 있지만 더불어 JOOQ도 같이 사용하고 있습니다. 

 

public interface AccountInteHisRepositoryCustom {
    List<ActivityMonthlyReportDto> findAccountIntegrationHistoryMonthlyActiveStateByCreateDate(LocalDateTime date);
}

 

custom repository inteface를 정의하고 

 

@Repository
public class AccountInteHisRepositoryImpl implements AccountInteHisRepositoryCustom {

    ....

}

 

위와 같은 구현체인 XXXImpl에서 DSLContext를 주입받아 쿼리를 작성합니다.

이렇게 작성한 custom repository는 아래와 같이 JpaRepository와 같이 interface로 상속받아 적용합니다.

 

public interface AccountInteHisRepository extends JpaRepository<AccountInteHis, Long>, AccountInteHisRepositoryCustom {
....
}

 

그런데 이렇게 만들어진 custom repository는 변경에 대해 JPA보다 취약합니다.

native 쿼리보다 jooq dsl이 type safe하고 DB엔진에 영향을 받지 않습니다. 하지만 결국 쿼리가 바뀌면 custom repository도 바뀌어야 합니다.

예를 들어 필요에 의해 join해야하는 table이 하나 더 추가되면 JPA는 reference를 추가하는 것만으로 해결되지만 Jooq는 구현된 DSL을 다 바꿔야 하는 것입니다.

this.dslContext.select()
      .from(acctInteHis)
      .innerJoin(T_DEVICE)
      .on(acctInteHis.IMEI.eq(T_DEVICE.DEVICE_UID))
      .leftJoin(T_DEVICE_EXT)
      .on(device.DEVICE_UID.eq(T_DEVICE_EXT.DEVICE_UID))
      .where(acctInteHis.SEQ.in(selectSubQuery))
      .fetch()
      .stream()
      .map(m -> {
        return ActivityMonthlyReportDto.builder()
          .imei(m.getValue(acctInteHis.IMEI))
          .userId(m.getValue(acctInteHis.USER_ID))
          .state(AccountStateType.valueOf(m.getValue(acctInteHis.STATE)))
          .reqDate(m.getValue(acctInteHis.REQ_DATE).toInstant())
          .opCode(m.getValue(deviceExtension.OPCO))
          .productCode(m.getValue(deviceExtension.PRODUCE_CD))
          .build();
      })
      .collect(Collectors.toList());

jooq DSL의 예시입니다. 분명 내가 원하는 복잡한 쿼리를 구현할 수 있죠. 하지만 (비즈니스 로직 추가로 인해) query가 변경되면 DSL도 격하게 바뀔게 눈에 보이지 않나요? 실제 프로젝트는 영향받는 쿼리가 한 두 군데가 아닐 거라 생각합니다.

그리고 ActivityMonthlyReportDto라는 return 값도 바뀌지 말라는 보장도 없고 로직에 따라 Dto가 엄청 많아질 수 있습니다. 옛날에는 이게 귀찮아서 superset Dto를 만들어 사용하기도 했는데 이게 상황에 따라 어떤 member변수는 null이 된다는 것이 문제입니다.

하지만 JPA는 entity의 모든 reference는 (DB가 비어있지 않는 한) 채워져 있다는 것은 보장합니다. (이건 JPA가 application이 구동될 때 entity와 DB 스키마를 체크하기 때문에 믿을 수 있죠.)

더보기

이외에 JPA의 persistenceContext에 대한 장점등이 있는데요... dirty check, write behind, lazy loading등의 이점들이죠. 또한 domain 중심으로 데이터를 객체로 다룰 수 있는 기반을 JPA가 제공합니다.

 

결론은 DSL이 복잡하고 어려운 쿼리를 명확하게 구현한다는 것에는 의심의 여지가 없어 보입니다. 위의 dsl 예시처럼 subquery(selectSubQuery)도 마음먹은 대로 구현이 가능하니까요.

하지만 JPA를 잘 알고(!) 적용할 수 있다면 상당한 생산력 향상과 확장성을 확보 한다는 것은 매우 큰 매리트입니다.

JPA를 제대로 공부해서 마음껏 개발해야 겠습니다. (무슨 캠페인 같네요.ㅋㅋ)

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
jpa 복합키에서 auto increment  (0) 2019.06.27
JPA 기본 키 전략  (0) 2019.06.27

먼저 이 패턴을 사용하게 된 계기는 api response에서 http status code처럼 result를 return하려고 하려다 보니 이 result라는 놈이 특정 에러에 따라 정해지는 것이라 메소드 이름에 내용을 정해서 파라미터를 정하면 어떨까 하고 사용하게 되었습니다.

 

Result model입니다. builder 패턴에 Success, failWithNotFoundPath같은 이름에 따라 내용이 정해지게 됩니다.

@Getter
@ToString
@Builder(builderClassName = "Builder")
public class MyResult {

    private boolean succeed;

    private String majorCode;

    private String minorCode;

    public static class Builder {

        public Builder success() {
            this.succeed = true;
            this.majorCode = "200";
            this.minorCode = "0000";
            return this;
        }

        public Builder failWithNotFoundPath() {
            this.succeed = false;
            this.majorCode = "404";
            this.minorCode = "4001";
            return this;
        }
    }
}

builder 패턴은 객체 생성에 관련된 파라미터가 많을 때 깔끔하게 생성을 해주는 장점이 있습니다. 또한 builder에 변경된 파라미터들이 세팅된 후 build()를 하는 시점에 불변하게 만들 수 있는 장점도 있습니다. 하지만 결국 이 패턴도 변경될 파라미터들이 많아지면 복잡해 지는건 마찬가지 입니다.

위의 예제는 3개밖에 안되는 파라미터지만 더 많아도 메소드 이름을 잘 지으면 숫자로 된 result code들이 사람이 읽을 수 있는 string으로 대체되는 효과가 있습니다.

 

아래는 해당 result를 test하는 코드입니다.

@Component
public class BuilderRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        MyResult myResult = MyResult.builder().failWithNotFoundPath().build();
        System.out.println(myResult.toString());
        System.out.println("Value of majorCode on failure : " + myResult.getMajorCode());

        myResult = MyResult.builder().success().build();
        System.out.println(myResult.toString());
        System.out.println("Before changed value of majorCode : " + myResult.getMajorCode());

        myResult = MyResult.builder().success().majorCode("201").build();
        System.out.println(myResult.toString());
        System.out.println("After changed value of majorCode : " + myResult.getMajorCode());
    }
}

결과는 예상하신 것과 같으실 거예요.

MyResult(succeed=false, majorCode=404, minorCode=4001)
Value of majorCode on failure : 404
MyResult(succeed=true, majorCode=200, minorCode=0000)
Before changed value of majorCode : 200
MyResult(succeed=true, majorCode=201, minorCode=0000)
After changed value of majorCode : 201

 

'Programming > Java' 카테고리의 다른 글

java print api 주의 사항  (0) 2021.01.15
왜 돈 연산에는 Floating-point type을 쓰면 안되나?  (0) 2020.06.30
JVM Garbage Collection Basic  (0) 2019.05.10
Non-blocking, Blocking  (0) 2019.03.18
Asynchronous (VS Synchronous)  (0) 2019.03.18

+ Recent posts