<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Leffe's tistory</title>
    <link>https://leffept.tistory.com/</link>
    <description>개발자로서 성장하면서 배워온 지식과 경험을 공유하는 공간입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 20 Aug 2026 03:59:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Leffe_pt</managingEditor>
    <image>
      <title>Leffe's tistory</title>
      <url>https://tistory1.daumcdn.net/tistory/4163038/attach/74d64af26de7415e87ff5da6a1a224cf</url>
      <link>https://leffept.tistory.com</link>
    </image>
    <item>
      <title>[MySQL]트랜잭션의 격리 수준 파헤치기 2 (feat. 영속성 컨텍스트의 함정)</title>
      <link>https://leffept.tistory.com/568</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 지난 포스팅에서는 MySQL에서의 트랜잭션 격리 수준에 대해 이해하는 주제를 다루었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 조금 더 실무의 경험에 가깝게 구성된 JPA에서의 트랜잭션의 격리 수준이 어떻게 적용되는지 알아보고자 합니다. 또, 영속성 컨텍스트의 함정도 같이 다룹니다.&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;[1편 보러 가기]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leffept.tistory.com/567&quot;&gt;https://leffept.tistory.com/567&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754400210900&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;[MySQL]트랜잭션의 격리 수준 파헤치기 1&quot; data-og-description=&quot;안녕하세요, 오랜만에 인사드립니다. 오늘은 트랜잭션의 격리 수준에 대해서 낱낱이 파헤쳐 보려고 합니다. MySQL의 트랜잭션 격리 수준에 따라서 어떠한 점들이 달라지게 되는지 알아보겠습니&quot; data-og-host=&quot;leffept.tistory.com&quot; data-og-source-url=&quot;https://leffept.tistory.com/567&quot; data-og-url=&quot;https://leffept.tistory.com/567&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brmVZE/hyZuCYTIl3/2k1WyoORYptM41AkiQBj11/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7Ja5S/hyZqU705Ul/73viK1yyDnZkEja0Khyk51/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://leffept.tistory.com/567&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leffept.tistory.com/567&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brmVZE/hyZuCYTIl3/2k1WyoORYptM41AkiQBj11/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7Ja5S/hyZqU705Ul/73viK1yyDnZkEja0Khyk51/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;[MySQL]트랜잭션의 격리 수준 파헤치기 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 오랜만에 인사드립니다. 오늘은 트랜잭션의 격리 수준에 대해서 낱낱이 파헤쳐 보려고 합니다. MySQL의 트랜잭션 격리 수준에 따라서 어떠한 점들이 달라지게 되는지 알아보겠습니&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leffept.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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Data JPA 와 트랜잭션의 격리 수준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅을 통해 격리 수준은 Read Uncomitted, Read Comitted, Repeatable Read, Serializable 총 4가지가 있는 것을 배웠습니다. Spring의 격리 수준은 어떻게 구성되어 있을까요?&lt;/p&gt;
&lt;pre id=&quot;code_1754400200700&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.transaction.annotation.Isolation;

public enum Isolation {
	/**
	 * Use the default isolation level of the underlying data store.
	 * &amp;lt;p&amp;gt;All other levels correspond to the JDBC isolation levels.
	 * @see java.sql.Connection
	 */
	DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

	READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

	READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

	REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

	SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 격리 수준과 동일하게 구성되어 있습니다. @Transactional 어노테이션에 별도의 옵션을 지정하지 않으면 데이터베이스의 기본 격리 수준을 따라갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 변경을 하지 않았다면 MySQL이면 Repeatable Read, PostrgreSQL/Oracle이면 Read Comitted가 적용됩니다.&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에서 지원하는 격리 수준 자체는 동일하지만 JPA에는 영속성 컨텍스트라는 개념이 존재해서, 애플리케이션 레벨에서는 조금 다르게 적용됩니다. 오늘은 이 부분을 집중적으로 다룹니다.&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;위에서 언급한 Isolation은 Spring의 JDBC 수준에서 동작하는 개념입니다. (JPA의 표준에서 동작하는 개념은 아닙니다)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA를 이용한 격리 수준의 이해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 영속성 컨텍스트로 인해서 트랜잭션의 격리 수준이 Read Comitted일 지라도 애플리케이션 레벨에서는 Repeatable Read처럼 읽는 것이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;b&gt;영속성 컨텍스트란?&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-hveid=&quot;CAMQAQ&quot; data-ved=&quot;2ahUKEwj_wIbrkt2OAxUvslYBHdCjHxgQo_EKegQIAxAB&quot;&gt;JPA 영속성 컨텍스트는&amp;nbsp;애플리케이션과 데이터베이스 사이에서 엔티티 객체를 관리하는 가상의 데이터베이스 역할을 하는 논리적인 개념입니다.&lt;br /&gt;영속성 컨텍스트는 엔티티의 생명주기를 관리하고, 동일성 보장, 변경 감지, 1차 캐시, 지연 로딩 등의 기능을 제공하여 데이터베이스 연동을 효율적으로 처리할 수 있도록 돕습니다.&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPA의 Repeatable Read (영속성 컨텍스트)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 이용하면 애플리케이션 레벨에서의 Repeatable Read는 어떻게 가능한 것일까요? 예시와 함께 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754400200702&quot; class=&quot;routeros&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;데이터베이스의 격리 수준은 Read Comitted로 가정

┌────────────────────────────────────────┬────────────────────────────────────────┐
│               트랜잭션 A                 │               트랜잭션 B                 │
├────────────────────────────────────────┼────────────────────────────────────────┤
│ @Transactional                         │                                        │
│ User user1 = userRepository            │                                        │
│     .findById(1L);                     │                                        │
│ -&amp;gt; DB 조회, 결과 : John                  │                                        │
│ -&amp;gt; 영속성 컨텍스트에 저장                   │                                        │
│                                        │                                        │
│                                        │ UPDATE User SET name = 'James' WHERE   │
│                                        │ id = 1;                                │
│                                        │ COMMIT                                 │
│                                        │                                        │
│ User user2 = userRepository            │                                        │
│     .findById(1L);                     │                                        │
│ -&amp;gt; DB 조회, 결과 : John (동일)            │                                        │
│ -&amp;gt; 영속성 컨텍스트에서 조회                  │                                        │
│                                        │                                        │
└────────────────────────────────────────┴────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA는 일반적으로 트랜잭션 단위로 영속성 컨텍스트를 시작하고 종료합니다. 엔티티를 한번 조회하고 나면 그 결과를 캐시처럼 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 다른 트랜잭션 B에서 데이터가 업데이트되어도 트랜잭션 A에서는 Repeatable Read가 가능해지는 것입니다.&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;Repeatable Read를 달성하기 위해 영속성 컨텍스트가 존재하는 것은 아니고, 영속성 컨텍스트의 1차 캐시 특징으로 인해서 반복 가능한 읽기가 가능한 것이라고 이해하시면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, DB 조회가 일어나지 않기에 성능적인 측면에서도 유리한 부분이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&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;size16&quot;&gt;제가 실제로 경험했던 한 가지 사례를 함께 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754400200703&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;┌──────────────────────────────────────┬──────────────────────────────────────┐
│             트랜잭션 A                 │             트랜잭션 B                 │
├──────────────────────────────────────┼──────────────────────────────────────┤
│ couponRepository.findById(1L);       │                                      │
│ -&amp;gt; 쿠폰 조회, 상태 : ACTIVE             │                                      │
│                                      │ couponRepository.findById(1L);       │
│                                      │ -&amp;gt; 쿠폰 조회, 상태 : ACTIVE             │
│ 쿠폰 사용 기록 생성 (RESERVED)           │                                      │
│                                      │ 쿠폰 사용 기록 생성 (RESERVED)           │
│                                      │                                      │
│ 기타 비즈니스 로직 수행                   │                                      │
│                                      │ 기타 비즈니스 로직 수행                   │
│                                      │                                      │
│ couponRepository                     │                                     │
│   .findByIdForUpdate(1L);            │ couponRepository                    │
│ -&amp;gt; 락과 함께 쿠폰 조회, 상태 : ACTIVE     │    .findByIdForUpdate(1L);           │
│                                      │ -&amp;gt; 락 획득 대기                        │
│ 쿠폰 사용 기록 생성 (USED)               │                                      │
│ 쿠폰 상태 USED로 변경                    │                                      │
│                                      │                                      │
│ COMMIT                               │                                      │
│                                      │                                      │
│                                      │ -&amp;gt; 락 획득                            │
│                                      │ 조회(영속성 컨텍스트, DB에서 가져온 값 버림)  │
│                                      │ 상태 : ACTIVE                         │
│                                      │                                      │
│                                      │ 쿠폰 사용 기록 생성 (USED, 중복)          │
│                                      │ 쿠폰 상태 USED로 변경 (의미 X)            │
│                                      │                                      │
│                                      │ COMMIT                               │
└──────────────────────────────────────┴──────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 기대했던 바는 트랜잭션 A에서 FOR UPDATE와 함께 락을 걸었기에 다른 트랜잭션 B에서는 커밋된 이후의 데이터인 USED가 조회되는 것을 예상했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 예상과는 달리 락을 획득하여 조회한 트랜잭션 B도 ACTIVE 상태를 가지는 쿠폰이 조회되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 영속성 컨텍스트 1차 캐시에서 엔티티가 조회되었기 때문입니다. 그렇게 쿠폰이 중복으로 사용되고 사용 기록도 2번 생성이 된 문제입니다.&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;p data-ke-size=&quot;size16&quot;&gt;만약, 처음 쿠폰 상태를 조회할 때 FOR UPDATE로 락을 걸었다면 트랜잭션 A에서 쿠폰 사용을 마치기 전까지는 B에서 사용을 못 했을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 쿠폰의 두 번째 조회에서 뒤늦게 락을 걸었기에 이전에 수행된 비즈니스 로직에서 여러 데이터는 변경되었을 것입니다. 예상했던 대로 쿠폰이 USED로 조회되었어도 충분히 다른 문제가 생길 수 있는 것이죠.&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;첫째로, 쿠폰을 조회할 때 처음에는 findById는 JPA의 Query Methods를 이용하였고 두 번째 조회에서는 JPQL + @Lock 어노테이션을 사용해 FOR UPDATE로 조회했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현한다면 두번째 조회에서는 JPQL로 작성되었기에 DB로 쿼리가 무조건 나가게 됩니다. @Lock 어노테이션도 있기에 FOR UPDATE 락도 함께 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, JPQL에서는 DB에서 조회한 결과가 있어도 영속성 컨텍스트에 엔티티가 이미 존재한다면 조회한 결과를 버리고 영속성 컨텍스트의 엔티티를 그대로 이용하게 됩니다.&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;size16&quot;&gt;&lt;b&gt;왜 DB의 조회 결과를 버리게 될까요?&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션 내에서 엔티티가 영속성 컨텍스트에 존재하는 것은 이미 조회가 된 상황입니다. 그리고 그 엔티티는 언제든 수정이 될 수 있고 트랜잭션이 진행중 이기에 아직 커밋도 되지 않은 상황입니다.&lt;br /&gt;&lt;br /&gt;이러한 상황에서 JPQL로 DB에서 조회한 내용을 영속성 컨텍스트에 덮어씌운다면 수정 중인 사항이 사라질 수 있습니다. 그래서 DB에서 조회했던 데이터를 폐기하고 영속성 컨텍스트의 엔티티를 사용하게 됩니다.&lt;br /&gt;JPA 영속성 컨텍스트의 구현 방식으로 인해 생긴 현상이라고 이해하시면 됩니다.&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;둘째로, 영속성 컨텍스트를 사용하지 않는 네이티브 쿼리로 구현했다면 트랜잭션 B에서 쿠폰을 두 번째 조회하는 시점에서의 상태는 USED일 것입니다. 하지만 이미 비즈니스 로직을 수행하면서 여러 데이터가 변경된 시점이기에 여전히 예상한 결과와는 달랐을 것입니다.&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;내용이 길고 복잡했는데요, 다시 정리해 보면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;처음 조회 시점부터 락을 거는 것이 근본적으로 문제를 해결하는 방법입니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JPA의 영속성 컨텍스트 동작으로 인해 JPQL로 가져온 결과가 폐기될 수 있습니다.&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만, 영속성 컨텍스트는 트랜잭션 내에서만 동작하고 예시처럼 조회를 두 번하는 로직은 상황은 사실 드뭅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;만약 네이티브 쿼리로 작성했다면 쿠폰의 사용 기록은 생성 안 되었겠지만, 비즈니스 로직 처리로 인해 데이터가 꼬였을 것입니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;영속성 컨텍스트의 REPETABLE READ 동작이 때로는 함정에 빠지게 할 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션과 격리 수준, JPA의 영속성 컨텍스트와 REPEATABLE 그리고 실무에서 발생할 수 있는 문제까지 모두 다루어 보았습니다. 진짜로 본질적인 부분까지 파보려니 꽤 헷갈리는 부분이 많았던 것 같습니다.&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;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;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;</description>
      <category>SW개발/Database</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/568</guid>
      <comments>https://leffept.tistory.com/568#entry568comment</comments>
      <pubDate>Tue, 5 Aug 2025 23:44:26 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL]트랜잭션의 격리 수준 파헤치기 1</title>
      <link>https://leffept.tistory.com/567</link>
      <description>&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;size16&quot;&gt;오늘은 트랜잭션의 격리 수준에 대해서 낱낱이 파헤쳐 보려고 합니다. MySQL의 트랜잭션 격리 수준에 따라서 어떠한 점들이 달라지게 되는지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션의 격리 수준이란?&lt;/h2&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;size16&quot;&gt;SQL-92 표준에 따르면 트랜잭션의 격리 수준은 Serializable, Repetable Reads, Read Comitted, Read Uncomitted 총 4가지의 레벨이 존재합니다. 각 레벨에 따라 특성들이 다르고 이로 인해 쿼리의 결과에도 영향을 미치게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 역시 마찬가지로 4가지의 레벨을 모두 지원합니다. (대부분의 상용 데이터베이스도 동일합니다.)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Read Uncomitted&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 낮은 격리 수준에 해당됩니다. 이 레벨에서는 SELECT 문은 락이 없는 방식으로 수행되고, 다른 트랜잭션에서 아직 커밋되지 않은 변경 사항을 읽을 수 있습니다. 그렇기에 Dirty Read 현상이 생기게 됩니다.&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;&lt;b&gt;Dirty Read&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 아직 커밋되지 않은 다른 트랙잭션에서 업데이트한 행을 읽어올 수 있습니다. 아래의 그림을 참고해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1753193289697&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────────────────────┬──────────────────────────────┐
