π€ λ¬Έμ μν©
μ§νμ€μΈ λΈλΌμΈ νλ‘μ νΈμμ κ²μ κ²°κ³Ό μ‘°ν νμλ₯Ό μ¦κ°μν€λ λ‘μ§μ ꡬννλ κ³Όμ μμ λμμ± λ¬Έμ κ° λ°μν μ μλ μν©μ νμΈνμ΅λλ€. μλ₯Ό λ€μ΄ κ²μ κ²°κ³Όλ‘ κ½ A, B, C κ° μ‘°νλλ©΄ A, B, C μ μ‘°ν νμλ₯Ό κ°κ° 1μ© μ¦κ°μμΌμΌ νλλ°, λ§μ½ A μ νμ¬ μ‘°ν νμκ° 1μΌ λ, λμμ 3λ² μ‘°νλλ€λ©΄ κ²°κ³Όλ 4νκ° λμ΄μΌ νμ§λ§, 2νκ° λλ μν©μ΄μμ΅λλ€. μ΄λ₯Ό μνλ λ°©μλλ‘ λμμν€κΈ° μν΄ λΉκ΄μ λ½μ μ μ©ν΄λ³΄κΈ°λ‘ νμ΅λλ€.
π» JPA λμμ± μ μ΄ λ°©μ
JPA λ μν°ν°μ λν 무결μ±μ μ μ§ν μ μλλ‘ λμμ± μ μ΄ λ©μ»€λμ¦μ μ§μνκ³ μμ΅λλ€.
μ΄ λ©μ»€λμ¦μλ λκ΄μ λ½κ³Ό λΉκ΄μ λ½μ΄ ν¬ν¨λ©λλ€.
βοΈ λκ΄μ λ½ (Optimistic Lock)
λκ΄μ λ½μ DB κ° μ 곡νλ λ½ κΈ°λ₯μ νμ©νμ§ μκ³ JPA κ° μ 곡νλ @Version
μ΄λ
Έν
μ΄μ
μ νμ©ν΄μ μν°ν°μ λ²μ μ κ΄λ¦¬νλ λ°©μμ
λλ€.
@Entity
public class SearchHit {
@Id
private Long id;
private String name;
private Integer count; // μ‘°ν νμ
@Version
private Integer version; // λ²μ
}
λ€μκ³Ό κ°μ΄ λ²μ κ΄λ¦¬μ© νλλ₯Ό μμ±ν μ μλλ°, ν΄λΉ μν°ν°κ° λ³κ²½λ λλ§λ€ version κ°μ΄ μ¦κ°νκ² λ©λλ€.
κ·Έλ¦¬κ³ μν°ν°λ₯Ό μμ ν λ, μν°ν°λ₯Ό μ‘°νν μμ μ λ²μ κ°κ³Ό μμ ν μμ μ λ²μ κ°μ΄ μΌμΉνμ§ μμΌλ©΄ μμΈκ° λ°μνκ² λ©λλ€.
λκ΄μ λ½μ μ μ©ν΄μ μμμ μΈκΈνλ λ¬Έμ μν©μ ν΄κ²°ν μ μμκΉ?
A μ μ‘°ν νμκ° 1μΌ λ λ²μ μ΄ 1μ΄λΌκ³ κ°μ ν΄λ³΄κ² μ΅λλ€. λμμ μΈλ²μ μμ²μ΄ λ€μ΄μκ³ κ°κ°μ νΈλμμ μ t1, t2, t3 λΌκ³ νλ©΄ λͺ¨λ μ‘°ν μμ μ λ²μ μ 1μ λλ€. μ΄ λ t1 μ΄ κ°μ₯ λ¨Όμ μν°ν°λ₯Ό λ³κ²½νκ³ μ»€λ°μ μλ£νλ€κ³ νλ©΄ A μ μ‘°ν νμλ 2, λ²μ μ 2λ‘ λ³κ²½λ©λλ€. λμ€μ t2 μ t3 λͺ¨λ μ‘°ν νμ 2 λ‘ μ»€λ°νλ € νμ§λ§, λ²μ μ΄ 2λ‘ λ³κ²½λμ΄ μ‘°νν μμ μ λ²μ κ°κ³Ό μΌμΉνμ§ μμ μμΈκ° λ°μν©λλ€. λ°λΌμ μ΅μ΄ 컀λ°λ§ μΈμ λμ΄ μ‘°νμλ 4κ° μλ 2λ‘ μ λ°μ΄νΈλμ΄ μνλ λ°©μλλ‘ λ¬Έμ λ₯Ό ν΄κ²°ν μ μμ΅λλ€.
βοΈ λΉκ΄μ λ½ (Pessimistic Lock)
λΉκ΄μ λ½μ DB κ° μ 곡νλ λ½ κΈ°λ₯μ μ¬μ©ν©λλ€. @Lock
μ΄λ
Έν
μ΄μ
μ νμ©ν΄μ ꡬνν μ μμ΅λλ€.
public interface SearchHitRepository extends JpaRepository<SearchHit, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select h from SearchHit h where h.id = :id")
SearchHit findByIdForUpdate(Long id);
}
Querydsl μ νμ©νκ³ μλ€λ©΄ λ€μκ³Ό κ°μ΄ μ μ©ν μ μμ΅λλ€.
public class SearchHitRepositoryImpl implements SearchHitRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public SearchHit findByIdForUpdate(Long id) {
return queryFactory
.selectFrom(searchHit)
.where(searchHit.id.eq(id))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
}
}
μμ κ°μ΄ LockModeType
μ PESSIMISTIC_WRITE
λ‘ μ€μ νκ² λλ©΄ λ°μ΄ν°λ² μ΄μ€μ select for update
μΏΌλ¦¬κ° λκ°κ² λκ³ μ‘°νν λ μ½λ μ체μ λ½μ κ±Έκ² λ©λλ€.
μ΄μ λ€μ λ¬Έμ μν©μ λν΄ μκ°ν΄λ³΄κ² μ΅λλ€. A μ μ‘°ν νμκ° 1μΌ λ μμ²μ΄ λμμ 3λ² λ€μ΄μ€λ©΄ κ°μ₯ λ¨Όμ A λ₯Ό μ‘°νν νΈλμμ t1 μ΄ A μ λν λ½μ νλνκ² λ©λλ€. λ€λ₯Έ νΈλμμ t2, t3 λ t1 μ΄ λ½μ ν΄μ ν λκΉμ§ λκΈ°νκ² λκ³ , t1 μ΄ A μ μ‘°ν νμλ₯Ό 2λ‘ μ λ°μ΄νΈ ν νμ λ½μ ν΄μ νλ©΄ t2 κ° λ½μ νλνμ¬ A μ μ‘°ν νμλ₯Ό 3μΌλ‘ μ λ°μ΄νΈ, λ§μ§λ§μΌλ‘ t3 κ° λ½μ νλνμ¬ μ‘°ν νμλ₯Ό 4λ‘ μ λ°μ΄νΈν©λλ€. μνλ λ°©μλλ‘ λ¬Έμ λ₯Ό ν΄κ²°ν μ μκ² λμμ΅λλ€.
βοΈ κ³ λ €ν μ
λΉκ΄μ λ½μ μ μ©ν΄μ μνλ κ²°κ³Όλ₯Ό μ»μ μ μμμ΅λλ€. νμ§λ§ λΉκ΄μ λ½μ μ μ©νκ² λλ©΄ νΈλμμ μ΄ νΉμ λ°μ΄ν°μ λν λ½μ ν΄μ νκΈ° μ κΉμ§ λ€λ₯Έ νΈλμμ λ€μ λκΈ°ν΄μΌ νκΈ° λλ¬Έμ μ±λ₯μ΄ μ νλ μ μλ€λ λ¬Έμ μ μ΄ μ‘΄μ¬ν©λλ€. κ·Έλ κΈ° λλ¬Έμ λ°μ΄ν°μ μ νμ±κ³Ό μ±λ₯ μ νλΌλ νΈλ μ΄λ μ€νλ₯Ό κ³ λ €ν΄μ λ¬Έμ ν΄κ²° λ°©μμ μ μ νν΄μΌ ν©λλ€.
'βοΈ λ°±μλ: Backend' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[Spring] Slack API νμ©νμ¬ μλ¬ λͺ¨λν°λ§νκΈ° (0) | 2023.08.03 |
---|---|
[Spring] JPA μμμ± μ»¨ν μ€νΈ μ΄ν΄νκΈ° (0) | 2023.06.27 |
[Spring] νΌλλλ JPA κ°λ μ 리 (Hibernate, Spring Data JPA) (0) | 2023.06.27 |
[Spring] λ©ν° λͺ¨λ νλ‘μ νΈ μ μ© (0) | 2023.06.26 |
[Spring] MySQLκ³Ό mongoDB λ°μ΄ν°λ² μ΄μ€ 2κ° μ°λνκΈ° (0) | 2023.05.10 |
Spring 무νμ€ν¬λ‘€ ꡬν (2) - μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ (0) | 2023.01.01 |
Spring 무νμ€ν¬λ‘€ ꡬν (1) - 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ (0) | 2023.01.01 |