Skip to content

Commit 8e41a04

Browse files
committed
feat: domain
1 parent 708e6f0 commit 8e41a04

File tree

17 files changed

+471
-2
lines changed

17 files changed

+471
-2
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,11 @@
66

77
## Settings
88

9-
- 멀티 모듈기반으로 주제 별로 관리
9+
- 멀티 모듈기반으로 주제 별로 관리
10+
11+
## JPA에서 제공하는 트랜잭션과 락에 대한 개념정리
12+
13+
### 트랜잭션과 락
14+
15+
- [트랜잭션과 격리 수준](docs/_1_transaction_and_lock.md)
16+
- [낙관적 락과 비관적 락 기초](docs/_2_optimistic_lock_and_pessimistic_lock.md)

docs/_1_transaction_and_lock.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
### 트랜잭션과 락
2+
3+
> **트랜잭션과 격리 수준**
4+
5+
- 트랜잭션 기초와 JPA가 제공하는 낙관적인 락과 비관적인 락에 대한 정리
6+
7+
> **트랜잭션의 특성**
8+
9+
트랜잭션은 `ACID`라 하는 원자성, 일관성, 격리성, 지속성을 보장해야 한다.
10+
11+
- `원자성`:
12+
- 트랜잭션내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 `성공` 하든가 모두 `실패`해야 한다.
13+
- `일관성`:
14+
- 모든 트랜잭션은 `일관성`있는 데이터베이스 상태를 유지해야한다.
15+
- 예를 들어 데이터베이스에서 정한 `무결성 제약 조건`을 항상 만족해야 한다.
16+
- `격리성`:
17+
- 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
18+
- 예를 들어 동시에 같은 데이터를 수정하지 못하도록 해야 한다.
19+
- `격리성``동시성과 관련된 성능 이슈`로 인해 `격리 수준을 선택`할 수 있다.
20+
- `지속성`:
21+
- 트랜잭션을 성공적으로 끝내면 그 `결과``항상 기록`되어야 한다.
22+
- 중간에 `시스템에 문제가 발생`해도 데이터베이스 로그 등을 사용해서 `성공한 트랜잭션 내용을 복구`해야 한다.
23+
24+
* 여기서 중요한 점은 `트랜잭션 간``격리성을 완벽히 보장`하기 위해서는 트랜잭션을 차례로 실행해야 하고, 그렇게 되면 `동시성 처리 성능`이 나빠진다.
25+
* 그리하여 `트랜잭션의 격리 레벨에 대한 이해``동시성에 대한 문제를 해결`하는 방법에 대해 알아야 한다.
26+
27+
> **트랜잭션과 격리 수준**
28+
29+
- **READ UNCOMMITED(커밋되지않은 읽기)**
30+
- 커밋하지 않은 데이터를 읽을 수 있다.
31+
- 트랜잭션 A 가 데이터를 수정하고 있는 와중에 `COMMIT`을 하지 않아도 트랜잭션 B가 수정 중인 데이터를 볼 수 있다.
32+
- 이를 `DIRTY READ`라 하고, 데이터 정합성에 심각한 문제가 발생할 수 있다.
33+
34+
- **READ COMMITTED(커밋된 읽기)**
35+
- 커밋한 데이터만 읽을 수 있다.
36+
- 커밋한 데이터만 볼 수 있기 때문에 `DIRTY READ` 현상이 발생하지 않는다.
37+
- 하지만 `NON-REPEATABLE READ`는 발생할 수 있다.
38+
- 트랜잭션 A가 회원 1을 조회 중인데, 그 순간에 트랜잭션 B가 회원 1을 수정하고 `COMMIT`하는 경우 트랜잭션 A가 다시 회원을 조회 하였을 때 수정된 데이터가 조회된다.
39+
- 이처럼 반복해서 같은 데이터를 읽을 수 없는 상태를 `NON-REPEATABLE READ`라 한다.
40+
- `DIRTY-READ`는 허용하지만, `NON-REPEATABLE READ`는 허용하는 격리 수준을 `READ COMMITED`라 한다.
41+
42+
- **REPEATABLE READ(반복가능한 읽기)**
43+
- 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회된다.
44+
- 하지만 `PHANTOM READ`가 발생할 수 있다.
45+
- 트랜잭션 A가 회원 10 살 이하의 회원을 조회 했는데 트랜잭션 B가 5살 회원을 추가하고 커밋하면, 트랜잭션 A가 다시 10살 이하의 회원을 조회하는 경우 회원 하나가 추가된 상태로 조회된다.
46+
- 이처럼 반복 조회 시 결과 집합이 달라지는 것을 `PHANTOM READ`라 한다.
47+
- `NON-REPEATABLE READ`는 허용하지 않지만, `PHANTOM READ`는 허 용하는 격리 수준을 `REPEATABLE READ`라 한다.
48+
49+
- **SERIALIZABLE(직렬화 가능)**
50+
- 가장 엄격한 트랜잭션 격리 수준이다.
51+
- `PHANTOM READ`가 발생하지 않는다. 하지만 `동시성 처리 성능`이 급격히 떨어질 수 있다.
52+
53+
> **격리 수준에 대한 고민하기**
54+
55+
어플리케이션 대부분은 `동시성 처리`가 중요하므로 `READ COMMITED`의 격리 수준을 기본으로 사용한다.
56+
57+
기본은 `READ COMMITED`로 두고 더 높은 격리 수준이 필요한 경우 `데이터베이스 트랜잭션이 제공하는 잠금 기능`을 사용하는 방식을 쓴다.
58+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
### 낙관적 락과 비관적 락 기초
2+
3+
JPA를 사용함으로써 누릴 수 있는 것이 영속성 컨텍스트이다.
4+
5+
영속성 컨텍스트를 적절하게 사용하면 데이터베이스 트랜잭션이 `READ COMMITED` 격리 수준이어도 애플리케이션 레벨에서 `반복 가능한 읽기(REPEATABLE READ)`가 가능하다.
6+
61 KB
Loading
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.example.optimistic.application.owner;
2+
3+
import com.example.optimistic.application.pet.Pet;
4+
import com.example.optimistic.core.domain.Person;
5+
6+
import javax.persistence.*;
7+
import javax.validation.constraints.Digits;
8+
import javax.validation.constraints.NotEmpty;
9+
import java.util.Set;
10+
11+
@Entity
12+
@Table(name = "OWNERS")
13+
public class Owner extends Person {
14+
15+
@Column(name = "address")
16+
@NotEmpty
17+
private String address;
18+
19+
@Column(name = "CITY")
20+
@NotEmpty
21+
private String city;
22+
23+
@Column(name = "TELEPHONE")
24+
@NotEmpty
25+
@Digits(fraction = 0, integer = 10)
26+
private String telephone;
27+
28+
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner", fetch = FetchType.EAGER)
29+
private Set<Pet> pets;
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.example.optimistic.application.pet;
2+
3+
import com.example.optimistic.application.owner.Owner;
4+
import com.example.optimistic.application.visit.Visit;
5+
import com.example.optimistic.core.domain.NamedEntity;
6+
import org.springframework.format.annotation.DateTimeFormat;
7+
8+
import javax.persistence.*;
9+
import java.time.LocalDate;
10+
import java.util.LinkedHashSet;
11+
import java.util.Set;
12+
13+
@Entity
14+
@Table(name = "PETS")
15+
public class Pet extends NamedEntity {
16+
17+
@Column(name = "BIRTH_DATE")
18+
@DateTimeFormat(pattern = "yyyy-MM-dd")
19+
private LocalDate birthDate;
20+
21+
@ManyToOne
22+
@JoinColumn(name = "type_id")
23+
private PetType type;
24+
25+
@ManyToOne
26+
@JoinColumn(name = "owner_id")
27+
private Owner owner;
28+
29+
@Transient
30+
private Set<Visit> visits = new LinkedHashSet<>();
31+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.optimistic.application.pet;
2+
3+
import com.example.optimistic.core.domain.NamedEntity;
4+
5+
import javax.persistence.Entity;
6+
import javax.persistence.Table;
7+
8+
@Entity
9+
@Table(name = "TYPES")
10+
public class PetType extends NamedEntity {
11+
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.example.optimistic.application.vet;
2+
3+
4+
import com.example.optimistic.core.domain.NamedEntity;
5+
6+
import javax.persistence.Entity;
7+
import javax.persistence.Table;
8+
import java.io.Serializable;
9+
10+
@Entity
11+
@Table(name = "SPECIALTIES")
12+
public class Specialty extends NamedEntity implements Serializable {
13+
14+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.example.optimistic.application.vet;
2+
3+
import com.example.optimistic.core.domain.Person;
4+
5+
import javax.persistence.*;
6+
import java.util.HashSet;
7+
import java.util.Set;
8+
9+
@Entity
10+
@Table(name = "VETS")
11+
public class Vet extends Person {
12+
13+
@ManyToMany(fetch = FetchType.EAGER)
14+
@JoinTable(name = "VET_SPECIALTIES", joinColumns = @JoinColumn(name = "vet_id"),
15+
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
16+
private Set<Specialty> specialties;
17+
18+
protected Set<Specialty> getSpecialtiesInternal() {
19+
if (this.specialties == null) {
20+
this.specialties = new HashSet<>();
21+
}
22+
return this.specialties;
23+
}
24+
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.example.optimistic.application.visit;
2+
3+
import com.example.optimistic.core.domain.BaseEntity;
4+
import org.springframework.format.annotation.DateTimeFormat;
5+
6+
import javax.persistence.Column;
7+
import javax.persistence.Entity;
8+
import javax.persistence.Table;
9+
import javax.validation.constraints.NotEmpty;
10+
import java.time.LocalDate;
11+
12+
@Entity
13+
@Table(name = "VISITS")
14+
public class Visit extends BaseEntity {
15+
16+
@Column(name = "PET_ID")
17+
private Integer petId;
18+
19+
@Column(name = "VISIT_DATE")
20+
@DateTimeFormat(pattern = "yyyy-MM-dd")
21+
private LocalDate date;
22+
23+
@NotEmpty
24+
@Column(name = "DESCRIPTION")
25+
private String description;
26+
27+
}

0 commit comments

Comments
 (0)