│         트랜잭션 A            │             트랜잭션 B         │
├─────────────────────────────┼──────────────────────────────┤
│ BEGIN                       │                              │
│                             │ BEGIN                        │
│                             │                              │
│ SELECT name FROM User       │                              │
│ WHERE id = 1;               │                              │
│ → 결과: John                 │                              │
│                             │ UPDATE User                  │
│                             │ SET name = 'James'           │
│                             │ WHERE id = 1;                │
│                             │ (아직 COMMIT 하지 않음)         │
│                             │                              │
│ SELECT name FROM User       │                              │
│ WHERE id = 1;               │                              │
│ → 결과: James (Dirty Read)   │                              │
│                             │                              │
│                             │ ROLLBACK                     │
│                             │ (name은 다시 John으로 복원됨)    │
└─────────────────────────────┴──────────────────────────────┘&lt;/code&gt;&lt;/pre&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;size16&quot;&gt;가장 낮은 레벨이기에 Dirty Read 이외에도 Non-Repeatable Read, Phantom Read 현상도 같이 발생합니다. 이 부분은 아래에서 하나씩 다루겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Read Comitted&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Uncomitted 보다 한 단계 높은 격리 수준입니다. 이 레벨에서는 SELECT 문은 락이 없는 방식으로 수행되지만, 다른 트랜잭션에서 커밋된 변경 사항만 읽을 수 있습니다. 그렇기에 Dirty Read 현상은 발생하지 않습니다.&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;Non-Repetable Read&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋된 이후의 데이터만 조회하게 되었지만 하나의 트랜잭션에서 동일 쿼리를 여러 번 수행할 때 일관된 읽기가 불가능합니다. 예시와 함께 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753619625818&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────────────────────┬──────────────────────────────┐
│         트랜잭션 A            │             트랜잭션 B         │
├─────────────────────────────┼──────────────────────────────┤
│ BEGIN                       │                              │
│                             │ BEGIN                        │
│                             │                              │
│ SELECT name FROM User       │                              │
│ WHERE id = 1;               │                              │
│ → 결과: John                 │                              │
│                             │                              │
│                             │ UPDATE User                  │
│                             │ SET name = 'James'           │
│                             │ WHERE id = 1;                │
│                             │ COMMIT                       │
│                             │                              │
│ SELECT name FROM User       │                              │
│ WHERE id = 1;               │                              │
│ → 결과: James,               │                              │
│ (Non-Repeatable Read)       │                              │
└─────────────────────────────┴──────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락이 없는 SELECT 문의 경우 하나의 트랜잭션 내에서 반복된 읽기에 대해서는 보장해주지 못합니다. 그렇기 때문에 같은 조건의 쿼리를 여러 번 조회한다면 데이터가 변경될 수도 있는 현상을 겪게 됩니다.&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;또한, Phantom Read 현상도 다음과 같은 이유로 인해 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 레벨에서의 읽기 락(SELECT with FOR UPDATE / FOR SHARE)과 UPDATE, DELETE 문의 경우 InnoDB는 해당 레코드만 락을 걸고, 간격을 제한하는 갭 락은 걸지 않습니다. 그렇기 때문에 락 걸린 레코드 옆의 공간에 새 레코드는 자유롭게 삽입이 가능합니다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;갭 락이란?&lt;/b&gt;&lt;br&gt;갭 락은 MySQL의 InnoDB 스토리지 엔진에서 사용되는 잠금 방식입니다.&lt;br&gt;레코드와 레코드 사이의 공간(간격)을 잠궈 해당 간격에 새로운 레코드가 삽입되는 것을 방지하는 역할을 합니다.&amp;nbsp;&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;레코드 사이의 공간에 새로운 레코드 삽입이 가능하기에 Phantom Read 현상이 발생합니다.&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;Phantom Read&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 내에서 동일한 쿼리가 서로 다른 시점에 서로 다른 레코드 집합을 생성할 때 발생합니다. 예를 들어, SELECT 문이 두 번 실행되었을 때 첫 번째 실행 시에 반환되지 않았던 레코드를 반환하는 경우입니다. 아래의 그림을 참고해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1753193457956&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌────────────────────────────────────────┬────────────────────────────────────────┐
│              트랜잭션 A                  │               트랜잭션 B                 │
├────────────────────────────────────────┼────────────────────────────────────────┤
│ BEGIN                                  │                                        │
│                                        │ BEGIN                                  │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31, 50                          │                                        │
│ (갭 락 X, 레코드 락 O)                    │                                        │
│                                        │ INSERT INTO User(name, age)            │
│                                        │ VALUES ('Alice', 35);                  │
│                                        │ COMMIT                                 │
│                                        │ (갭 락이 없으므로 공간 사이에 레코드 삽입 가능   │
│                                        │                                        │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31, 35(Phantom), 50             │                                        │
└────────────────────────────────────────┴────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 A에서 SELECT FOR UPDATE 쿼리의 결과로 31, 50 레코드를 읽었습니다. 이 시점에서 갭 락은 걸려있지 않은 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 트랜잭션 B에서 갭 락이 걸려 있지 않으므로 공간 사이에 새로운 레코드를 삽입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 트랜잭션 A에서 SELECT FOR UPDATE 쿼리로 범위 검색을 진행하면, 동일한 트랜잭션이지만 갑자기 새로운 레코드가 조회되는 현상이 발생합니다.&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;FOR UPDATE가 없는 SELECT도 갭 락이 걸리지 않기에 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Repetable Reads&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Comitted 보다 한 단계 더 높은 격리 수준입니다. 이 레벨에서는 동일 트랜잭션 내에서 첫 번째 읽기에서 생성된 스냅샷을 계속 읽습니다. 즉, 동일 트랜잭션이라면 잠금 없는 SELECT 명령문을 계속 수행하더라도 일관된 읽기가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, MySQL 격리 수준의 기본 설정입니다.&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;이 레벨에서는 MySQL은 대부분의 상황에서 Phantom Read가 발생하지 않습니다. 하지만 특정 상황에서는 발생할 수 있습니다. 한번 알아보도록 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753274614501&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌────────────────────────────────────────┬────────────────────────────────────────┐
│              트랜잭션 A                  │               트랜잭션 B                 │
├────────────────────────────────────────┼────────────────────────────────────────┤
│ BEGIN                                  │                                        │
│                                        │ BEGIN                                  │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30;                        │                                        │
│ → 결과: 31, 50                          │                                        │
│ (Undo 로그에서 조회, MVCC)                │                                        │
│                                        │                                        │
│                                        │ INSERT INTO User(name, age)            │
│                                        │ VALUES ('Alice', 35);                  │
│                                        │ COMMIT                                 │
│                                        │                                        │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31, 35(Phantom), 50             │                                        │
│ (데이터베이스의 최근 상태를 사용하여 조회/락)    │                                        │
└────────────────────────────────────────┴────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락을 사용하지 않은 SELECT문 뒤에 락을 사용한 SELECT문의 순서로 혼합해 사용하는 경우에 Phantom Read가 발생할 수 있습니다.&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;락을 사용하지 않은 조회는 Undo 로그에서 데이터를 읽어오게 되고, 트랜잭션 B에서 데이터가 변동이 된 후에 락을 사용한 조회를 하면 데이터베이스의 가장 최근 상태에서 데이터를 조회하면서 락을 하게 됩니다. 이 상황에서 데이터의 상태가 일치하지 않기 때문에 발생하는 것입니다.&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;MySQL 공식 문서에서도 Repeatable Reads 레벨의 단일 트랜잭션에서는 락 없는 조회와 락 있는 조회의 혼합해서 사용하는 것을 권장하지 않고 있습니다. 실제로 애플리케이션 로직에서도 이렇게 작성했던 경험은 별로 없었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Undo 로그란?&lt;/b&gt;&lt;br&gt;Undo 로그는 데이터베이스 트랜잭션에서 변경 전의 데이터를 저장하는 로그입니다.&lt;br&gt;트랜잭션이 롤백(rollback)될 때, 즉 작업을 취소하고 이전 상태로 되돌릴 때 사용됩니다. 데이터베이스 변경 사항을 기록하는 Redo 로그와 반대되는 개념입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;MVCC 란? (Multi-Version Concurrency Control)&lt;br&gt;&lt;/b&gt;MVCC(다중 버전 동시성 제어)는 데이터베이스에서 동시성을 관리하기 위한 방법 중 하나입니다. 하나의 데이터 레코드에 대해 여러 버전의 데이터를 유지하여 여러 사용자가 동시에 데이터에 접근하더라도 충돌 없이 작업을 수행할 수 있도록 합니다.&amp;nbsp;&lt;br&gt;즉, 데이터베이스 사용자들이 데이터를 읽을 때 다른 사용자의 변경 작업에 영향을 받지 않고, 일관된 데이터를 볼 수 있도록 보장합니다.&amp;nbsp;&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;위에서 설명한 케이스는 다른 데이터베이스와 MySQL의 InnoDB가 모두 Phantom Read 현상이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 InnoDB의 갭 락으로 인해 타 엔진들과 다른 결과를 보여주는 케이스가 있습니다. 예시와 함께 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753276155798&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MySQL InnoDB
┌────────────────────────────────────────┬────────────────────────────────────────┐
│              트랜잭션 A                  │               트랜잭션 B                 │
├────────────────────────────────────────┼────────────────────────────────────────┤
│ BEGIN                                  │                                        │
│                                        │ BEGIN                                  │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31, 50                          │                                        │
│ (넥스트 키 락)                            │                                        │
│                                        │                                        │
│                                        │ INSERT INTO User(name, age)            │
│                                        │ VALUES ('Alice', 35);                  │
│                                        │ (락 획득 대기)                            │
│                                        │                                        │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31,50                           │                                        │
│ COMMIT                                 │                                        │
│                                        │ COMMIT                                 │
└────────────────────────────────────────┴────────────────────────────────────────┘

타 데이터베이스
┌────────────────────────────────────────┬────────────────────────────────────────┐
│              트랜잭션 A                  │               트랜잭션 B                 │
├────────────────────────────────────────┼────────────────────────────────────────┤
│ BEGIN                                  │                                        │
│                                        │ BEGIN                                  │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31, 50                          │                                        │
│ (갭 락이 없으므로 레코드 락만 걸림)            │                                        │
│                                        │                                        │
│                                        │ INSERT INTO User(name, age)            │
│                                        │ VALUES ('Alice', 35);                  │
│                                        │ COMMIT, 레코드 사이에 삽입 가능              │
│                                        │                                        │
│ SELECT * FROM User                     │                                        │
│ WHERE age &amp;gt; 30 FOR UPDATE;             │                                        │
│ → 결과: 31, 35(Phantom), 50             │                                        │
│ COMMIT                                 │                                        │
└────────────────────────────────────────┴────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에는 MySQL은 넥스트 키 락(레코드 락 + 갭 락)으로 인해 유령 데이터가 생성되지 않습니다. 하지만 다른 데이터베이스의 경우에는 레코드 사이의 간격을 잠글 수 없기에 Phantom Read 현상이 발생하게 됩니다.&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;이렇게만 보면 MySQL이 다른 데이터베이스 엔진에 비해 좋은 것처럼 보일 수 있습니다. 하지만 격리 수준이 높다는 것은 데이터의 격리성은 올라가지만 반대로 삽입 지연, 동시성 저하, 데드락 같은 문제가 발생할 가능성이 높아지게 된다는 뜻과도 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;b&gt;넥스트 키 락이란?&lt;br&gt;&lt;/b&gt;&lt;/b&gt;넥스트 키 락은 MySQL의 InnoDB 스토리지 엔진에서 사용되는 락 메커니즘 중 하나입니다.&lt;br&gt;레코드 락과 갭 락을 합쳐놓은 형태로, 특정 레코드뿐만 아니라 해당 레코드와 다음 레코드 사이의 간격까지 잠금으로써 Phantom Read 현상을 방지하는 데 사용됩니다.&lt;b&gt;&lt;b&gt;&lt;br&gt;&lt;/b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Serializable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 높은 격리 수준입니다. 이 레벨에서는 모든 트랜잭션은 순차적으로 실행됩니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락이 없는 SELECT 문의 경우에도 SELECT ~ FOR SHARE를 통해 읽기 락을 걸어 조회하게 됩니다. 따라서 여러 트랜잭션이 동일한 레코드에 대해 동시에 접근할 수 없어 순차성을 보장받을 수 있습니다. 위에서 언급한 여러 현상 또한 발생하지 않습니다.&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;(CockroachDB는 기본 격리 수준이 Serializable 입니다. 이 데이터베이스 엔진에서는 격리 수준을 지키면서도 성능 문제를 어떻게 해결했는지 알아보셔도 좋을 것 같습니다.)&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;최종적으로 정리하자면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Read Uncomitted : Dirty Read + Non-Repeatable Read + Phantom Read 현상&lt;/li&gt;
&lt;li&gt;Read Comitted : Non-Repeatable Read + Phantom Read 현상&lt;/li&gt;
&lt;li&gt;Repetable Reads : Phantom Read (일부 상황) 현상&lt;/li&gt;
&lt;li&gt;Serializable : 현상 X&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무에서는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL, PostgreSQL을 사용했던 저의 경험으로는 기본 격리 수준을 따로 변경한 적은 없었던 것 같습니다. 실제로 동시성과 순차성에 대한 제어가 필요한 경우에는 상황에 맞게 FOR UPDATE나 UPDATE 쿼리, 낙관적 락, Redis 분산락을 이용하는 것만으로도 충분했습니다.&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;그렇지만 동시성과 관련해 다양한 문제가 발생하는 상황에서 트랜잭션 격리 수준에 대한 이해 자체는 매우 중요합니다. 특히 요즈음에는 분산 환경에서 서버, DB가 운용되는 경우가 매우 일반적이고 이러한 환경에서 발생하는 문제들은 매우 다양합니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 트랜잭션의 격리 수준과 그에 따른 현상에 대해서 알아보았습니다. 다음 편에는 JPA 환경에서 트랜잭션의 격리 수준은 또 어떻게 적용되는지에 대해 알아보겠습니다.&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;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;h4 data-ke-size=&quot;size20&quot;&gt;레퍼런스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_read-committed&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_read-committed&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753280211844&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;MySQL :: MySQL 8.4 Reference Manual :: 17.7.2.1 Transaction Isolation Levels&quot; data-og-description=&quot;17.7.2.1&amp;nbsp;Transaction Isolation Levels Transaction isolation is one of the foundations of database processing. Isolation is the I in the acronym ACID; the isolation level is the setting that fine-tunes the balance between performance and reliability, consi&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_read-committed&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_read-committed&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_read-committed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_read-committed&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;MySQL :: MySQL 8.4 Reference Manual :: 17.7.2.1 Transaction Isolation Levels&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;17.7.2.1&amp;nbsp;Transaction Isolation Levels Transaction isolation is one of the foundations of database processing. Isolation is the I in the acronym ACID; the isolation level is the setting that fine-tunes the balance between performance and reliability, consi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.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>SW개발/Database</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/567</guid>
      <comments>https://leffept.tistory.com/567#entry567comment</comments>
      <pubDate>Thu, 24 Jul 2025 00:24:48 +0900</pubDate>
    </item>
    <item>
      <title>2024년 회고 (feat. 바빠도 너무 바빠)</title>
      <link>https://leffept.tistory.com/566</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 이번에도 어김없이 2024년도의 회고를 진행해 보려고 합니다. 최근 블로그에 글을 자주 쓰고 있지는 못했는데요, 지금까지의 일들을 돌아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이직&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자바와 스프링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년도 회고를 진행하면서 마지막쯤에 좋은 소식이 있을 것이라는 내용을 적었었는데요, 그 결과로 새로운 회사로 이직하게 되었습니다! 또, 새로운 도전과도 같은 자바 + 스프링 환경에 입문하게 되었습니다.&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;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;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;size16&quot;&gt;기술 스택을 전환해보니 예전부터 느꼈던 것이지만 역시나 웹, 백엔드, CS 등 근본적인 개념들이 정말 중요했습니다. 특히, 대부분은 Django, FastAPI에서 경험해 본 개념들이었기에 스프링의 새로운 키워드에 대한 이해도 빠르게 할 수 있었습니다.&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;가장 크게 와닿았던 차이점은 &quot;객체&quot; 개념을 굉장히 대놓고 강조하는 것입니다. 파이썬 진영에서도 객체와 의존성 등 동일한 개념들이 존재하지만 자바 + 스프링 진영에서는 더욱 드러나 있습니다.&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;프링이 탄생한 배경도 EJB라는 복잡한 엔터프라이즈 환경에서 벗어나고자 하는 해결책으로 만들었던 예제 코드들이 조금 더 다듬어진 형태가 되어 스프링이 된 것입니다. 그 근간에는 DI 컨테이너가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술 블로그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전부터 기술 블로그를 운영해왔던 사람으로서 사내의 기술 블로그에 글을 작성해보고자 하는 욕망이 있었습니다. 마침, 재직 중인 회사도 기술 블로그를 운영 중이었고 진행했던 &quot;정산 시스템 이관&quot;이라는 주제를 가지고 글을 작성하게 되었습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leffept.tistory.com/565&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leffept.tistory.com/565&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1736773879350&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;포스타입 정산 시스템 이관기&quot; data-og-description=&quot; 안녕하세요, 오랜만에 블로그에 글을 작성하게 되었습니다.&amp;nbsp;기술 블로그를 운영하면서부터 조금 더 공식적인 자리에서 기술적인 글을 작성해 보고 싶다는 생각이 많았습니다.최근에 좋은 기&quot; data-og-host=&quot;leffept.tistory.com&quot; data-og-source-url=&quot;https://leffept.tistory.com/565&quot; data-og-url=&quot;https://leffept.tistory.com/565&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Odceq/hyX0pgei5N/u9VetfWKc8SkbK0kuukii0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fBNRG/hyX0kss6iT/4xFSJUyTRD1xJhGvMsOyh1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://leffept.tistory.com/565&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leffept.tistory.com/565&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Odceq/hyX0pgei5N/u9VetfWKc8SkbK0kuukii0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fBNRG/hyX0kss6iT/4xFSJUyTRD1xJhGvMsOyh1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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; 안녕하세요, 오랜만에 블로그에 글을 작성하게 되었습니다.&amp;nbsp;기술 블로그를 운영하면서부터 조금 더 공식적인 자리에서 기술적인 글을 작성해 보고 싶다는 생각이 많았습니다.최근에 좋은 기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leffept.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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이콘 오거나이저&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이직과 거의 동시에 진행하게 된 활동이 하나 있습니다. 23년도에는 파이콘에서 스피커였다면, 24년도에는 행사를 만들어가는 오거나이저로 활동을 하게 되었습니다.&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;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;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;size16&quot;&gt;그래서인지 중간 즈음에는 많은 리소스 투입으로 인해 힘들었던 시기도 있었습니다. 하지만 막상 행사 당일날 오프닝 세션의 웅장함, 행사의 참여자들과 스피커들의 네트워킹 등을 보면서 열정에 대한 에너지를 다시 얻을 수 있었던 것 같습니다. 끝내고 보니 많이 뿌듯한 것들이 있었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멘토링&lt;/h2&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;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;size16&quot;&gt;현재도, 특정 코스를 수강하는 수강생들을 대상으로 멘토링을 진행하고 있습니다. 팀 &amp;amp; 개인 멘토링을 진행하면서 기술 질의응답, 커리어 고민, 학습 방법에 대해서 다양한 이야기들을 나누고 있습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며, 2025년 계획&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년도에는 뭔가 동시 다발적으로 벌여놓은 일이 많아서 바쁘게 느껴졌던 것 같습니다.&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;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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다가오는 25년도에도 크게 다른 목표가 있지는 않습니다.&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;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;size16&quot;&gt;셋째로, 개발자로서 더 넓은 시야와 생산성을 갖추려고 할 것 같습니다. AI가 많이 발전한 만큼 더 많은 생산성을 빠르게 끌어올릴 수 있다고 보고 있습니다. 사람이 잘하는 영역, AI가 잘하는 영역에 대해서 잘 분리해보려고 할 것 같습니다. 그렇게 확보한 시간만큼 더 넓은 시야와 다른 부분에 대한 성장을 갖출 수 있을 것 같습니다.&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;긴 글 읽어주셔서 감사합니다 :)&lt;/p&gt;</description>
      <category>회고</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/566</guid>
      <comments>https://leffept.tistory.com/566#entry566comment</comments>
      <pubDate>Mon, 13 Jan 2025 22:44:50 +0900</pubDate>
    </item>
    <item>
      <title>포스타입 정산 시스템 이관기</title>
      <link>https://leffept.tistory.com/565</link>
      <description>&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;size16&quot;&gt;기술 블로그를 운영하면서부터 조금 더 공식적인 자리에서 기술적인 글을 작성해 보고 싶다는 생각이 많았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 좋은 기회가 되어서 &quot;정산 시스템 이관기&quot; 라는 주제로 사내 팀 블로그에 글을 작성하게 되어 해당 내용을 공유해봅니다.&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;1편에서는 이관 배경, 문제 정의, 기술 선택 과정, 아키텍처 설계 내용, &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2편에서는 테크 스펙과, 실제 구현, 트러블 슈팅을 자세하게 담았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;많은 관심 부탁드립니다!!&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;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;[1편 보러 가기 ]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0070d1; text-align: left;&quot;&gt;&lt;a href=&quot;https://www.postype.com/@team/post/18187628&quot;&gt;https://www.postype.com/@team/post/18187628&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734533404862&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;포스타입 정산 시스템 이관기 1: 포스타입 팀&quot; data-og-description=&quot;안녕하세요! 포스타입 기술팀의 백엔드 엔지니어 윤준기예요. 포스타입에서는 수많은 크리에이터들이 수익을 얻고 있고 매월 수익을 출금하여 입금을 받고 있어요. 기술팀 내부에서는 해당 기&quot; data-og-host=&quot;www.postype.com&quot; data-og-source-url=&quot;https://www.postype.com/@team/post/18187628&quot; data-og-url=&quot;https://www.postype.com/@team/post/18187628&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cUl1ud/hyXOrydcqz/HABLc8pkvlxX0sC3Y1dgc0/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/dWc1iN/hyXOi87nWm/W6jUXwOOh4Q1w8BrqKGTNK/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/g9BVE/hyXOfq0tqy/Ih2Xmms8BlMFTnQ7bRGN9K/img.png?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250&quot;&gt;&lt;a href=&quot;https://www.postype.com/@team/post/18187628&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.postype.com/@team/post/18187628&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cUl1ud/hyXOrydcqz/HABLc8pkvlxX0sC3Y1dgc0/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/dWc1iN/hyXOi87nWm/W6jUXwOOh4Q1w8BrqKGTNK/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/g9BVE/hyXOfq0tqy/Ih2Xmms8BlMFTnQ7bRGN9K/img.png?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250');&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;포스타입 정산 시스템 이관기 1: 포스타입 팀&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.postype.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;[2편 보러 가기 ]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt; &lt;a href=&quot;https://www.postype.com/@team/post/18200609&quot;&gt;https://www.postype.com/@team/post/18200609&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735219336049&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;포스타입 정산 시스템 이관기 2: 포스타입 팀&quot; data-og-description=&quot;안녕하세요! 포스타입 기술팀의 백엔드 엔지니어 윤준기예요. 지난 정산 시스템 이관기 1편에 이어서 2편으로 또 찾아오게 되었어요! 2편에서는 실제로 정산 작업을 이관한 과정과 트러블 슈팅&quot; data-og-host=&quot;www.postype.com&quot; data-og-source-url=&quot;https://www.postype.com/@team/post/18200609&quot; data-og-url=&quot;https://www.postype.com/@team/post/18200609&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GsWPi/hyXSBAKRWe/TM5k7RmtizslXTHHKKRzN0/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/cliUDn/hyXSzJH1Y0/VeWflBYLdsCrrP6kzk9Ay0/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/bCR1AT/hyXSCzFacX/YJRzOkwyaXedc8NpKd5bD1/img.png?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250&quot;&gt;&lt;a href=&quot;https://www.postype.com/@team/post/18200609&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.postype.com/@team/post/18200609&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GsWPi/hyXSBAKRWe/TM5k7RmtizslXTHHKKRzN0/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/cliUDn/hyXSzJH1Y0/VeWflBYLdsCrrP6kzk9Ay0/img.png?width=650&amp;amp;height=325&amp;amp;face=0_0_650_325,https://scrap.kakaocdn.net/dn/bCR1AT/hyXSCzFacX/YJRzOkwyaXedc8NpKd5bD1/img.png?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250');&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;포스타입 정산 시스템 이관기 2: 포스타입 팀&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요! 포스타입 기술팀의 백엔드 엔지니어 윤준기예요. 지난 정산 시스템 이관기 1편에 이어서 2편으로 또 찾아오게 되었어요! 2편에서는 실제로 정산 작업을 이관한 과정과 트러블 슈팅&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.postype.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;이 글은 포스타입의 팀 블로그에서 발행된 글입니다.&lt;/i&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>SW개발/개발이야기</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/565</guid>
      <comments>https://leffept.tistory.com/565#entry565comment</comments>
      <pubDate>Wed, 18 Dec 2024 23:50:55 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle]그래들 알아보기 10 - Build Scans</title>
      <link>https://leffept.tistory.com/561</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/build_scans.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/build_scans.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717923149770&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;Build Scans&quot; data-og-description=&quot;Gradle captures your build metadata and sends it to the Build Scan Service. The service then transforms the metadata into information you can analyze and share with others. The information that scans collect can be an invaluable resource when troubleshooti&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/build_scans.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/build_scans.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dZgtpI/hyWlaxfQto/ilK98K9CILv3CXHO8SGRSK/img.png?width=2582&amp;amp;height=1682&amp;amp;face=0_0_2582_1682,https://scrap.kakaocdn.net/dn/gaMNg/hyWlgdaroI/pkPXCXEs4CVW75mKzcMvGK/img.png?width=2582&amp;amp;height=1572&amp;amp;face=0_0_2582_1572,https://scrap.kakaocdn.net/dn/b2sjgO/hyWgXsPLI6/JzAwmTxIKJDTpVSvWqfJnK/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/build_scans.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/build_scans.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dZgtpI/hyWlaxfQto/ilK98K9CILv3CXHO8SGRSK/img.png?width=2582&amp;amp;height=1682&amp;amp;face=0_0_2582_1682,https://scrap.kakaocdn.net/dn/gaMNg/hyWlgdaroI/pkPXCXEs4CVW75mKzcMvGK/img.png?width=2582&amp;amp;height=1572&amp;amp;face=0_0_2582_1572,https://scrap.kakaocdn.net/dn/b2sjgO/hyWgXsPLI6/JzAwmTxIKJDTpVSvWqfJnK/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200');&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;Build Scans&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Gradle captures your build metadata and sends it to the Build Scan Service. The service then transforms the metadata into information you can analyze and share with others. The information that scans collect can be an invaluable resource when troubleshooti&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&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;h2 data-ke-size=&quot;size26&quot;&gt;Build Scans&lt;/h2&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wURJO/btsHTm0nE3f/RWLxKSrZhARdvgKKP9MLT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wURJO/btsHTm0nE3f/RWLxKSrZhARdvgKKP9MLT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wURJO/btsHTm0nE3f/RWLxKSrZhARdvgKKP9MLT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwURJO%2FbtsHTm0nE3f%2FRWLxKSrZhARdvgKKP9MLT0%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;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&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;그래들은 Build Scan Service를 사용해서 빌드 메타 데이터를 캡쳐합니다. 그런 다음 서비스는 메타데이터를 분석하고 다른 사람과 공유할 수 있는 정보로 변환합니다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkrwEJ/btsHSWHQEl9/0V6w4Qq5a4NaG9JzDpeBkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkrwEJ/btsHSWHQEl9/0V6w4Qq5a4NaG9JzDpeBkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkrwEJ/btsHSWHQEl9/0V6w4Qq5a4NaG9JzDpeBkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkrwEJ%2FbtsHSWHQEl9%2F0V6w4Qq5a4NaG9JzDpeBkk%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;1280&quot; height=&quot;833&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;size16&quot;&gt;예를 들어, 빌드 스캔을 사용하면 해당 빌드 스캔에 대한 링크를 복사 및 공유함으로써 더 이상 일일히 오류 메시지를 복사하거나 붙여 넣을 필요가 없어집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LZR4J/btsHRZevpGd/H9g8tV5QpRbEA04LAUT1Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LZR4J/btsHRZevpGd/H9g8tV5QpRbEA04LAUT1Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LZR4J/btsHRZevpGd/H9g8tV5QpRbEA04LAUT1Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLZR4J%2FbtsHRZevpGd%2FH9g8tV5QpRbEA04LAUT1Kk%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;1280&quot; height=&quot;779&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;779&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;h2 data-ke-size=&quot;size26&quot;&gt;Enable Build Scans&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 스캔을 활성하기 위한 그래들 명령어는 --scan 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717923437022&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ./gradlew build --scan&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;</description>
      <category>SW개발/Spring</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/561</guid>
      <comments>https://leffept.tistory.com/561#entry561comment</comments>
      <pubDate>Sat, 15 Jun 2024 15:57:38 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle]그래들 알아보기 9 - Gradle Incremental Builds and Build Caching</title>
      <link>https://leffept.tistory.com/560</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/gradle_optimizations.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/gradle_optimizations.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717918220889&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;Gradle Incremental Builds and Build Caching&quot; data-og-description=&quot;An incremental build is a build that avoids running tasks whose inputs have not changed since the previous build. Re-executing such tasks is unnecessary if they would only re-produce the same output. For incremental builds to work, tasks must define their &quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/gradle_optimizations.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/gradle_optimizations.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Lb8qT/hyWgUXc8rb/jHsarskakOrk0reU0bLNd0/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/gradle_optimizations.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/gradle_optimizations.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Lb8qT/hyWgUXc8rb/jHsarskakOrk0reU0bLNd0/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200');&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;Gradle Incremental Builds and Build Caching&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An incremental build is a build that avoids running tasks whose inputs have not changed since the previous build. Re-executing such tasks is unnecessary if they would only re-produce the same output. For incremental builds to work, tasks must define their&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&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;h2 data-ke-size=&quot;size26&quot;&gt;Gradle&amp;nbsp;Incremental&amp;nbsp;Builds&amp;nbsp;and&amp;nbsp;Build&amp;nbsp;Caching&lt;/h2&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GyeJZ/btsHScEzCwj/aBrv0xJ4kdmYs1yIuPqd1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GyeJZ/btsHScEzCwj/aBrv0xJ4kdmYs1yIuPqd1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GyeJZ/btsHScEzCwj/aBrv0xJ4kdmYs1yIuPqd1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGyeJZ%2FbtsHScEzCwj%2FaBrv0xJ4kdmYs1yIuPqd1K%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;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&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;h2 data-ke-size=&quot;size26&quot;&gt;Incremental builds&lt;/h2&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;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;빌드는 항상 활성화 되어 있으며, 이를 확인할 수 있는 가장 좋은 방법은 verbose 모드를 활성화 하는 것입니다. verbose 모드에서는 각각의 태스크가 빌드 동안에 라벨링 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717922043872&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew compileJava --console=verbose

