Avatar
πŸ˜‰

Organizations

  • λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ λͺ¨λ“  νŠΈλžœμž­μ…˜μ— λŒ€ν•΄ Lockκ³Ό Unlock을 μ‚¬μš©ν•˜λŠ” 방법은 λ°μ΄ν„°λ² μ΄μŠ€ 및 νŠΈλžœμž­μ…˜μ˜ λ™μž‘ 방식에 따라 λ‹€λ¦…λ‹ˆλ‹€. 일반적으둜 ν…Œμ΄λΈ” 잠금, ν–‰ 잠금, νŠΈλžœμž­μ…˜ 격리 μˆ˜μ€€, λͺ…μ‹œμ  잠금 ν•΄μ œλ₯Ό μ‘°ν•©ν•˜μ—¬ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. Java ν™˜κ²½μ—μ„œλŠ” JDBC, JPA, QueryDSL 등을 ν™œμš©ν•΄ 이λ₯Ό μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    μ•„λž˜λŠ” SELECT뿐만 μ•„λ‹ˆλΌ INSERT, UPDATE, DELETE λ“± λͺ¨λ“  νŠΈλžœμž­μ…˜μ—μ„œ Lockκ³Ό Unlock을 μ‚¬μš©ν•˜λŠ” 방법을 μ„€λͺ…ν•©λ‹ˆλ‹€.


    1. ν…Œμ΄λΈ” 잠금 (Table Lock)

    ν…Œμ΄λΈ” λ‹¨μœ„λ‘œ μž κΈˆμ„ μ„€μ •ν•˜μ—¬ νŠΈλžœμž­μ…˜μ΄ μ™„λ£Œλ˜κΈ° μ „κΉŒμ§€ λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ˜ 접근을 λ§‰μŠ΅λ‹ˆλ‹€.

    Created Wed, 26 Mar 2025 11:33:45 +0900
  • QueryDSL을 μ‚¬μš©ν•  λ•Œ λ°μ΄ν„°λ² μ΄μŠ€μ˜ Lock κΈ°λŠ₯을 ν™œμš©ν•˜λŠ” 방법은 JPA와 μœ μ‚¬ν•©λ‹ˆλ‹€. QueryDSL은 JPA와 ν†΅ν•©λ˜μ–΄ λ™μž‘ν•˜λ©°, JPQL 및 Native Queryμ—μ„œ μ‚¬μš©ν•˜λŠ” 잠금 λ©”μ»€λ‹ˆμ¦˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€. μ•„λž˜λŠ” QueryDSLμ—μ„œ Lockκ³Ό κ΄€λ ¨λœ μ£Όμš” κΈ°λŠ₯κ³Ό κ΅¬ν˜„ 방법을 μ„€λͺ…ν•©λ‹ˆλ‹€.


    1. QueryDSLμ—μ„œ Pessimistic Lock μ‚¬μš©

    QueryDSL은 JPA LockModeType을 μ§€μ›ν•˜μ—¬ 비관적 μž κΈˆμ„ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    예제: LockModeType.PESSIMISTIC_WRITE μ‚¬μš©

    import com.querydsl.jpa.impl.JPAQueryFactory;
    import jakarta.persistence.EntityManager;
    import jakarta.persistence.LockModeType;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class ProductService {
        private final JPAQueryFactory queryFactory;
    
        public ProductService(EntityManager entityManager) {
            this.queryFactory = new JPAQueryFactory(entityManager);
        }
    
        @Transactional
        public Product findByIdWithLock(Long productId) {
            QProduct product = QProduct.product;
    
            // Pessimistic Write Lock μ„€μ •
            return queryFactory.selectFrom(product)
                    .where(product.id.eq(productId))
                    .setLockMode(LockModeType.PESSIMISTIC_WRITE)
                    .fetchOne();
        }
    }
    

    μ£Όμš” 포인트:

    • setLockMode(LockModeType.PESSIMISTIC_WRITE)λ₯Ό μ‚¬μš©ν•˜μ—¬ 비관적 μ“°κΈ° μž κΈˆμ„ μ„€μ •ν•©λ‹ˆλ‹€.
    • PESSIMISTIC_READ와 같은 λ‹€λ₯Έ 잠금 λͺ¨λ“œλ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    2. 낙관적 잠금 (Optimistic Lock)

    낙관적 μž κΈˆμ€ QueryDSLμ—μ„œ λ³„λ„λ‘œ μ§€μ›λ˜μ§€ μ•Šμ§€λ§Œ, JPA μ—”ν„°ν‹°μ˜ @Version ν•„λ“œλ₯Ό ν™œμš©ν•˜μ—¬ λ™μž‘ν•©λ‹ˆλ‹€.

    Created Wed, 26 Mar 2025 11:30:45 +0900
  • Java μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ‹€ν–‰ν•  λ•Œ -Xmx512mκ³Ό -XX:MaxRAM=512mλŠ” λͺ¨λ‘ μ΅œλŒ€ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ„ μ œν•œν•˜λŠ” μ˜΅μ…˜μ΄μ§€λ§Œ, λ™μž‘ 방식에 차이가 μžˆμŠ΅λ‹ˆλ‹€.

    1. -Xmx512m

    • νž™(Heap) λ©”λͺ¨λ¦¬μ˜ μ΅œλŒ€ 크기λ₯Ό 512MB둜 μ„€μ •ν•©λ‹ˆλ‹€.
    • 예제:
      java -Xmx512m -jar myapp.jar
      
    • 이 섀정은 Java νž™ λ©”λͺ¨λ¦¬λ§Œ μ œν•œν•˜λ©°, 전체 ν”„λ‘œμ„ΈμŠ€μ˜ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ„ μ œν•œν•˜λŠ” 것은 μ•„λ‹™λ‹ˆλ‹€.
    • 즉, νž™ μ™Έμ˜ λ©”λͺ¨λ¦¬(λ©”νƒ€μŠ€νŽ˜μ΄μŠ€, μŠ€νƒ, λ„€μ΄ν‹°λΈŒ λ©”λͺ¨λ¦¬ λ“±)λŠ” λ”°λ‘œ μ œν•œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

    2. -XX:MaxRAM=512m

    • JVM이 μ‚¬μš©ν•  수 μžˆλŠ” 전체 λ©”λͺ¨λ¦¬(νž™ + λ©”νƒ€μŠ€νŽ˜μ΄μŠ€ + 기타 λ„€μ΄ν‹°λΈŒ λ©”λͺ¨λ¦¬) λ₯Ό 512MB둜 μ œν•œν•©λ‹ˆλ‹€.
    • 예제:
      java -XX:MaxRAM=512m -jar myapp.jar
      
    • -XX:MaxRAM을 μ„€μ •ν•˜λ©΄ -Xmx의 기본값이 λ‹¬λΌμ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.
      • 일반적으둜 -Xmx의 기본값은 MaxRAM κ°’μ˜ 25~50% μ •λ„λ‘œ μ„€μ •λ©λ‹ˆλ‹€(JVM 버전에 따라 닀름).
    • 이 섀정은 μ»¨ν…Œμ΄λ„ˆ ν™˜κ²½(Docker, Kubernetes) μ—μ„œ JVM이 μ‚¬μš©ν•  수 μžˆλŠ” λ©”λͺ¨λ¦¬λ₯Ό μ œμ–΄ν•˜λŠ” 데 μœ μš©ν•©λ‹ˆλ‹€.

    차이점 μš”μ•½

    μ˜΅μ…˜ λŒ€μƒ μ„€λͺ…
    -Xmx512m νž™(Heap) λ©”λͺ¨λ¦¬ νž™ λ©”λͺ¨λ¦¬μ˜ μ΅œλŒ€ 크기만 μ œν•œ
    -XX:MaxRAM=512m 전체 JVM λ©”λͺ¨λ¦¬ νž™ + λ©”νƒ€μŠ€νŽ˜μ΄μŠ€ + λ„€μ΄ν‹°λΈŒ λ©”λͺ¨λ¦¬λ₯Ό ν¬ν•¨ν•œ JVM 전체 λ©”λͺ¨λ¦¬ μ œν•œ

    ν•¨κ»˜ μ‚¬μš©ν•˜λ©΄?

    두 μ˜΅μ…˜μ„ ν•¨κ»˜ μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

    Created Wed, 26 Mar 2025 11:26:45 +0900
  • Javaμ—μ„œ λ°μ΄ν„°λ² μ΄μŠ€μ˜ SELECT 쿼리에 λŒ€ν•΄ Lockκ³Ό Unlock을 μ‚¬μš©ν•˜λŠ” 방법은 λ°μ΄ν„°λ² μ΄μŠ€ μ‹œμŠ€ν…œκ³Ό 쿼리의 섀정에 따라 λ‹¬λΌμ§‘λ‹ˆλ‹€. 일반적으둜 SELECT μžμ²΄λŠ” λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μž κΈˆμ„ μœ λ°œν•˜μ§€ μ•Šμ§€λ§Œ, νŠΈλžœμž­μ…˜μ΄λ‚˜ 쿼리 힌트λ₯Ό μ‚¬μš©ν•˜μ—¬ 잠금 λ™μž‘μ„ μ œμ–΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜μ— λͺ‡ κ°€μ§€ 방법을 μ„€λͺ…ν•©λ‹ˆλ‹€.


    1. νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•œ Lock μ œμ–΄

    νŠΈλžœμž­μ…˜μ„ 톡해 SELECT 문이 μ‹€ν–‰ 쀑인 λ™μ•ˆ λ‹€λ₯Έ μž‘μ—…μ΄ 데이터에 μ ‘κ·Όν•˜μ§€ λͺ»ν•˜λ„둝 μž κΈˆμ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    예제 μ½”λ“œ:

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    public class DatabaseLockExample {
        public static void main(String[] args) {
            String url = "jdbc:mysql://localhost:3306/your_database";
            String user = "your_username";
            String password = "your_password";
    
            try (Connection connection = DriverManager.getConnection(url, user, password)) {
                // Auto-commit λΉ„ν™œμ„±ν™”
                connection.setAutoCommit(false);
    
                // SELECT ... FOR UPDATE (ν–‰ 잠금 μ„€μ •)
                String sql = "SELECT * FROM your_table WHERE id = ? FOR UPDATE";
                try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
                    preparedStatement.setInt(1, 1); // ID κ°’ 바인딩
                    ResultSet resultSet = preparedStatement.executeQuery();
    
                    while (resultSet.next()) {
                        System.out.println("Data: " + resultSet.getString("your_column"));
                    }
    
                    // μž‘μ—… ν›„ 컀밋
                    connection.commit();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    μ„€λͺ…:

    • FOR UPDATEλŠ” ν•΄λ‹Ή 행을 잠그고, λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ΄ μˆ˜μ •ν•˜μ§€ λͺ»ν•˜λ„둝 ν•©λ‹ˆλ‹€.
    • νŠΈλžœμž­μ…˜μ„ μ»€λ°‹ν•˜κ±°λ‚˜ λ‘€λ°±ν•˜λ©΄ 잠금이 ν•΄μ œλ©λ‹ˆλ‹€.

    2. 쿼리 힌트λ₯Ό μ‚¬μš©ν•œ Lock μ œμ–΄

    λ°μ΄ν„°λ² μ΄μŠ€μ— 따라 SELECT 쿼리에 힌트λ₯Ό μΆ”κ°€ν•˜μ—¬ 잠금 λ™μž‘μ„ μ§€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, MySQLμ—μ„œ LOCK IN SHARE MODE λ˜λŠ” FOR UPDATEλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    Created Wed, 26 Mar 2025 11:25:45 +0900
  • Spring Boot JPAμ—μ„œ @Transactional μ‚¬μš©λ²•

    Spring Boot JPAμ—μ„œ @Transactional μ–΄λ…Έν…Œμ΄μ…˜μ€ νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œ λ°μ΄ν„°λ² μ΄μŠ€ μž‘μ—…μ„ μ‹€ν–‰ν•˜κ³ , μ‹€νŒ¨ μ‹œ λ‘€λ°±ν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” μ€‘μš”ν•œ μš”μ†Œμž…λ‹ˆλ‹€.


    1. @Transactional κΈ°λ³Έ μ‚¬μš©λ²•

    Spring의 @Transactional을 μ‚¬μš©ν•˜λ©΄ ν•˜λ‚˜μ˜ λ©”μ„œλ“œμ—μ„œ μ—¬λŸ¬ 개의 JPA μž‘μ—…(쑰회, μ €μž₯, μˆ˜μ •, μ‚­μ œ λ“±)을 μˆ˜ν–‰ν•  λ•Œ νŠΈλžœμž­μ…˜μ„ μžλ™μœΌλ‘œ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.

    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class MemberService {
        private final MemberRepository memberRepository;
    
        public MemberService(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
    
        @Transactional
        public void updateMember(Long id, String name) {
            Member member = memberRepository.findById(id)
                    .orElseThrow(() -> new IllegalArgumentException("Member not found"));
    
            member.setName(name);  // JPA μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ˜ν•΄ μžλ™μœΌλ‘œ μ—…λ°μ΄νŠΈλ¨
        }
    }
    
    • @Transactional을 μ‚¬μš©ν•˜λ©΄, λ©”μ„œλ“œ μ‹€ν–‰ 쀑 μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ©΄ 둀백되며, μ„±κ³΅ν•˜λ©΄ μžλ™μœΌλ‘œ μ»€λ°‹λ©λ‹ˆλ‹€.
    • member.setName(name);을 ν˜ΈμΆœν•˜λ©΄ JPA의 λ³€κ²½ 감지(dirty checking) 덕뢄에 save()λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν˜ΈμΆœν•˜μ§€ μ•Šμ•„λ„ μ—…λ°μ΄νŠΈλ©λ‹ˆλ‹€.

    2. 읽기 μ „μš© νŠΈλžœμž­μ…˜ (@Transactional(readOnly = true))

    readOnly = trueλ₯Ό μ„€μ •ν•˜λ©΄ μ„±λŠ₯ μ΅œμ ν™”λ₯Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    Created Wed, 26 Mar 2025 11:21:45 +0900
  • Spring Boot JPAμ—μ„œ @Transactional μ˜΅μ…˜ 정리

    Spring Boot JPAμ—μ„œ @Transactional은 νŠΈλžœμž­μ…˜ 처리λ₯Ό μœ„ν•΄ μ‚¬μš©ν•˜λŠ” 맀우 μ€‘μš”ν•œ μ–΄λ…Έν…Œμ΄μ…˜μ΄μ—μš”. λ°μ΄ν„°λ² μ΄μŠ€μ˜ 일관성과 무결성을 μœ μ§€ν•˜κΈ° μœ„ν•΄ μ—¬λŸ¬ μž‘μ—…μ„ ν•˜λ‚˜μ˜ νŠΈλžœμž­μ…˜μœΌλ‘œ λ¬Άμ–΄ μ‹€ν–‰ν•  λ•Œ μ‚¬μš©ν•˜μ£ . μ•„λž˜μ— μ‚¬μš©λ²•μ„ μ •λ¦¬ν•΄λ³Όκ²Œμš”.


    πŸ“Œ κΈ°λ³Έ μ‚¬μš©λ²•

    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class MemberService {
    
        @Transactional
        public void joinMember(Member member) {
            memberRepository.save(member);
            // μ˜ˆμ™Έ λ°œμƒ μ‹œ 전체 νŠΈλžœμž­μ…˜ λ‘€λ°±
        }
    }
    
    • @Transactional을 λ©”μ„œλ“œμ— 뢙이면 ν•΄λ‹Ή λ©”μ„œλ“œ μ•ˆμ˜ μž‘μ—…μ΄ ν•˜λ‚˜μ˜ νŠΈλžœμž­μ…˜μœΌλ‘œ μ²˜λ¦¬λΌμš”.
    • λŸ°νƒ€μž„ 쀑 μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ©΄ μžλ™μœΌλ‘œ λ‘€λ°±λΌμš”. (기본적으둜 unchecked exception일 λ•Œλ§Œ λ‘€λ°±)

    πŸ“Œ 클래슀 λ ˆλ²¨μ— μ‚¬μš©

    @Transactional
    @Service
    public class MemberService {
        public void method1() {
            // νŠΈλžœμž­μ…˜ 적용됨
        }
    
        public void method2() {
            // νŠΈλžœμž­μ…˜ 적용됨
        }
    }
    
    • 클래슀 전체에 νŠΈλžœμž­μ…˜μ„ κ±Έ 수 μžˆμ–΄μš”. 단, λ©”μ„œλ“œμ— κ°œλ³„μ μœΌλ‘œ μ„€μ •λœ 게 μš°μ„  μ μš©λ©λ‹ˆλ‹€.

    πŸ“Œ rollbackFor μ˜΅μ…˜

    기본적으둜 RuntimeExceptionκ³Ό κ·Έ ν•˜μœ„ 클래슀만 λ‘€λ°± λŒ€μƒμœΌλ‘œ κ°„μ£ΌλΌμš”. λ§Œμ•½ 체크 μ˜ˆμ™Έ(Exception)도 λ‘€λ°±ν•˜κ³  μ‹Άλ‹€λ©΄:

    Created Wed, 26 Mar 2025 11:15:45 +0900