<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>P의  개발/IT 일기</title>
    <link>https://jaesungyoun.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 10:44:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발이 재밌다</managingEditor>
    <item>
      <title>log4jdbc로 JPA 쿼리 로그 Formatter 적용하기</title>
      <link>https://jaesungyoun.tistory.com/93</link>
      <description>&lt;h3 data-end=&quot;203&quot; data-start=&quot;155&quot; data-ke-size=&quot;size23&quot;&gt;  Hibernate + log4jdbc: 완성형 SQL 로그 남기기와 포맷팅 적용&lt;/h3&gt;
&lt;p data-end=&quot;326&quot; data-start=&quot;205&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;br /&gt;오늘은 Hibernate를 사용할 때 Hibernate 외부에서 발생하는 쿼리 로그가 출력되지 않거나, Hibernate 쿼리가 &lt;b&gt;실행 가능한 완성형 쿼리&lt;/b&gt;로 보이지 않는 문제를 개선하는 방법에 대해 소개드리려 합니다.&lt;/p&gt;
&lt;hr data-end=&quot;331&quot; data-start=&quot;328&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;342&quot; data-start=&quot;333&quot; data-ke-size=&quot;size23&quot;&gt;  배경&lt;/h3&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;344&quot; data-ke-size=&quot;size16&quot;&gt;저희 프로젝트는 기본적으로 Hibernate 기반의 JPA를 사용하고 있으며, 쿼리 로그는 Spring Boot의 application.yml 설정을 통해 다음과 같이 남기고 있었습니다:&lt;/p&gt;
&lt;blockquote data-end=&quot;450&quot; data-start=&quot;344&quot; data-ke-style=&quot;style3&quot;&gt;logging:level:org.hibernate.SQL:debug&lt;br /&gt;org.hibernate.type.descriptor.sql.BasicBinder: trace&lt;/blockquote&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;569&quot; data-ke-size=&quot;size16&quot;&gt;이 설정을 통해 Hibernate가 생성한 SQL과 바인딩된 파라미터 정보를 확인할 수 있었지만, 다음과 같은 한계가 있었습니다.&lt;/p&gt;
&lt;h4 data-end=&quot;668&quot; data-start=&quot;643&quot; data-ke-size=&quot;size20&quot;&gt;❌ 문제점 1: 완성형 SQL이 아님&lt;/h4&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;669&quot; data-ke-size=&quot;size16&quot;&gt;Hibernate는 SQL 템플릿과 파라미터 바인딩 정보를 &lt;b&gt;별도로&lt;/b&gt; 출력합니다:&lt;/p&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;DEBUG&lt;br /&gt;org.hibernate.SQL:&lt;br /&gt;select user0_.id, user0_.name from user user0_ where user0_.id=?&lt;br /&gt;&lt;br /&gt;TRACE org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [1] as [BIGINT] - [1]&lt;/blockquote&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이런 방식은 실제 DB에 쿼리를 직접 넣고 확인하거나 복사해서 테스트할 때 매우 불편합니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1005&quot; data-start=&quot;978&quot; data-ke-size=&quot;size20&quot;&gt;❌ 문제점 2:&amp;nbsp;Hibernate 외부 로그 누락&lt;/h4&gt;
&lt;p data-end=&quot;1068&quot; data-start=&quot;1006&quot; data-ke-size=&quot;size16&quot;&gt;JDBC Template 등 Hibernate 외부에서 실행된 SQL은 위 설정으로는 로그에 나타나지 않습니다.&lt;/p&gt;
&lt;hr data-end=&quot;1073&quot; data-start=&quot;1070&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1113&quot; data-start=&quot;1075&quot; data-ke-size=&quot;size23&quot;&gt;✅ 해결 방향: log4jdbc 사용 + 로그 포맷팅 개선&lt;/h3&gt;
&lt;p data-end=&quot;1189&quot; data-start=&quot;1115&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 log4jdbc를 도입하여 JDBC 수준에서 발생하는 모든 SQL을 완성형으로 출력하도록 구성하였습니다.&lt;/p&gt;
&lt;h4 data-end=&quot;1216&quot; data-start=&quot;1191&quot; data-ke-size=&quot;size20&quot;&gt;1. log4jdbc 설정 파일 작성&lt;/h4&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;log4jdbc.log4j2.properties log4jdbc.spylogdelegator.name=logger.Slf4jSpyLogDelegator # 커스텀 클래스 경로 log4jdbc.dump.sql.maxlinelength=0&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1475&quot; data-start=&quot;1358&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1475&quot; data-start=&quot;1358&quot; data-ke-size=&quot;size16&quot;&gt;이 설정으로 Slf4jSpyLogDelegator를 통해 로그가 출력됩니다. 단, 이 방식만으로는 Hibernate가 생성한 SQL이 잘 포맷팅되지 않아, &lt;b&gt;JPA 포맷터를 직접 적용&lt;/b&gt;해보기로 했습니다.&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;log4jdbc.log4j2.properties log4jdbc.spylogdelegator.name은 기본 라이브러리가 제공하는 Slf4jSpyLogDelegator이 아닌 반드시 우리가 작성한 커스텀 클래스 경로를 넣어주어야합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1480&quot; data-start=&quot;1477&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1516&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size23&quot;&gt;  Slf4jSpyLogDelegator 커스터마이징&lt;/h3&gt;
&lt;p data-end=&quot;1615&quot; data-start=&quot;1518&quot; data-ke-size=&quot;size16&quot;&gt;Hibernate 내부에서 사용하는 SQL 포맷터인 BasicFormatterImpl을 활용하여 로그 출력 시 SQL을 자동 포맷팅하도록 델리게이터를 커스터마이징했습니다.&lt;/p&gt;
&lt;h4 data-end=&quot;1631&quot; data-start=&quot;1617&quot; data-ke-size=&quot;size20&quot;&gt;수정 코드 예시:&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1750909409989&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public void sqlOccurred(Spy spy, String methodCall, String sql)
    {
        BasicFormatterImpl formatter = new BasicFormatterImpl();
        if (!Properties.isDumpSqlFilteringOn() || shouldSqlBeLogged(sql))
        {
            if (sqlOnlyLogger.isDebugEnabled())
            {
                // JDBC Batch Insert일 경우, 정렬을 위해 포맷터 사용 x
                if (isBatchingLog(sql)) {
                    sqlOnlyLogger.debug(getDebugInfo() + nl + spy.getConnectionNumber() +
                            &quot;. &quot; + processSql(sql));
                }
                else {
                    /**
                     * 로그 찍을 때 jpa basic 포맷 적용
                     */
                    sqlOnlyLogger.debug(getDebugInfo() + nl + spy.getConnectionNumber() + &quot;. &quot; + formatter.format(processSql(sql)));
                }
            }
            else if (sqlOnlyLogger.isInfoEnabled())
            {
                // JDBC Batch Insert일 경우, 정렬을 위해 포맷터 사용 x
                if (isBatchingLog(sql)) {
                    sqlOnlyLogger.info(processSql(sql));
                }
                else {
                    /**
                     * 로그 찍을 때 jpa basic 포맷 적용
                     */
                    sqlOnlyLogger.info(formatter.format(processSql(sql)));
                }
            }
        }
    }

    private boolean isBatchingLog(String sql) {
        return sql != null &amp;amp;&amp;amp; sql.startsWith(&quot;batching&quot;) &amp;amp;&amp;amp; sql.contains(&quot;statements:&quot;);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2458&quot; data-start=&quot;2364&quot; data-ke-size=&quot;size16&quot;&gt;제가 수정한 부분은 쿼리 로그를 남기는 부분에 Hibernate의 SQL 포맷터를 적용한 것이며, JDBC Batch 쿼리 같은 경우는, 포맷터를 적용하면 오히려 쿼리 포맷이 이상하게 발생하여, isBatchingLog를 통해 별도로 구분하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2474&quot; data-start=&quot;2465&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-end=&quot;2524&quot; data-start=&quot;2476&quot; data-ke-size=&quot;size16&quot;&gt;이 방식으로 설정한 후에는 Hibernate 쿼리 로그도 다음처럼 깔끔하게 출력됩니다:&lt;/p&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;select &lt;br /&gt;&amp;nbsp; &amp;nbsp; user0_.id as id1_0_0_, &lt;br /&gt;&amp;nbsp; &amp;nbsp; user0_.name as name2_0_0_&lt;br /&gt;from &lt;br /&gt;&amp;nbsp; &amp;nbsp; user user0_ &lt;br /&gt;where &lt;br /&gt;&amp;nbsp; &amp;nbsp; user0_.id = 1;&lt;/blockquote&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2742&quot; data-start=&quot;2648&quot; data-ke-size=&quot;size16&quot;&gt;뿐만 아니라, JDBC Template 등에서 발생하는 쿼리도 동일하게 &lt;b&gt;완성형 + 포맷된 형태&lt;/b&gt;로 로그에 출력되므로 로그 분석이나 SQL 튜닝 시 매우 유용합니다.&lt;/p&gt;
&lt;p data-end=&quot;2742&quot; data-start=&quot;2648&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2742&quot; data-start=&quot;2648&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750909772231&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring boot 3.0.0 기행기(2) - log4jdbc로 JPA Logging 하기 및 Formatter 적용&quot; data-og-description=&quot;살다보면 그런 일이 있습니다. 아무도 하지 않고 검색해도 안 나오는 걸 보니 누구도 시도하지 않았지만 혼자서 집요하게 집착하게 되는 그런 일. 오늘 한 짓은 그런 류의 짓거리 입니다. 한창 JP&quot; data-og-host=&quot;karismamun.tistory.com&quot; data-og-source-url=&quot;https://karismamun.tistory.com/88&quot; data-og-url=&quot;https://karismamun.tistory.com/88&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjzBQY/hyZbuuiuSs/oEp2sn8zv04APEtJLjzURk/img.png?width=668&amp;amp;height=28&amp;amp;face=0_0_668_28,https://scrap.kakaocdn.net/dn/c5vQUQ/hyZcif9Rzg/nkhRYlkMUC9TGhOOMVLTeK/img.png?width=668&amp;amp;height=28&amp;amp;face=0_0_668_28&quot;&gt;&lt;a href=&quot;https://karismamun.tistory.com/88&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://karismamun.tistory.com/88&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjzBQY/hyZbuuiuSs/oEp2sn8zv04APEtJLjzURk/img.png?width=668&amp;amp;height=28&amp;amp;face=0_0_668_28,https://scrap.kakaocdn.net/dn/c5vQUQ/hyZcif9Rzg/nkhRYlkMUC9TGhOOMVLTeK/img.png?width=668&amp;amp;height=28&amp;amp;face=0_0_668_28');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring boot 3.0.0 기행기(2) - log4jdbc로 JPA Logging 하기 및 Formatter 적용&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;살다보면 그런 일이 있습니다. 아무도 하지 않고 검색해도 안 나오는 걸 보니 누구도 시도하지 않았지만 혼자서 집요하게 집착하게 되는 그런 일. 오늘 한 짓은 그런 류의 짓거리 입니다. 한창 JP&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;karismamun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발기록(feat.삽질)</category>
      <category>JPA</category>
      <category>log4jdbc</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/93</guid>
      <comments>https://jaesungyoun.tistory.com/93#entry93comment</comments>
      <pubDate>Thu, 26 Jun 2025 12:49:50 +0900</pubDate>
    </item>
    <item>
      <title>[Java] final</title>
      <link>https://jaesungyoun.tistory.com/92</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  final&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;final 키워드는 이름 그대로 끝 이라는 뜻이다.&lt;/li&gt;
&lt;li&gt;변수에 final 키워드가 붙으면 더는 값을 변경할 수 없다.&lt;/li&gt;
&lt;li&gt;final을 지역 변수에 설정할 경우 최초 한 번만 할당할 수 있다. 이후에 변수의 값을 변경하려면 컴파일 오류가 발생한다.&lt;/li&gt;
&lt;li&gt;final 을 지역 변수 선언 시 바로 초기화 한 경우 이미 값이 할당되었기 때문에 값을 할당할 수 없다.&lt;/li&gt;
&lt;li&gt;매개변수에 final 이 붙으면 메서드 내부에서 매개변수의 값을 변경할 수 없다. 따라서 메서드 호출 시점에 사용된 값이 끝까지 사용된다.&lt;/li&gt;
&lt;li&gt;final 을 필드에 사용할 경우 해당 필드는 생성자를 통해서 한번만 초기화 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  static + final&lt;/h3&gt;
&lt;pre id=&quot;code_1749604255444&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class FieldInit {
    static final int CONST_VALUE = 10;
    final int value = 10;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수 value처럼 final 필드를 필드에서 초기화하면 이미 값이 설정되었기 때문에 생성자를 통해서도 초기화 할 수 없다.&lt;/li&gt;
&lt;li&gt;final 필드를 필드에서 초기화 하는 경우, 모든 인스턴스가 같은 값을 가진다. 인스턴스를 생성할 때마다 힙 영역에 final 변수가 생성되는 것이다.&amp;nbsp;모든 인스턴스가 같은 값을 사용하기 때문에 결과적으로 메모리를 낭비하게 된다.&amp;nbsp; 이럴 때 사용하면 좋은 것이 바로 static 영역이다.&lt;/li&gt;
&lt;li&gt;static 영역은 단 하나만 존재하는 영역이다. 이런 이유로 필드에 final + 필드 초기화를 사용하는 경우 static을 붙여서 사용하는 것이 효과적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  참고&lt;/p&gt;
&lt;figure id=&quot;og_1749604754828&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;김영한의 실전 자바 - 기본편 강의 | 김영한 - 인프런&quot; data-og-description=&quot;김영한 | , 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만 명 돌&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8%ED%8E%B8?srsltid=AfmBOooa1X1JbbGav2oMB4lub0oy92F17JLtI1BD9rBDRcYSdLy7NJua&quot; data-og-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dPQG2V/hyY44QcarY/4Tevw4wOIxpSuPzsLkpffk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/e7Cdc/hyY8XaMfif/bjNqx6tbFLwPkfHsjfwbdK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ejWpZF/hyY8VjJf5S/rrhTzDzZDSFaKPrRGJg4G0/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8%ED%8E%B8?srsltid=AfmBOooa1X1JbbGav2oMB4lub0oy92F17JLtI1BD9rBDRcYSdLy7NJua&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8%ED%8E%B8?srsltid=AfmBOooa1X1JbbGav2oMB4lub0oy92F17JLtI1BD9rBDRcYSdLy7NJua&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dPQG2V/hyY44QcarY/4Tevw4wOIxpSuPzsLkpffk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/e7Cdc/hyY8XaMfif/bjNqx6tbFLwPkfHsjfwbdK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ejWpZF/hyY8VjJf5S/rrhTzDzZDSFaKPrRGJg4G0/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;김영한의 실전 자바 - 기본편 강의 | 김영한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김영한 | , 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만 명 돌&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/92</guid>
      <comments>https://jaesungyoun.tistory.com/92#entry92comment</comments>
      <pubDate>Wed, 11 Jun 2025 10:03:38 +0900</pubDate>
    </item>
    <item>
      <title>스프링 MVC2 정리</title>
      <link>https://jaesungyoun.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트는&amp;nbsp;spring-boot-starter-validation&amp;nbsp;라이브러리를&amp;nbsp;넣으면&amp;nbsp;자동으로&amp;nbsp;bean&amp;nbsp;validator를&amp;nbsp;인지하고&amp;nbsp;스프링에&amp;nbsp;통합 &lt;br /&gt;&lt;br /&gt;검증&amp;nbsp;로직을&amp;nbsp;모든&amp;nbsp;프로젝트에&amp;nbsp;적용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;공통화하고&amp;nbsp;,&amp;nbsp;표준화한&amp;nbsp;것이&amp;nbsp;Bean&amp;nbsp;Validation &lt;br /&gt;&lt;br /&gt;@Validated&amp;nbsp;:&amp;nbsp;스프링&amp;nbsp;전용&amp;nbsp;검증&amp;nbsp;어노테이션 &lt;br /&gt;&lt;br /&gt;@Valid:&amp;nbsp;자바&amp;nbsp;표준&amp;nbsp;검증&amp;nbsp;어노테이션 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;검증&amp;nbsp;순서 &lt;br /&gt;1.&amp;nbsp;@ModelAttribute&amp;nbsp;각각의&amp;nbsp;필드에&amp;nbsp;타입&amp;nbsp;변환&amp;nbsp;시도 &lt;br /&gt;1)&amp;nbsp;성공하면&amp;nbsp;다음으로 &lt;br /&gt;2)&amp;nbsp;실패하면&amp;nbsp;typeMismatch&amp;nbsp;로&amp;nbsp;FieldError&amp;nbsp;추가&amp;nbsp; &lt;br /&gt;2.&amp;nbsp;Validator&amp;nbsp;적용(실제&amp;nbsp;min&amp;nbsp;,&amp;nbsp;max&amp;nbsp;등의&amp;nbsp;검증&amp;nbsp;기능) &lt;br /&gt;&lt;br /&gt;바인딩에&amp;nbsp;성공한&amp;nbsp;필드만&amp;nbsp;Bean&amp;nbsp;Validation을&amp;nbsp;적용 &lt;br /&gt;BeanValidator&amp;nbsp;는&amp;nbsp;바인딩에&amp;nbsp;실패한&amp;nbsp;필드는&amp;nbsp;BeanValidation을&amp;nbsp;적용하지않는다. &lt;br /&gt;&lt;br /&gt;itemName`&amp;nbsp;에&amp;nbsp;문자&amp;nbsp;&quot;A&quot;&amp;nbsp;입력&amp;nbsp;타입&amp;nbsp;변환&amp;nbsp;성공&amp;nbsp;`itemName`&amp;nbsp;필드에&amp;nbsp;BeanValidation&amp;nbsp;적용 &lt;br /&gt;`price`&amp;nbsp;에&amp;nbsp;문자&amp;nbsp;&quot;A&quot;&amp;nbsp;입력&amp;nbsp;&quot;A&quot;를&amp;nbsp;숫자&amp;nbsp;타입&amp;nbsp;변환&amp;nbsp;시도&amp;nbsp;실패&amp;nbsp;typeMismatch&amp;nbsp;FieldError&amp;nbsp;추가&amp;nbsp;`price`&amp;nbsp;필 &lt;br /&gt;드는&amp;nbsp;BeanValidation&amp;nbsp;적용&amp;nbsp;X &lt;br /&gt;&lt;br /&gt;`@ModelAttribute`&amp;nbsp;는&amp;nbsp;HTTP&amp;nbsp;요청&amp;nbsp;파라미터(URL&amp;nbsp;쿼리&amp;nbsp;스트링,&amp;nbsp;POST&amp;nbsp;Form)를&amp;nbsp;다룰&amp;nbsp;때&amp;nbsp;사용한다. &lt;br /&gt;`@RequestBody`&amp;nbsp;는&amp;nbsp;HTTP&amp;nbsp;Body의&amp;nbsp;데이터를&amp;nbsp;객체로&amp;nbsp;변환할&amp;nbsp;때&amp;nbsp;사용한다.&amp;nbsp;주로&amp;nbsp;API&amp;nbsp;JSON&amp;nbsp;요청을&amp;nbsp;다룰&amp;nbsp;때&amp;nbsp;사용 &lt;br /&gt;한다. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;@ModelAttribute vs @RequestBody&lt;/b&gt;&lt;br /&gt;HTTP&amp;nbsp;요청&amp;nbsp;파리미터를&amp;nbsp;처리하는&amp;nbsp;`@ModelAttribute`&amp;nbsp;는&amp;nbsp;각각의&amp;nbsp;필드&amp;nbsp;단위로&amp;nbsp;세밀하게&amp;nbsp;적용된다.&amp;nbsp;그래서&amp;nbsp;특정&amp;nbsp;필드 &lt;br /&gt;에&amp;nbsp;타입이&amp;nbsp;맞지&amp;nbsp;않는&amp;nbsp;오류가&amp;nbsp;발생해도&amp;nbsp;나머지&amp;nbsp;필드는&amp;nbsp;정상&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있었다. &lt;br /&gt;`HttpMessageConverter`&amp;nbsp;는&amp;nbsp;`@ModelAttribute`&amp;nbsp;와&amp;nbsp;다르게&amp;nbsp;각각의&amp;nbsp;필드&amp;nbsp;단위로&amp;nbsp;적용되는&amp;nbsp;것이&amp;nbsp;아니라,&amp;nbsp;전체&amp;nbsp;객체 &lt;br /&gt;단위로&amp;nbsp;적용된다. &lt;br /&gt;따라서&amp;nbsp;메시지&amp;nbsp;컨버터의&amp;nbsp;작동이&amp;nbsp;성공해서&amp;nbsp;`ItemSaveForm`&amp;nbsp;객체를&amp;nbsp;만들어야&amp;nbsp;`@Valid`&amp;nbsp;,&amp;nbsp;`@Validated`&amp;nbsp;가&amp;nbsp;적용된다. &lt;br /&gt;`@ModelAttribute`&amp;nbsp;는&amp;nbsp;필드&amp;nbsp;단위로&amp;nbsp;정교하게&amp;nbsp;바인딩이&amp;nbsp;적용된다.&amp;nbsp;특정&amp;nbsp;필드가&amp;nbsp;바인딩&amp;nbsp;되지&amp;nbsp;않아도&amp;nbsp;나머지&amp;nbsp;필드 &lt;br /&gt;는&amp;nbsp;정상&amp;nbsp;바인딩&amp;nbsp;되고,&amp;nbsp;Validator를&amp;nbsp;사용한&amp;nbsp;검증도&amp;nbsp;적용할&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;`@RequestBody`&amp;nbsp;는&amp;nbsp;HttpMessageConverter&amp;nbsp;단계에서&amp;nbsp;JSON&amp;nbsp;데이터를&amp;nbsp;객체로&amp;nbsp;변경하지&amp;nbsp;못하면&amp;nbsp;이후&amp;nbsp;단계&amp;nbsp;자 &lt;br /&gt;체가 진행되지 않고 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링&amp;nbsp;MVC는&amp;nbsp;컨트롤러(핸들러)&amp;nbsp;밖으로&amp;nbsp;예외가&amp;nbsp;던져진&amp;nbsp;경우&amp;nbsp;예외를&amp;nbsp;해결하고,&amp;nbsp;동작을&amp;nbsp;새로&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;방법을&amp;nbsp;제 &lt;br /&gt;공한다.&amp;nbsp;컨트롤러&amp;nbsp;밖으로&amp;nbsp;던져진&amp;nbsp;예외를&amp;nbsp;해결하고,&amp;nbsp;동작&amp;nbsp;방식을&amp;nbsp;변경하고&amp;nbsp;싶으면&amp;nbsp; &lt;br /&gt;사용하면 된다. 줄여서 ExceptionResolver라&amp;nbsp;한다. &lt;br /&gt;&lt;br /&gt;&amp;nbsp;ExceptionResolver 를 사용하면 컨트롤러에서 예외가 발생해도 ExceptionResolver&lt;br /&gt;에서&amp;nbsp;예외를&amp;nbsp;처리해버린 &lt;br /&gt;따라서&amp;nbsp;예외가&amp;nbsp;발생해도&amp;nbsp;서블릿&amp;nbsp;컨테이너까지&amp;nbsp;예외가&amp;nbsp;전달되지&amp;nbsp;않고,&amp;nbsp;스프링&amp;nbsp;MVC에서&amp;nbsp;예외&amp;nbsp;처리는&amp;nbsp;끝이&amp;nbsp;난다. &lt;br /&gt;결과적으로&amp;nbsp;WAS&amp;nbsp;입장에서는&amp;nbsp;정상&amp;nbsp;처리가&amp;nbsp;된&amp;nbsp;것이다.&amp;nbsp;이렇게&amp;nbsp;예외를&amp;nbsp;이곳에서&amp;nbsp;모두&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;것이&amp;nbsp;핵심이다. &lt;br /&gt;서블릿 컨테이너까지 예외가 올라가면 복잡하고 지저분하게 추가 프로세스가 실행된다. 반면에 ExceptionResolver를 직접 사용하면 예외처리가 상당히 깔끔해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;figure id=&quot;og_1748437898093&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 | 김영한 - 인프런&quot; data-og-description=&quot;김영한 | , 원리를 알아야 핵심이 보인다!김영한의 스프링 MVC 활용편  &amp;zwj;    수강 전 확인해주세요! 본 강의는 자바 스프링 완전 정복 시리즈의 다섯 번째 강의입니다. 우아한형제들 최연소&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFjcsz/hyY0oUwJDz/AOi6kCBVUMivC0q746fXAK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bO4PSH/hyY0pME3Rd/GBxoIIXLms0Hjutv3dIpaK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cbPYzv/hyY07x9iIm/4ozlTTmAu10mmJihVOZze0/img.jpg?width=1200&amp;amp;height=700&amp;amp;face=0_0_1200_700&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFjcsz/hyY0oUwJDz/AOi6kCBVUMivC0q746fXAK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bO4PSH/hyY0pME3Rd/GBxoIIXLms0Hjutv3dIpaK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cbPYzv/hyY07x9iIm/4ozlTTmAu10mmJihVOZze0/img.jpg?width=1200&amp;amp;height=700&amp;amp;face=0_0_1200_700');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 | 김영한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김영한 | , 원리를 알아야 핵심이 보인다!김영한의 스프링 MVC 활용편  &amp;zwj;    수강 전 확인해주세요! 본 강의는 자바 스프링 완전 정복 시리즈의 다섯 번째 강의입니다. 우아한형제들 최연소&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Spring</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/89</guid>
      <comments>https://jaesungyoun.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 28 May 2025 22:11:41 +0900</pubDate>
    </item>
    <item>
      <title>김영한의 자바 입문 정리</title>
      <link>https://jaesungyoun.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;김영한님의 자바 입문편 강의를 수강하고 제가 복습이 필요하다고 생각한 부분들에 대해서 정리한 글입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;nbsp;&lt;/b&gt;&lt;b&gt;배열&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 같은 타입의 변수를 사용하기 편하게 하나로 묶어둔 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;배열 변수 선언&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746179804495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] students; // 배열 변수 선언&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 변수를 선언한다고해서 아직 사용할 수 있는 배열이 만들어진 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;배열 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746179873802&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;students = new int[5]; // 배열 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 배열을 사용하려면 배열을 생성해야한다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ewGrld/btsNJmhpDZd/mz2Uh0N0iZ0S0jWqf3pEkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ewGrld/btsNJmhpDZd/mz2Uh0N0iZ0S0jWqf3pEkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ewGrld/btsNJmhpDZd/mz2Uh0N0iZ0S0jWqf3pEkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FewGrld%2FbtsNJmhpDZd%2Fmz2Uh0N0iZ0S0jWqf3pEkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;102&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- new int[5]라고 입력하면 오른쪽 그림과 같이 총 5개의 int형 변수가 만들어진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;new는 새로 생성한다는 뜻이고, int[5]는 int형 변수 5개라는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;배열의 초기화&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new int[5] 라고 하면 총 5개의 int형 변수가 만들어진다. 자바는 배열을 생성할 때 그 내부값을 자동으로 초기화한다.&lt;/li&gt;
&lt;li&gt;숫자는 0, boolean은 false, String은 null로 초기화된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;배열 참조값 보관&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH4DQC/btsNKpX7ehm/zltnByy7HOBRDL1vttH9SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH4DQC/btsNKpX7ehm/zltnByy7HOBRDL1vttH9SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH4DQC/btsNKpX7ehm/zltnByy7HOBRDL1vttH9SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH4DQC%2FbtsNKpX7ehm%2FzltnByy7HOBRDL1vttH9SK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;105&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new int[5]로 배열을 생성하면 배열의 크기만큼 메모리를 확보한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;int형을 5개 사용하면 4byte * 5 -&amp;gt; 20 byte를 확보한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배열을 생성하고 나면 자바는 메모리 어딘가에 있는 이 배열에 접근할 수 있는참조값(주소)을 반환한다.&lt;/li&gt;
&lt;li&gt;앞서 선언한 배열 변수인 int[] students에 생성된 배열의 참조값을 보관한다.&lt;/li&gt;
&lt;li&gt;students 변수이 들고 있는 참조값을 통해 배열을 참조할 수 있다. 쉽게 이야기해서 참조값을 통해 메모리에 있는 실제 배열에 접근하고 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 기본형 vs 참조형&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 변수 데이터 타입을 가장 크게 보면 기본형과 참조형으로 분류할 수 있다. 사용하는 값을 직접 넣을 수 있는 기 형, 그리고 방금 본 배열 변수와 같이 메모리의 참조값을 넣을 수 있는 참조형으로 분류할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본형(Primitive Type)&lt;/b&gt;: 우리가 지금까지 봤던 int, long, double 처럼 직접 넣을 수 있는 데이터 타입을 기본형(Primitive Type)이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참조형(Reference Type)&lt;/b&gt;: int[] students , boolean처럼 변수에 사용할 값을 와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입을 참조형(Reference Type)이라 한다. 뒤에서 학습하는 객체나 클래스를 담을 수 있는 변수들도 모두 참조형이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;배열이 참조형을 사용하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본형은 모두 사이즈가 명확하게 정해져있다. 그런데 배열은 다음과 같이 동적으로 사이즈를 변경할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1746180637968&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; int size=10000; //사용자가 입력한 값을 넣었다고 가정해보자.
 new int[size]; //이 코드가 실행되는 시점에 배열의 크기가 정해진다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본형은 선언과 동시에 크기가 정해진다. 따라서 크기를 동적으로 바꾸거나 할 수는 없다. 반면에 배열과 같은 참조형은 크기를 동적으로 할당할 수 있다.예를 들면, Scanner를 사용해서 사용자 입력에 따라 size 변수의 값이 변하고, 생성되는 배열의 크기도 달라질 수 있다. 이런 것을 동적 메모리 할당이라한다. 기본형은 선언과 동시에 사이즈가 정적으로 정해지지만, 참조형을 사용하면 이처럼 동적으로 크기가 변해서 유연성을 제공할 수 있다.&lt;/li&gt;
&lt;li&gt;기본형은 사용할 값을 직접 저장한다. 반면에 참조형은 메모리에 저장된 배열이나 객체의 참조를 저장한다. 이로 인해 참조형은 더 복잡한 데이터 구조를 만들고 관리할 수 있다. 반면 기본형은 더 빠르고 메모리를 효율적으로 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746181094534&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;[지금 무료]김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음 강의 | 김영한 - 인프런&quot; data-og-description=&quot;김영한 | , 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 자바 입문[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만 명 돌&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8?srsltid=AfmBOoprf0N9DQFikANQVvijwgi14WdZuo49b1NxSeMJyDerF-ySv5yI&quot; data-og-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lKC2B/hyYM1yARFl/WD1OlGGmo8mEz250ZDT6CK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/h4Gaf/hyYMZU4lMv/JT3DYTa9auGDfT1SG9wKKk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ffluz/hyYM2qJuiX/9JlpjR0GkOjRUln1euhd8k/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8?srsltid=AfmBOoprf0N9DQFikANQVvijwgi14WdZuo49b1NxSeMJyDerF-ySv5yI&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8?srsltid=AfmBOoprf0N9DQFikANQVvijwgi14WdZuo49b1NxSeMJyDerF-ySv5yI&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lKC2B/hyYM1yARFl/WD1OlGGmo8mEz250ZDT6CK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/h4Gaf/hyYMZU4lMv/JT3DYTa9auGDfT1SG9wKKk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ffluz/hyYM2qJuiX/9JlpjR0GkOjRUln1euhd8k/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[지금 무료]김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음 강의 | 김영한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김영한 | , 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 자바 입문[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만 명 돌&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>김영한</category>
      <category>배열</category>
      <category>입문</category>
      <category>자바</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/88</guid>
      <comments>https://jaesungyoun.tistory.com/88#entry88comment</comments>
      <pubDate>Fri, 2 May 2025 19:18:54 +0900</pubDate>
    </item>
    <item>
      <title>내부 트랜잭션이 @Transactional(propagation = Propagation.REQUIRES_NEW)일 때, 외부 트랜잭션에 대해서는 커밋이 되어야하는데 되지 않는 문제</title>
      <link>https://jaesungyoun.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 내가 개발을 하고 있는 프로젝트에서 팀원 분이 다음과 같은 이슈가 발생한다고 하셨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부(최상단) 트랜잭션에서 a,b,c라는 로직을 수행하고 d라는 알림톡 발송 로직을 수행하고 있었는데, d라는 알림톡 발송 로직이 실패하더라도 외부 트랜잭션에서 호출하는 a,b,c 로직에 대해서는 정상적으로 커밋이 되길 바라셨는데 롤백이 되었던 이슈였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 나는 알림톡 발송 로직을 수행하는 d의 트랜잭션 전파 옵션을 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;REQUIRES_NEW로 바꾸어서 테스트를 해보았는데도 a,b,c 로직은 여전히 롤백되었다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 트랜잭션 -&amp;gt; d(알림톡 발송로직 및 기타 로직) -&amp;gt; (실제 알림톡 발송 로직)&amp;nbsp;&lt;/b&gt;이렇게 비즈니스 로직이 구성되어있었는데, 이것에 대해 각각 A,B,C라고 언급하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성된 코드를 보니, C에서 예외가 발생하면 try-catch로 잡고 있었고, 그것을 커스텀예외로 변환해서 B로 던지고 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 B는 try-catch로 해당 예외를 잡고, 로그만 남기고 A로는 던지지 않고 있었다. 따라서 나는 A에는 예외가 던져지지 않고, B에서 예외가 모두 잡혀 A에 대한 로직은 정상적으로 커밋이 되어야한다고 생각했다. 하지만 이게 웬걸.. 계속 커밋이 되지 않고 UnExpectedRollbackException이 계속 발생하는 것이었다. 나는 원인이 뭐지 싶어서 구글링을 계속 해보았는데 다음 링크와 같은 글을 찾을 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1741162114124&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;REQUIRES_NEW인데 rollback되는 이유가 궁금합니다. - 인프런 | 커뮤니티 질문&amp;amp;답변&quot; data-og-description=&quot;누구나 함께하는 인프런 커뮤니티. 모르면 묻고, 해답을 찾아보세요.&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/community/questions/1341391/requires-new%EC%9D%B8%EB%8D%B0-rollback%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0%EA%B0%80-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4?srsltid=AfmBOoo1P3YggF5ieQnhOAKC-ty45aZzUz4wVZv0OGKxCo8WEGUfvyjN&quot; data-og-url=&quot;https://www.inflearn.com/community/questions/1341391/requires-new%EC%9D%B8%EB%8D%B0-rollback%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0%EA%B0%80-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4?srsltid=AfmBOoo1P3YggF5ieQnhOAKC-ty45aZzUz4wVZv0OGKxCo8WEGUfvyjN&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PEUgh/hyYmN2bwvh/KStGcRQK8oQqOd0ZeCsycK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bIMfxU/hyYm1F22fK/3AmdsWjwoph1sT2J3IKTAK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/community/questions/1341391/requires-new%EC%9D%B8%EB%8D%B0-rollback%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0%EA%B0%80-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4?srsltid=AfmBOoo1P3YggF5ieQnhOAKC-ty45aZzUz4wVZv0OGKxCo8WEGUfvyjN&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/community/questions/1341391/requires-new%EC%9D%B8%EB%8D%B0-rollback%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0%EA%B0%80-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4?srsltid=AfmBOoo1P3YggF5ieQnhOAKC-ty45aZzUz4wVZv0OGKxCo8WEGUfvyjN&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PEUgh/hyYmN2bwvh/KStGcRQK8oQqOd0ZeCsycK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bIMfxU/hyYm1F22fK/3AmdsWjwoph1sT2J3IKTAK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;REQUIRES_NEW인데 rollback되는 이유가 궁금합니다. - 인프런 | 커뮤니티 질문&amp;amp;답변&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;누구나 함께하는 인프런 커뮤니티. 모르면 묻고, 해답을 찾아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;김영한님이 답변하신 내용은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;A(REQUIRED)&lt;br /&gt;B(REQUIRES_NEW) -&amp;gt; C(REQUIRED)&lt;br /&gt;결과적으로 A와, B 2개의 물리 트랜잭션이 존재하는 것이지요. C의 경우에는 B에 포함된 논리적인 트랜잭션입니다.&lt;br /&gt;이 경우 C에서 롤백 마킹을 하게 됩니다. 그러면 B에서 커밋을 하는 순간에 UnexpectedRollbackException이 발생하게 됩니다.&lt;br /&gt;그런데 이 예외가 AOP에서 발생하기 때문에 [A] -&amp;gt; [B의 AOP] -&amp;gt; [B]와 같이 됩니다.&lt;br /&gt;결과적으로 [B의 AOP]에서 UnexpectedRollbackException을 던집니다.&lt;br /&gt;A가 이 예외를 명확하게 잡아서 예외를 제거하면 A를 정상 커밋할 수 있습니다.&lt;br /&gt;A가 이 예외를 잡지 않고 그냥 둔다면 A도 예외가 발생했기 때문에 트랜잭션이 롤백됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음.. 그런데 사실 내가 보고 있던 코드는 A에서 예외를 잡고 있기 때문에 A가 정상 커밋이 되어야했는데 여전히 되고 있지 않았다. 정말 여기서부터는 머리가 하얘졌고, 강의도 다시 돌려보고 구글링도 계속 해봤는데 이해가 정말 가지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가, 한 줄기 빛과 같은 글을 보았는데, 실제로 &amp;nbsp;&lt;b&gt;한 클래스 안에서&lt;/b&gt;&lt;span style=&quot;color: #343333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Transactional 어노테이션을 단&lt;/span&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;다른 메서드를 호출해도 계속 같은 트랜잭션을 사용&lt;/b&gt;을 한다는 것이었다.&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spring은 메서드 또는 클래스에 @Transactional이 달린 클래스들에 대한 proxy를 생성하고, 트랜잭션 적용을 위해 &lt;b&gt;프록시를 통해 호출하는 메서드만 인터셉트&lt;/b&gt;한다. 동일한 bean에 있는 메서드라면 프록시를 통해 호출하는 것이 아니다. 즉, 내부 메서드를 호출한다는 것은 this.메서드()를 호출한 것과 같은 의미이기 때문에 프록시가 적용되지 않아 같은 트랜잭션을 사용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해결 방법으로는 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Service(클래스)를 분리해서 새롭게 Service를 스프링 빈으로 등록하고 해당 Service내에 B를 두고, A에서 B를 호출하도록 하는 것이었다. 물론 여기서도 REQUIRES_NEW는 적용해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 이렇게 하면, A에서 catch (Exception e) 을 타고, C에서 발생한 예외로 인한 B의 nested exception is org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only가 로그로 남는 걸 알 수있다. 그리고 이 예외를 catch 했기 때문에 A는 정상적으로 커밋, B,C만 롤백되는 것을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고로 다른 해결 방법도 있는데, 그 방법은 이 &lt;a href=&quot;https://keencho.github.io/posts/transaction-rollback/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고하시길 바랍니다!&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #343333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yeonyeon.tistory.com/283&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeonyeon.tistory.com/283&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741162817531&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring] @Transactional이 동작하지 않는다? &quot; data-og-description=&quot;부제: 동일한 bean에서는 @Transactional 적용이 되지 않는다. 핵심 내용 바로가기   문제 상황 줍줍은 자꾸만 사라지는 슬랙 메시지를 백업해주는 서비스입니다. 최근 줍줍에서는 신규 이용자들의 &quot; data-og-host=&quot;yeonyeon.tistory.com&quot; data-og-source-url=&quot;https://yeonyeon.tistory.com/283&quot; data-og-url=&quot;https://yeonyeon.tistory.com/283&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/i1Y9r/hyYm8LXnNh/ViNOoV48r0EEVP5quj9CJ1/img.png?width=800&amp;amp;height=280&amp;amp;face=0_0_800_280,https://scrap.kakaocdn.net/dn/B7Lcd/hyYmRQ2EAH/Rxy8vK0SuqOjnsDikIotf0/img.png?width=800&amp;amp;height=280&amp;amp;face=0_0_800_280,https://scrap.kakaocdn.net/dn/jOSQc/hyYncU53Bb/XKh8VwJsf15lFAUzu7B9Ak/img.png?width=2561&amp;amp;height=899&amp;amp;face=0_0_2561_899&quot;&gt;&lt;a href=&quot;https://yeonyeon.tistory.com/283&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yeonyeon.tistory.com/283&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/i1Y9r/hyYm8LXnNh/ViNOoV48r0EEVP5quj9CJ1/img.png?width=800&amp;amp;height=280&amp;amp;face=0_0_800_280,https://scrap.kakaocdn.net/dn/B7Lcd/hyYmRQ2EAH/Rxy8vK0SuqOjnsDikIotf0/img.png?width=800&amp;amp;height=280&amp;amp;face=0_0_800_280,https://scrap.kakaocdn.net/dn/jOSQc/hyYncU53Bb/XKh8VwJsf15lFAUzu7B9Ak/img.png?width=2561&amp;amp;height=899&amp;amp;face=0_0_2561_899');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] @Transactional이 동작하지 않는다? &lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;부제: 동일한 bean에서는 @Transactional 적용이 되지 않는다. 핵심 내용 바로가기   문제 상황 줍줍은 자꾸만 사라지는 슬랙 메시지를 백업해주는 서비스입니다. 최근 줍줍에서는 신규 이용자들의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yeonyeon.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ocblog.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ocblog.tistory.com/91&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741163000135&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;@Transactional propagation이 동작하지 않는 문제&quot; data-og-description=&quot;모든 코드는 github 에 있습니다. 들어가기 앞서... 다음과 같은 서비스 메서드가 있을 때, @RequiredArgsConstructor @Service public class HumanHandler { private final HumanRepository humanRepository; //passOneyear을 3번 호출&quot; data-og-host=&quot;ocblog.tistory.com&quot; data-og-source-url=&quot;https://ocblog.tistory.com/91&quot; data-og-url=&quot;https://ocblog.tistory.com/91&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ESU8S/hyYmW5SGmo/G53Ap1PuDTewvR1xbin1k0/img.png?width=800&amp;amp;height=293&amp;amp;face=0_0_800_293,https://scrap.kakaocdn.net/dn/o0zl6/hyYm252PRF/lqCUkHVC9St3dLXwUUzLK1/img.png?width=800&amp;amp;height=293&amp;amp;face=0_0_800_293,https://scrap.kakaocdn.net/dn/EgiRL/hyYm9D4ViT/cuUeYUMHkh0vfx1B4sDxFk/img.png?width=1786&amp;amp;height=844&amp;amp;face=0_0_1786_844&quot;&gt;&lt;a href=&quot;https://ocblog.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ocblog.tistory.com/91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ESU8S/hyYmW5SGmo/G53Ap1PuDTewvR1xbin1k0/img.png?width=800&amp;amp;height=293&amp;amp;face=0_0_800_293,https://scrap.kakaocdn.net/dn/o0zl6/hyYm252PRF/lqCUkHVC9St3dLXwUUzLK1/img.png?width=800&amp;amp;height=293&amp;amp;face=0_0_800_293,https://scrap.kakaocdn.net/dn/EgiRL/hyYm9D4ViT/cuUeYUMHkh0vfx1B4sDxFk/img.png?width=1786&amp;amp;height=844&amp;amp;face=0_0_1786_844');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;@Transactional propagation이 동작하지 않는 문제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;모든 코드는 github 에 있습니다. 들어가기 앞서... 다음과 같은 서비스 메서드가 있을 때, @RequiredArgsConstructor @Service public class HumanHandler { private final HumanRepository humanRepository; //passOneyear을 3번 호출&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ocblog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발기록(feat.삽질)</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/87</guid>
      <comments>https://jaesungyoun.tistory.com/87#entry87comment</comments>
      <pubDate>Wed, 5 Mar 2025 17:21:11 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data JPA 외래키로 findBy 사용 시에는 where절 엔티티로 조회하자</title>
      <link>https://jaesungyoun.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MxJOY/btsMjCeXO12/C3U18JxhPAHVg6ahGLz9Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MxJOY/btsMjCeXO12/C3U18JxhPAHVg6ahGLz9Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MxJOY/btsMjCeXO12/C3U18JxhPAHVg6ahGLz9Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMxJOY%2FbtsMjCeXO12%2FC3U18JxhPAHVg6ahGLz9Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1197&quot; height=&quot;614&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 엔티티에서 findByUserIdAndPopUpStoreId로 하면 User 테이블과 PopUpStore 테이블과 join을 하여 다음과 같은 쿼리가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qwZmL/btsMjqMo2qV/xkdIhYUATIgQmcOYL56lGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qwZmL/btsMjqMo2qV/xkdIhYUATIgQmcOYL56lGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qwZmL/btsMjqMo2qV/xkdIhYUATIgQmcOYL56lGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqwZmL%2FbtsMjqMo2qV%2FxkdIhYUATIgQmcOYL56lGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1762&quot; height=&quot;524&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 건 bookmark 테이블의 컬럼 값만 가지고 where절을 통해 조회를 하고 싶은데 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;보면 불필요한 join이 발생함을 알 수 있다.&lt;span&gt; 이 불필요한 join문을 제거할 방법이 있는데, 그것은 where 절에 연관관계 엔티티를 넣어 조회하는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;41&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bleSzp/btsMkrXOOpH/eP3uPqd03Ea4OjZCevJuJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bleSzp/btsMkrXOOpH/eP3uPqd03Ea4OjZCevJuJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bleSzp/btsMkrXOOpH/eP3uPqd03Ea4OjZCevJuJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbleSzp%2FbtsMkrXOOpH%2FeP3uPqd03Ea4OjZCevJuJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;41&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;41&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Spring Data JPA findBy의 파라미터로 엔티티를 전달하면 다음과 같이 join문이 제거되고 내가 원하던 쿼리가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n45NS/btsMkK3Uev1/1HskOPWKPrwVqI22StcmpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n45NS/btsMkK3Uev1/1HskOPWKPrwVqI22StcmpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n45NS/btsMkK3Uev1/1HskOPWKPrwVqI22StcmpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn45NS%2FbtsMkK3Uev1%2F1HskOPWKPrwVqI22StcmpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1722&quot; height=&quot;377&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>JPA</category>
      <category>findby</category>
      <category>JPA</category>
      <category>where절 엔티티로 조회</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/86</guid>
      <comments>https://jaesungyoun.tistory.com/86#entry86comment</comments>
      <pubDate>Sat, 15 Feb 2025 21:15:08 +0900</pubDate>
    </item>
    <item>
      <title>Pageable 동작 방식</title>
      <link>https://jaesungyoun.tistory.com/85</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #333238; text-align: left;&quot;&gt;
&lt;div&gt;다음은 내가 Spring에서 지원하는 Pageable 인터페이스를 활용할 때, 여러 케이스를 시도하면서 얻었던 결론을 정리한 것이다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333238; text-align: left;&quot; data-testid=&quot;wiki-page-content&quot; data-qa-selector=&quot;wiki_page_content&quot;&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⚽ Pageable 정렬 동작 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333238; text-align: initial;&quot; data-sourcepos=&quot;3:1-3:97&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;CASE 1) 조회하고자 하는 쿼리에 OrderBy (정적 정렬 조건)명시되어있을 때&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: initial;&quot; data-sourcepos=&quot;5:1-11:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;5:1-6:39&quot;&gt;&lt;b&gt;Querydsl&lt;/b&gt;을 사용한 메소드의&amp;nbsp;경우에는 클라이언트에서 sort에 파라미터 값 보내도 무시됨(Querydsl에서는 따로 정렬 쿼리 메소드를 구현해서 작업을 해주지 않는 이상 Pageable sort가 동작하지 않음)&lt;/li&gt;
&lt;li data-sourcepos=&quot;7:1-9:0&quot;&gt;&lt;b&gt;JPA&lt;/b&gt;에서 반환 타입이 Page이고, @Query 어노테이션으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;JPQL&lt;/b&gt;에 정적 정렬 조건을 넣는 경우 클라이언트에서 전달받은 sort 파라미터 추가돼서 정렬 조건으로 들어감 (JPQL 정적 정렬 + 클라이언트로부터 받은 sort 정렬 조건)&lt;/li&gt;
&lt;li data-sourcepos=&quot;10:1-11:0&quot;&gt;파라미터 값 보내지 않으면, &lt;span style=&quot;color: #ee2323;&quot;&gt;서버 디폴트(@PageDefault) 정렬 조건(어노테이션)이 있는 경우에는 정적 정렬 조건에 더해서 디폴트 정렬조건도 같이 적용(중복 주의)&lt;/span&gt;, 디폴트 정렬 조건이 없으면 정적 정렬 조건만 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333238; text-align: initial;&quot; data-sourcepos=&quot;12:1-12:104&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;CASE 2) 조회하고자 하는 쿼리에 OrderBy(정적 정렬 조건) 명시되어있지 않을 때&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: initial;&quot; data-sourcepos=&quot;14:4-20:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;14:4-17:0&quot;&gt;클라이언트에서 sort에 파라미터 값 보내지 않으면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;14:4-17:0&quot;&gt;서버에 Default 정렬 조건 명시되어있으면 해당 정렬 조건으로 정렬 &amp;nbsp;&lt;/li&gt;
&lt;li data-sourcepos=&quot;14:4-17:0&quot;&gt;명시하지 않았으면 정렬 조건 들어가지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;18:4-20:0&quot;&gt;클라이언트에서 sort 파라미터 값 요청 보내면 repository 인터페이스에서 반환 타입으로 Page를 쓰면 해당 클라이언트 요청 sort 파라미터 값으로 정렬&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333238; text-align: initial;&quot; data-sourcepos=&quot;21:1-21:32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;CASE 3) 동적 정렬 조건&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: initial;&quot; data-sourcepos=&quot;22:2-24:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;22:2-24:0&quot;&gt;Querydsl에서만 동적 정렬 조건 적용, 파라미터 값으로 pageble 객체와 Q클래스 엔티티를 전달하면 사용 가능&lt;/li&gt;
&lt;li data-sourcepos=&quot;22:2-24:0&quot;&gt;예제 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1735883700090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class QueryDslUtils {

    public static List&amp;lt;OrderSpecifier&amp;gt; getOrderSpecifiers(List&amp;lt;? extends SortCode&amp;gt; sortCodes, Pageable pageable, Path&amp;lt;?&amp;gt; qEntity) {
        List&amp;lt;OrderSpecifier&amp;gt; orders = new ArrayList&amp;lt;&amp;gt;();
        PathBuilder pathBuilder = new PathBuilder&amp;lt;&amp;gt;(qEntity.getType(), qEntity.getMetadata());

        for (SortCode sortCode : sortCodes) {
            orders.add(new OrderSpecifier&amp;lt;&amp;gt;(sortCode.getOrder(), pathBuilder.get(sortCode.getField())));
        }

        // pageable의 sort 파라미터에 해당하는 부분 
//        for (Sort.Order order : pageable.getSort()) {
//            Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
//            orders.add(new OrderSpecifier&amp;lt;&amp;gt;(direction, pathBuilder.get(order.getProperty())));
//        }
//        return orders;

        return orders;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;여기서 주석 처리된 부분을 사용하지 않았던 이유&lt;/span&gt;&lt;/b&gt;로는, 프로젝트를 진행하면서 웹에서는 페이징 sort를 위한 파라미터로 &lt;b&gt;sort=column1,desc&amp;amp;sort=column2,asc&lt;/b&gt; 이런 형식으로 쿼리 파라미터를 받을 수 있었는데, 같이 프로젝트를 진행한 iOS 클라이언트 개발자분께서 &lt;b&gt;sort=column1&amp;amp;sort=desc&amp;amp;sort=column2&amp;amp;sort=asc&lt;/b&gt; 이런 형식으로 쿼리 파라미터를 넘겨줄 수 있다고 하여 다음과 같이 sortCode라는 인터페이스를 따로 정의해놓고 그것을 상속 받는 여러 도메인에 대한 정렬 enum 들을 정의해서 파라미터를 받을 수 있도록 구현하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735884021905&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface SortCode {
    String getField();
    Order getOrder();
}

@Getter
public enum CommentSortCode implements SortCode {
    MOST_LIKED(&quot;likeCount&quot;, Order.DESC),  // 좋아요 많은 순
    NEWEST(&quot;createDateTime&quot;, Order.DESC); // 최근 작성 순
    private final String field;
    private final Order order;

    CommentSortCode(String field, Order order) {
        this.field = field;
        this.order = order;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발기록(feat.삽질)</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/85</guid>
      <comments>https://jaesungyoun.tistory.com/85#entry85comment</comments>
      <pubDate>Fri, 3 Jan 2025 15:00:31 +0900</pubDate>
    </item>
    <item>
      <title>JPA Bulk Insert</title>
      <link>https://jaesungyoun.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 완전 대량의 데이터는 아니지만, 적당하게 많은 데이터들을 받아서 인터페이스 테이블에 동기화를 해야 할 일이 생겼다. 그래서 insert 하는 방식으로, 매번 for문을 돌면서 save를 하기보다는 List에 엔티티 객체들을 담아두고 saveAll을 통해 새로운 트랜잭션 생성을 위한 비용을 줄이고, 성능을 높이려고 하였는데, 이 방식도 결국에는 단 건 쿼리로 insert 되는 바람에 내가 기대하던 바는 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음 환경 설정을 통해 batch size를 지정하고 해당 size만큼의 데이터를 하나의 insert 쿼리로 처리하도록 하였다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734080010746&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  jpa:
    properties:
      hibernate:
        jdbc.batch_size: 100
        order_inserts: true
        order_updates: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 웬일.. hibernate가 생성하는 쿼리는 단 건 쿼리로 나가고 있었다. 이게 뭐지 싶어서 이런 저런 기술 블로그를 다 찾아봐도 나와 똑같이 설정을 했었는데, 내가 빠뜨린 것이 하나 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 사용하는 DBMS는 MySQL 이었고, MySQL의 경우에는, multi row insert 를 하기 위해서는 spring datasource의 url에 다음과 같은 옵션을 추가해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734080111744&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;url: jdbc:~~://localhost:{port}/~~?&amp;amp;rewriteBatchedStatements=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 추가하고 다시 서버를 실행해서 서비스를 호출했는데,, 또 hibernate는 로그로 단 건 쿼리를 여러 번&amp;nbsp; 출력할 뿐이었다. 더 구글링을 해보니 다음과 같은 옵션을 추가로 주어야 실제 DBMS에서 실행되는 쿼리를 볼 수 있다고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734080179574&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;profileSQL=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면, &lt;b&gt;rewriteBatchedStatements 옵션을 적용&lt;/b&gt;해주면 &lt;b&gt;여러 insert 쿼리를 bulk insert 방식으로 재작성&lt;/b&gt;준다. 이렇게 재작성된 쿼리는 &lt;b&gt;Hibernate에 찍히는 SQL 로그와 달라지는데 바뀐 쿼리를 보고 싶은 경우 profileSQL=true 를 추가&lt;/b&gt;해주면 실제 mysql 쿼리를 볼 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발기록(feat.삽질)</category>
      <category>BULK INSERT</category>
      <category>JPA</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/84</guid>
      <comments>https://jaesungyoun.tistory.com/84#entry84comment</comments>
      <pubDate>Fri, 13 Dec 2024 17:58:20 +0900</pubDate>
    </item>
    <item>
      <title>[Querydsl] java.time.LocalDateTime is not compatible with java.lang.String 에러</title>
      <link>https://jaesungyoun.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Querydsl을 이용해서 개발을 하다가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&quot;java.time.LocalDateTime is not compatible with java.lang.String&quot;&lt;/span&gt; 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러가 발생했던 이유로는, 조회했던 Entity의 컬럼이 LocalDateTime 타입이었는데, DTO에 매핑시키려고 했던 필드의 타입은 String이어서 발생했었다. DTO 필드 형태를 바꿔주면 간단히 해결되지만, 나는 외부 솔루션을 연동하기 때문에, 어쩔 수 없이 String으로 넘겨주어야했다. 따라서 조회할 때 LocalDateTime을 String으로 변환을 해줘야하는데, 찾아보니Querydsl에서 지원하는 Expressions와 MySQL의 DATE_FORMAT이라는 함수에 변환해줄 String 형식을 지정하면 바꿀 수 있었다. 내가 변환을 한 코드는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729647997366&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.stringTemplate(
                            &quot;DATE_FORMAT({0}, '%Y%m%d%H%i%s')&quot;,
                            popUpStoreEntity.startDt
                    ).as(&quot;startDt&quot;),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Expressions.stringTemplate 메서드에서 {0}은 첫 번째 인자를 하고, 이 경우, popUpStoreEntity.startDt가 {0}에 해당한다.이 이 값을 ' &lt;span style=&quot;background-color: #e7e0d6; color: #111827; text-align: start;&quot;&gt;yyyyMMddHHmmss&lt;/span&gt; ' 형태로 변경한다는 의미이다. &amp;nbsp;참고로, stringTemplate 메서드는 SQL 템플릿을 생성하며, {0}, {1} 등의 플레이스홀더를 사용하여 인자를 삽입할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발기록(feat.삽질)</category>
      <category>java.time.localdatetime is not compatible with java.lang.string</category>
      <category>queryDSL</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/79</guid>
      <comments>https://jaesungyoun.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 23 Oct 2024 10:50:39 +0900</pubDate>
    </item>
    <item>
      <title>[Issue] SpringDocs Swagger UI에서 Inner Class 필드 인식 못 하는 경우</title>
      <link>https://jaesungyoun.tistory.com/78</link>
      <description>&lt;div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⚽ 문제 상황&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발하는 도중에 Swagger에 DTO의 필드 수정 사항이 반영이 제대로 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 DTO 2개가 프로젝트 내부에 있었는데, 둘 다 이너클래스 명이 PopUpStore였다.&lt;/p&gt;
&lt;pre id=&quot;code_1727602544319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Builder
public class GetOpenPopUpStoreListResponse {

    private List&amp;lt;PopUpStore&amp;gt; openPopUpStoreList;
    private int totalPages;
    private long totalElements;


    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class PopUpStore {
        private Long id;
        private Category category;
        private String name;
        private String address;
        private String mainImageUrl;
        private LocalDateTime startDate;
        private LocalDateTime endDate;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1727602598041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Builder
public class GetPopUpStoreDetailResponse {

    private String name;
    private String desc;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    private String address;
    private long commentCount;
    private boolean bookmarkYn;
    private boolean loginYn;
    private String mainImageUrl;
    private List&amp;lt;PopUpStoreImage&amp;gt; imageList;
    private List&amp;lt;Comment&amp;gt; commentList;
    private List&amp;lt;PopUpStore&amp;gt; similarPopUpStoreList;

    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class PopUpStore {
        private Long id;
        private String name;
        private String mainImageUrl;
        private LocalDateTime endDate;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Docs은 패키지와 상관 없이 Class 이름으로 구분하여 @Schema를 Swagger에 적용시킨다. 즉, GetOpenPopUpStoreListResponse객체의 PopUpStore 객체와 GetPopUpStoreDetailResponse객체의 PopUpStore 객체가 같은 꼴이 되고, 먼저 찾아진 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;GetPopUpStoreDetailResponse객체의 PopUpStore 객체&lt;/span&gt;를 기준으로 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;GetOpenPopUpStoreListResponse객체&lt;/span&gt;까지 @Schema가 적용이 되는 것이다&lt;/p&gt;
&lt;h3 id=&quot;해결-방법&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⚽ 해결 방법&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;springdoc:
  use-fqn: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.yml에 위와 같이 설정을 해주면 해결할 수 있다. fqn은 fully qualifed name을 뜻하며 패키지명을 포함하여 Class를 찾기 위한 옵션이라고 보면 된다.```&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;
&lt;div data-testid=&quot;follow-btn&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Spring docs</category>
      <category>swagger</category>
      <author>개발이 재밌다</author>
      <guid isPermaLink="true">https://jaesungyoun.tistory.com/78</guid>
      <comments>https://jaesungyoun.tistory.com/78#entry78comment</comments>
      <pubDate>Sun, 29 Sep 2024 18:40:40 +0900</pubDate>
    </item>
  </channel>
</rss>