&amp;gt; Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
&amp;gt; Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
&amp;gt; Task :buildSrc:compilePluginsBlocks UP-TO-DATE
&amp;gt; Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
&amp;gt; Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
&amp;gt; Task :buildSrc:compileKotlin UP-TO-DATE
&amp;gt; Task :buildSrc:compileJava NO-SOURCE
&amp;gt; Task :buildSrc:compileGroovy NO-SOURCE
&amp;gt; Task :buildSrc:pluginDescriptors UP-TO-DATE
&amp;gt; Task :buildSrc:processResources UP-TO-DATE
&amp;gt; Task :buildSrc:classes UP-TO-DATE
&amp;gt; Task :buildSrc:jar UP-TO-DATE
&amp;gt; Task :list:compileJava UP-TO-DATE
&amp;gt; Task :utilities:compileJava UP-TO-DATE
&amp;gt; Task :app:compileJava UP-TO-DATE

BUILD SUCCESSFUL in 374ms
12 actionable tasks: 12 up-to-date&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태스크를 실행할 때 이전에 실행되었고 변경 사항이 없다면 UP-TO-DATE가 태스크 오른쪽에 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Build caching&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;size16&quot;&gt;그러나, 같은 개발자가 지난주에 생성된 새 브랜치로 전환하면 어떻게 될까요? 개발자가 이전에 빌드된 항목을 빌드하는 경우에도 파일이 다시 빌드됩니다.&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;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;size16&quot;&gt;빌드 캐시를 사용하여 로컬 디렉토리를 다시 채우면 태스크 오른쪽에 FROM-CACHE로 마킹됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717922653120&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew compileJava --build-cache

