5. 글쓰기 기능 만들기 (2)
DB 에 글쓰기 ‘INSERT’ 를 요청할 Repository 를 만들었다면, 글쓰기 기능을 넣을 차례다.
기능을 만들기 전에 transaction 먼저 이해하고 넘어가자.
5-3. Transaction 이해하기
DB 에는 여러 명이 접근할 수 있다. SELECT 는 수백 건 이 들어올 수 있다.
ex 1) DB 에 ‘사과’ 라는 데이터 read 요청을 한다. 100 명이 아니라 1000 명이 요청해도 항상 데이터는 사과다. 10 명의 사람이 ‘사과’ 데이터를 보고 싶어서 select 요청 했을 때, 요청 시간은 모두가 다르다. 그리고 그 중 한 명이 DB 에 변경을 요청을 할 가능성도 있다. (write)
5번 사람이 ‘사과’ 를 ‘딸기’ 로 변경 요청을 했다고 가정해보자. 그렇다면, 6번부터 10번 사람은 읽은 데이터를 ‘딸기’ 로 보게 된다.
동시에 같은 데이터를 요청했는데, 누구는 ‘사과’ 를 ‘사과’ 로 보고 누구는 ‘딸기’ 로 보는 경우가 생기는 것이다.
ex 2) 다른 예시로, 똑같이 10 명의 사람이 데이터 read 요청을 했다고 생각해보자.
1번 부터 7번 사람 까지는 ‘잔액 10,000 원’ 을 보고, 8번 사람이 돌연 5,000 원을 인출했을 때, 9번, 10번 사람은 ‘잔액 5,000 원’ 을 보는 경우가 생긴다.
이 때, 잔액을 출금하게 되면 1~7번 사람은 ‘10,000 원 출금하기’ 를 시도 할 것이고, 9~10번은 ‘5,000 원 출금하기’ 를 시도한다. → (당연히 꼬일 것, 동시성 문제가 생긴다.)
⇒ 해결 방법
DB 에서 8번 사람이 write 요청을 하네, 하고 기록을 해둬야 한다.
8번이 출금 하고 있는 도중에 9번 10번의 select 요청이 들어왔다.
이 때 내가 정하는 거야, 정하는 건 내 마음 ★
- 나는 5,000 원으로 바꿀거지만, 바꾸기 전 데이터를 줄거야 라고 설정하면 → 9번, 10번은 10,000 원이 있다는 것을 보게 한다. (→ commit 되기 전 데이터를 보여주는 게 일반적)
- write 요청이 들어온 시점부터는 변경된 데이터를 줘야지 하고 설정할 수도 있다 → 9번, 10번에게 오천 원이 있다는 것을 보게 한다.
고립성
고립성(Isolation)
고립성은 여러 트랜잭션이 동시에 수행될 때 서로 간섭하지 않도록 보장하는 성질이다. 즉, 각 트랜잭션이 독립적으로 수행되어 다른 트랜잭션의 중간 상태나 미완료된 데이터를 보지 않도록 한다. 이를 통해 데이터의 일관성을 유지할 수 있다.
(위의 예시에 이어)
5,000 원으로 변경 요청 들어와서 트랜잭션이 insert 하고 있고, 그것이 안끝난 상황에서 9번, 10번의 select 요청(read)이 들어오면 9번과 10번에게는 commit 되기 전 데이터를 보여준다. (→ 9번과 10번은 10,000 원을 보게됨)
트랙잭션이 끝나기 전에는 메모리에만 남아있다.
트랜잭션이 끝나고 나서 9번 10번이 요청하면 commit 된 데이터를 준다.
이것을 고립성이라 한다. 내가 write 요청을 하고 있으면, 그 전 데이터를 준다.
고립성의 종류가 많은데 이걸 isolation 이라 한다.
고립성에 대한 설정은 DB 마다 다르다, 내가 설정할 수 도 있음.
commit 되기 전 데이터를 보여주는 게 일반적
트랜잭션이 시작되고 끝나기 전에는 이전데이터를 보여준다.
transaction 의 뜻은 일의 최소 단위를 의미한다.
일의 최소 단위는 상황마다 다르다.
tansaction은 망가질 수 있다. transaction이 파괴되면 DB는 기본적으로 rollback 을 한다.

5-4. Transaction 설정하기(@Transactional)
DB 의 변경 요청은 @Transactional 이 걸려있어야 한다.
걸려있는 동안 다른 사람이 보면 트랜잭션이 안끝났으니까 이전 데이터를 보고 있는것.
트랜잭션이 없을 때 발생할 수 있는 예시로, 통계 쿼리의 경우 만일 트랜잭션이 없다면 데이터가 다 꼬이게 된다. 한번 읽었던 데이터를 또 읽어야 하고, 변경된 데이터를 읽게 되면 유령데이터가 되기도 한다.
반드시 수정은 트랜잭션으로 고립시켜야한다.
내가 수정하고 있는 상태에서 누군가가 읽으러 들어왔을 때,일관되게 시스템이 만들어져있어야한다. 커밋 전의 데이터를 읽게 할 것인지, 커밋 후의 데이터를 읽게 할 것인지가 반드시 정립되어 있어야 한다.
Transaction
- 트랜잭션은 일의 최소 단위를 의미한다.
- 트랜잭션이 시작되면 고립성이 시작된다. write 가있기 때문에 데이터가 민감해진다. 내가 write 하고있을때 다른 사람은 write 하지 못하도록 원천 봉쇄시키는것.
ex. DB 의 3번째 row 를 손댈거야, 그 때 고립되어야함. 다른데이터는 손 대도 돼.
동시에 다른 행은 괜찮지만, 같은 행은 안되게 해 → 고립
transaction 을 걸어서 데이터를 고립시킨다.
read 는 읽는거니까 느려지지 않고 고립되지 않지만,
ex. 3번째 행에 transaction 이 걸렸어, 100명이 동일한 행을 수정하고 싶어, 1번째 애가 수정하고 있을 때 → 다른 애들이 wait 걸림 → 시스템이 느려짐 read 할 때는 commit 된 데이터를 볼거니까
(transaction 을 많이 하는게 좋지 않다.)
ex. 파라미터로 들어온 title 이 null 일 경우, 트랜잭션 걸리고 DB 에서 not null 설정이 되어있으면 이거 안돼~ 하고 값이 돌아오기 까지 한참이다. 이런 불필요한 작업이 생기지 않게, 고립성이 안되게 유효성 검사를 컨트롤러에서 해줘야 한다.
transaction 을 줄일 수 있다.
게시글 쓰기로 DB 에 insert 할 때, 꼭 @Transactional 붙이기
package shop.mtcoding.blog2.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository // @Repository 를 붙이면 스프링이 new 를 해서 IoC(컬렉션 List자료형 같은거) 에 저장한다.
public class BoardRepository {
@Autowired // IoC에 있는 객체를 찾아온다.
private EntityManager em;
// insert 하기
@Transactional
public void save(String title, String content) {
Query query = em.createNativeQuery("insert into board_tb(title, content, created_at) values(?,?,now())");
query.setParameter(1, title);
query.setParameter(2, content);
query.executeUpdate(); // em 이 위의 해당쿼리를 완성시켜서 전송함
} // insert 나 delete 할 때는 query.executeUpdate();
}
Share article