&amp;gt; Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
&amp;gt; Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
&amp;gt; Task :buildSrc:compilePluginsBlocks UP-TO-DATE
&amp;gt; Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
&amp;gt; Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
&amp;gt; Task :buildSrc:compileKotlin UP-TO-DATE
&amp;gt; Task :buildSrc:compileJava NO-SOURCE
&amp;gt; Task :buildSrc:compileGroovy NO-SOURCE
&amp;gt; Task :buildSrc:pluginDescriptors UP-TO-DATE
&amp;gt; Task :buildSrc:processResources UP-TO-DATE
&amp;gt; Task :buildSrc:classes UP-TO-DATE
&amp;gt; Task :buildSrc:jar UP-TO-DATE
&amp;gt; Task :list:compileJava FROM-CACHE
&amp;gt; Task :utilities:compileJava FROM-CACHE
&amp;gt; Task :app:compileJava FROM-CACHE

BUILD SUCCESSFUL in 364ms
12 actionable tasks: 3 from cache, 9 up-to-date&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 디렉토리가 한번 채워지면, 다음 실행은 FROM-CACHE가 아닌 UP-TO-DATE로 표시됩니다.&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;빌드 캐시를 사용하면 팀 전체에서 변경되지 않은 빌드 및 테스트 출력을 공유하고 재사용할 수 있습니다. 새로운 코드 변경에 영향을 받지 않는 바이너리를 다시 빌드하는데 사이클이 낭비되지 않아 로컬 및 CI 빌드 속도가 빨라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SW개발/Spring</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/560</guid>
      <comments>https://leffept.tistory.com/560#entry560comment</comments>
      <pubDate>Fri, 14 Jun 2024 21:49:16 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle]그래들 알아보기 8 - Plugin Basics</title>
      <link>https://leffept.tistory.com/559</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/plugin_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/plugin_basics.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717915897512&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;Plugin Basics&quot; data-og-description=&quot;Gradle is built on a plugin system. Gradle itself is primarily composed of infrastructure, such as a sophisticated dependency resolution engine. The rest of its functionality comes from plugins. A plugin is a piece of software that provides additional func&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/plugin_basics.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/plugin_basics.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lmOGM/hyWg4Mdm9s/KZkbibzeVwhkJv32TiOXfK/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/plugin_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/plugin_basics.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lmOGM/hyWg4Mdm9s/KZkbibzeVwhkJv32TiOXfK/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200');&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;Plugin Basics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Gradle is built on a plugin system. Gradle itself is primarily composed of infrastructure, such as a sophisticated dependency resolution engine. The rest of its functionality comes from plugins. A plugin is a piece of software that provides additional func&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&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;h2 data-ke-size=&quot;size26&quot;&gt;Plugin Basics&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래들은 플러그인 시스템을 기반으로 구축되었습니다. 그래들 자체는 매우 복잡한 의존성 해결 엔진과 같은 인프라로 구성되어 있습니다. 나머지 기능은 전부 플러그인에서 나옵니다.&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAFys/btsHTV2qyUr/BTopVsTWs9PfK7KsEKf8tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAFys/btsHTV2qyUr/BTopVsTWs9PfK7KsEKf8tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAFys/btsHTV2qyUr/BTopVsTWs9PfK7KsEKf8tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAFys%2FbtsHTV2qyUr%2FBTopVsTWs9PfK7KsEKf8tK%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;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&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;플러그인은 그래들 빌드 스크립트에 적용하여 새로운 태스크나 설정을 추가하거나 기타 빌드 관련 기능을 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;The Java Library Plugin - (java-library)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 라이브러리를 빌드하고 정의하는데 사용됩니다. compileJava 태스크 로 자바 코드를 컴파일하고, javadoc 태스크 로 Javadoc을 생성하고, jar 태스크로 JAR 파일을 컴파일된 클래스로 패키징합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;The Google Service Gradle Plugin - (com.google.gms:google-services)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;googleServices{} 설정 블록과 generateReleaseAssets 태스크를 사용하여 안드로이드 애플리케이션에 Google API와 파이어베이스 서비스를 활성화합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;The Gradle Bintray Plugin - (com.jfrom.bintray)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bintray{} 설정 블록을 사용해서 Bintray에 아티펙트를 게시할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Plugin distribution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 3가지 방법으로 배포됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;코어 플러그인 &lt;/b&gt;- 그래들은 코어 플러그인 세트를 개발하고 유지보수합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커뮤니티 플러그인 &lt;/b&gt;- 그래들 커뮤니티는 &lt;a href=&quot;https://plugins.gradle.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gradle Plugin Portal&lt;/a&gt;를 통해 플러그인을 공유합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로컬 플러그인 &lt;/b&gt;- 그래들은 API를 사용하여 커스텀 사용자 정의 플러그인을 만들 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Applying plugins&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 플러그인을 적용하면 플러그인이 프로젝트의 기능을 확장할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;plugin id(글로벌 유니크한 식별자 혹은 이름)를 사용하여 플러그인을 적용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717916948294&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id &amp;laquo;plugin id&amp;raquo; version &amp;laquo;plugin version&amp;raquo;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 코어 플러그인&lt;/h3&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;size16&quot;&gt;핵심 플러그인의 몇가지 예시입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;java :&amp;nbsp;&lt;/b&gt;자바 프로젝트를 빌드하는데 도움을 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;groovy : &lt;/b&gt;Groovy 소스 파일을 컴파일하고 테스트하는데 도움을 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ear : &lt;/b&gt;엔터프라이즈 애플리케이션에서 EAR 파일을 빌드하는데 도움을 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코어 플러그인은 빌드 스크립트에 적용될 때 java 혹은 JavaPlugin과 같이 짧은 이름을 제공한다는 점에서 독특합니다. 또한, 버전이 필요하지 않습니다.&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;java 플러그인 적용 예시 - build.gradle.kts&lt;/p&gt;
&lt;pre id=&quot;code_1717917219456&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id(&quot;java&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 커뮤니티 플러그인&lt;/h3&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;size16&quot;&gt;Spring Boot Gradle plugin 패키지는 실행 가능한 JAR나 WAR 아카이브 파일을 패키징하고, Spring Boot Java 애플리케이션을 실행시킵니다.&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;org.springframework.boot 플러그인 적용 예시 - build.gradle.kts&lt;/p&gt;
&lt;pre id=&quot;code_1717917504905&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id(&quot;org.springframework.boot&quot;) version &quot;3.1.5&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니티 플러그인은 &lt;a href=&quot;https://plugins.gradle.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gradle Plugin Portal&lt;/a&gt;에 게시되고, 다른 그래들 사용자가 손쉽게 사용하고 발견할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 로컬 플러그인&lt;/h3&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;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;size16&quot;&gt;그래들은 API를 사용하여 커스텀 플러그인을 개발할 수 있는 기능을 제공합니다. 본인만의 플러그인을 만들려면 다음 단계를 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 플러그인 클래스 정의하기 - (Plugin&amp;lt;Project&amp;gt; 인터페이스를 상속하는 클래스를 만듭니다.)&lt;/h4&gt;
&lt;pre id=&quot;code_1717917997993&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Define a 'HelloPlugin' plugin
class HelloPlugin : Plugin&amp;lt;Project&amp;gt; {
    override fun apply(project: Project) {
        // Define the 'hello' task
        val helloTask = project.tasks.register(&quot;hello&quot;) {
            doLast {
                println(&quot;Hello, Gradle!&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 플러그인을 빌드하고 선택적으로 게시하기 - (플러그인 코드가 포함된 JAR 파일을 생성하고 선택적으로 이 JAR를 저장소에 게시하여 다른 프로젝트에서 사용합니다.)&lt;/h4&gt;
&lt;pre id=&quot;code_1717918093052&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Publish the plugin
plugins {
    `maven-publish`
}

publishing {
    publications {
        create&amp;lt;MavenPublication&amp;gt;(&quot;mavenJava&quot;) {
            from(components[&quot;java&quot;])
        }
    }
    repositories {
        mavenLocal()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 플러그인 적용 - (플러그인을 사용하려면 plugins{} 빌드 파일 블록에 플러그인 ID와 버전을 포함합니다.)&lt;/h4&gt;
&lt;pre id=&quot;code_1717918133986&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Apply the plugin
plugins {
    id(&quot;com.example.hello&quot;) version &quot;1.0&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SW개발/Spring</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/559</guid>
      <comments>https://leffept.tistory.com/559#entry559comment</comments>
      <pubDate>Thu, 13 Jun 2024 21:29:33 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle]그래들 알아보기 7 - Task Basics</title>
      <link>https://leffept.tistory.com/558</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/task_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/task_basics.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717914836172&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;Task Basics&quot; data-og-description=&quot;A task represents some independent unit of work that a build performs, such as compiling classes, creating a JAR, generating Javadoc, or publishing archives to a repository. You run a Gradle build task using the gradle command or by invoking the Gradle Wra&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/task_basics.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/task_basics.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nzoVa/hyWlkT9Tbs/tEwnYWu74mAcenty2IS9I1/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/task_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/task_basics.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nzoVa/hyWlkT9Tbs/tEwnYWu74mAcenty2IS9I1/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200');&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;Task Basics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A task represents some independent unit of work that a build performs, such as compiling classes, creating a JAR, generating Javadoc, or publishing archives to a repository. You run a Gradle build task using the gradle command or by invoking the Gradle Wra&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&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;h2 data-ke-size=&quot;size26&quot;&gt;Task Basics&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태스크는 클래스 컴파일, JAR 생성, Javadoc 생성, 레포지토리에 아카이브 등 작업을 수행하는 독립적인 단위를 나타냅니다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vXZtW/btsHSXfFtnn/9wvNGjQkUo4iSf9z4cgMe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vXZtW/btsHSXfFtnn/9wvNGjQkUo4iSf9z4cgMe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vXZtW/btsHSXfFtnn/9wvNGjQkUo4iSf9z4cgMe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvXZtW%2FbtsHSXfFtnn%2F9wvNGjQkUo4iSf9z4cgMe1%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;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&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;그래들 래퍼의 build 태스크를 다음과 같이 수행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717915143548&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Available tasks&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 사용 가능한 모든 태스크는 그래들 플러그인 및 빌드 스크립트에서 제공됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 명령어를 통해 프로젝트에서 사용가능한 모든 태스크 목록을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717915236637&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew tasks

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.

...

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

...

Other tasks
-----------
compileJava - Compiles main Java source.

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Running tasks&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;./gradlew run 명령어를 입력하면 run 태스크를 실행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717915289939&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew run

&amp;gt; Task :app:compileJava
&amp;gt; Task :app:processResources NO-SOURCE
&amp;gt; Task :app:classes

&amp;gt; Task :app:run
Hello World!

BUILD SUCCESSFUL in 904ms
2 actionable tasks: 2 executed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run 태스크를 실행하면 콘솔에 예제 자바 프로젝트의 Hello World가 출력된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task Depedency&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 상황에서 태스크를 실행하려면 선행되어야 하는 태스크가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, build 태스크를 실행하기 위해서는 Java 코드 컴파일이 먼저 수행되어야 합니다. 그래서 build 태스크는 compileJava 태스크에 의존합니다.&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;build 태스크가 실행되기 전 compileJava 태스크가 실행되는 상황을 보여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717915671906&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew build

&amp;gt; Task :app:compileJava
&amp;gt; Task :app:processResources NO-SOURCE
&amp;gt; Task :app:classes
&amp;gt; Task :app:jar
&amp;gt; Task :app:startScripts
&amp;gt; Task :app:distTar
&amp;gt; Task :app:distZip
&amp;gt; Task :app:assemble
&amp;gt; Task :app:compileTestJava
&amp;gt; Task :app:processTestResources NO-SOURCE
&amp;gt; Task :app:testClasses
&amp;gt; Task :app:test
&amp;gt; Task :app:check
&amp;gt; Task :app:build

BUILD SUCCESSFUL in 764ms
7 actionable tasks: 7 executed&lt;/code&gt;&lt;/pre&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;</description>
      <category>SW개발/Spring</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/558</guid>
      <comments>https://leffept.tistory.com/558#entry558comment</comments>
      <pubDate>Wed, 12 Jun 2024 23:48:45 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle]그래들 알아보기 6 - Dependency Management Basics</title>
      <link>https://leffept.tistory.com/557</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/dependency_management_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/dependency_management_basics.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717765632996&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;Dependency Management Basics&quot; data-og-description=&quot;To add a dependency to your project, specify a dependency in the dependencies block of your build.gradle(.kts) file. The following build.gradle.kts file adds a plugin and two dependencies to the project using the version catalog above: plugins { alias(libs&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/dependency_management_basics.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/dependency_management_basics.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/dependency_management_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/dependency_management_basics.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Dependency Management Basics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To add a dependency to your project, specify a dependency in the dependencies block of your build.gradle(.kts) file. The following build.gradle.kts file adds a plugin and two dependencies to the project using the version catalog above: plugins { alias(libs&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&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;h2 data-ke-size=&quot;size26&quot;&gt;Dependency&amp;nbsp;Management&amp;nbsp;Basics&lt;/h2&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egQVgr/btsHSpQSiK1/aUzLwa8q5SSRv6RLufKc61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egQVgr/btsHSpQSiK1/aUzLwa8q5SSRv6RLufKc61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egQVgr/btsHSpQSiK1/aUzLwa8q5SSRv6RLufKc61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegQVgr%2FbtsHSpQSiK1%2FaUzLwa8q5SSRv6RLufKc61%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;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&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;의존성 관리는 프로젝트에서 요구되는 외부 리소스를 선언하고 해결하기 위한 자동화된 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래들 빌드 스크립트는 외부 의존성이 요구되는 경우에 프로젝트를 빌드하는 프로세스를 정의합니다. 의존성은 프로젝트 빌드를 지원하는 JAR, 플러그인, 라이브러리 또는 소스 코드와 같은 것을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Version Catalog&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 카탈로그는 의존성 선언을 중앙 집중화하는 방법이고, libs.versions.toml 파일에 선언합니다.&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;size16&quot;&gt;버전 카탈로그는 아래 4개의 섹션을 포함합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;[versions] 플러그인이나 라이브러리가 참조할 버전명을 정의합니다.&lt;/li&gt;
&lt;li&gt;[libraries] 빌드 파일에서 사용할 라이브러리를 정의합니다.&lt;/li&gt;
&lt;li&gt;[bundles] 의존성 세트를 정의합니다.&lt;/li&gt;
&lt;li&gt;[plugins] 플러그인을 정의합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1717766347937&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[versions]
androidGradlePlugin = &quot;7.4.1&quot;
mockito = &quot;2.16.0&quot;

[libraries]
googleMaterial = { group = &quot;com.google.android.material&quot;, name = &quot;material&quot;, version = &quot;1.1.0-alpha05&quot; }
mockitoCore = { module = &quot;org.mockito:mockito-core&quot;, version.ref = &quot;mockito&quot; }

[plugins]
androidApplication = { id = &quot;com.android.application&quot;, version.ref = &quot;androidGradlePlugin&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은 gradle 디렉토리에 위치해야 IDE나 Gradle이 자동으로 사용할 수 있습니다. 버전 카탈로그는 소스 컨트롤로 체크해야 합니다. (gradle/libs.versions.toml)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Declaring Your Dependencies&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 의존성을 추가하려면, build.gradle 파일의 dependencies 블록에 의존성을 명시하면 됩니다.&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;아래는 build.gradle.kts 파일에서 버전 카탈로그를 사용해서 프로젝트에 2개의 의존성과 플러그인을 추가한 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717766921676&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
   alias(libs.plugins.androidApplication)  // 1
}

dependencies {
    // Dependency on a remote binary to compile and run the code
    implementation(libs.googleMaterial)    // 2

    // Dependency on a remote binary to compile and run the test code
    testImplementation(libs.mockitoCore)   // 3
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트에 Android Gradle 플러그인을 적용했고, 안드로이드 앱을 빌드할 때 몇몇 추가 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;프로젝트에 Material 의존성을 추가합니다.Material Design은 안드로이드 앱에서 유저 인터페이스를 만드는 컴포넌트를 제공합니다. 이 라이브러리는 프로젝트에서 Kotlin 소스 코드가 컴파일되거나 실행될 때 사용됩니다.&lt;/li&gt;
&lt;li&gt;프로젝트에 Mockito 의존성을 추가합니다. Mockito는 자바 코드를 테스팅 하기 위한 프레임워크 입니다. 이 라이브러리는 프로젝트의 test 소스 코드가 컴파일되거나 실행될 때 사용됩니다.&lt;/li&gt;
&lt;/ol&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;material 라이브러리는 implementation 설정에 추가되었고, 이는 프로덕션 코드를 컴파일하거나 실행하는데 사용됩니다.&lt;/li&gt;
&lt;li&gt;mockto-core 라이브러리는 testimplementation 설정에 추가되었고, 이는 테스트 코드를 컴파일하거나 실행하는데 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Viewing Project Dependencies&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 의존성을 확인하고 싶다면 ./gradlew :app:dependencis 명령어를 입력합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717767409153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./gradlew :app:dependencies

&amp;gt; Task :app:dependencies

------------------------------------------------------------
Project ':app'
------------------------------------------------------------

implementation - Implementation only dependencies for source set 'main'. (n)
\--- com.google.android.material:material:1.1.0-alpha05 (n)

testImplementation - Implementation only dependencies for source set 'test'. (n)
\--- org.mockito:mockito-core:2.16.0 (n)

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SW개발/Spring</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/557</guid>
      <comments>https://leffept.tistory.com/557#entry557comment</comments>
      <pubDate>Tue, 11 Jun 2024 21:37:20 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle]그래들 알아보기 5 - Build File Basics</title>
      <link>https://leffept.tistory.com/556</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/build_file_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/build_file_basics.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717764417201&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;Build File Basics&quot; data-og-description=&quot;Generally, a build script details build configuration, tasks, and plugins. Every Gradle build comprises at least one build script. In the build file, two types of dependencies can be added: The libraries and/or plugins on which Gradle and the build script &quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/build_file_basics.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/build_file_basics.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cDMZ2c/hyWg7PoBaW/C76XFYJedachOD1kjSK9jk/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/build_file_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/build_file_basics.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cDMZ2c/hyWg7PoBaW/C76XFYJedachOD1kjSK9jk/img.png?width=2100&amp;amp;height=1200&amp;amp;face=0_0_2100_1200');&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;Build File Basics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Generally, a build script details build configuration, tasks, and plugins. Every Gradle build comprises at least one build script. In the build file, two types of dependencies can be added: The libraries and/or plugins on which Gradle and the build script&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&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;h2 data-ke-size=&quot;size26&quot;&gt;Build File Basics&lt;/h2&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBcO1/btsHSwI6dF2/IOAsGIj60I8kqi0KJsX4e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBcO1/btsHSwI6dF2/IOAsGIj60I8kqi0KJsX4e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBcO1/btsHSwI6dF2/IOAsGIj60I8kqi0KJsX4e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBcO1%2FbtsHSwI6dF2%2FIOAsGIj60I8kqi0KJsX4e1%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;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&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;모든 그래들 빌드는 최소한 하나의 빌드 스크립트로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 파일에는 두 종류의 의존성을 추가할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;그래들과 빌드 스크립트가 의존하는 라이브러리나 플러그인입니다.&lt;/li&gt;
&lt;li&gt;프로젝트 소스가 의존하는 라이브러리입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Build Scripts&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 스크립트는 Groovy로 쓰인 build.gradle 혹은 Kotlin으로 쓰인 build.gradle.kts 파일입니다.&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;pre id=&quot;code_1717764657890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle

plugins {
    id 'application'                // 1
}

application {
    mainClass = 'com.example.Main'  // 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;플러그인을 추가합니다.&lt;/li&gt;
&lt;li&gt;규칙 속성을 사용합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 플러그인 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 그래들의 기능을 확장하거나 프로젝트의 태스크에 기여할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드에 플러그인을 추가하는 것을 플러그인을 적용한다고 하고, 추가적인 기능을 사용할 수 있게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717764774399&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id(&quot;application&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application 플러그인은 실행 가능한 JVM 애플리케이션을 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, application 플러그인은 암묵적으로 java 플러그인을 적용합니다. java 플러그인은 프로젝트에 자바 테스트 및 번들링 기능과 함께 자바 컴파일을 프로젝트에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 규칙 속성 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 프로젝트에 task를 추가합니다. 또한, 프로젝트에 속성과 메서드를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application 플러그인은 run task와 같이 애플리케이션을 패키징하고 배포하는 태스크를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application 플러그인은 코드를 실행하는데 필요한 Java 애플리케이션의 기본 클래스를 선언하는 방법을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717765325707&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;application {
    mainClass = &quot;com.example.Main&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서 메인 클래스는 com.example.Main이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SW개발/Spring</category>
      <author>Leffe_pt</author>
      <guid isPermaLink="true">https://leffept.tistory.com/556</guid>
      <comments>https://leffept.tistory.com/556#entry556comment</comments>
      <pubDate>Mon, 10 Jun 2024 22:02:33 +0900</pubDate>
    </item>
  </channel>
</rss>