Home > Spring > πŸƒ[Spring] `@Transactional`이 read λ©”μ„œλ“œμ— λΆ™μ–΄μžˆμ§€ μ•Šμ€ μ΄μœ λŠ” λ¬΄μ—‡μΌκΉŒμš”?

πŸƒ[Spring] `@Transactional`이 read λ©”μ„œλ“œμ— λΆ™μ–΄μžˆμ§€ μ•Šμ€ μ΄μœ λŠ” λ¬΄μ—‡μΌκΉŒμš”?
Spring Framework

πŸƒ[Spring] @Transactional이 read λ©”μ„œλ“œμ— λΆ™μ–΄μžˆμ§€ μ•Šμ€ μ΄μœ λŠ” λ¬΄μ—‡μΌκΉŒμš”?

πŸ“Œ Intro 및 μ½”λ“œ μ†Œκ°œ.

package kobe.board.article.service;

import kobe.board.article.entity.Article;
import kobe.board.article.repository.ArticleRepository;
import kobe.board.article.service.request.ArticleCreateRequest;
import kobe.board.article.service.request.ArticleUpdateRequest;
import kobe.board.article.service.response.ArticleResponse;
import kobe.board.common.exception.CustomException;
import kobe.board.common.responsecode.ResponseCode;
import kuke.board.common.snowflake.Snowflake;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ArticleService {
	private final Snowflake snowflake = new Snowflake();
	private final ArticleRepository articleRepository;

	@Transactional
	public ArticleResponse create(ArticleCreateRequest request) {
		ArticleResponse article = createArticle(request);
		return article;
	}

	private ArticleResponse createArticle(ArticleCreateRequest request) {
		// 1. κ²Œμ‹œκΈ€ 생성 및 μ €μž₯
		articleRepository.save(
			Article.create(snowflake.nextId(), request.getTitle(), request.getContent(), request.getBoardId(), request.getWriterId())
		);

		// 2. κ²Œμ‹œλ¬Ό μ—¬/λΆ€ 검증.
		Article article1 = articleRepository.findById(snowflake.nextId()).orElseThrow(() -> new CustomException(ResponseCode.NOT_FOUND_ARTICLE));

		// 3. ArticleResponse 리턴
		return ArticleResponse.from(article1, ResponseCode.SUCCESS_CREATE_ARTICLE);
	}

	@Transactional
	public ArticleResponse update(Long articleId, ArticleUpdateRequest request) {
		// 1. κ²Œμ‹œλ¬Ό μ—¬/λΆ€ 검증.
		Article article = articleRepository.findById(articleId).orElseThrow(() -> new CustomException(ResponseCode.NOT_FOUND_ARTICLE));
		
		// 2. κ²Œμ‹œλ¬Ό μ—…λ°μ΄νŠΈ.
		article.update(request.getTitle(), request.getContent());
		
		// 3. ArticleResponse 리턴.
		return ArticleResponse.from(article, ResponseCode.SUCCESS_UPDATE_ARTICLE);
	}


	public ArticleResponse read(Long articleId) {
		return ArticleResponse.from(articleRepository.findById(articleId).orElseThrow(), ResponseCode.SUCCESS_READ_ARTICLE);
	}

	@Transactional
	public void delete(Long articleId) {
		articleRepository.deleteById(articleId);
	}
}
  • β†˜οΈŽ μœ„ ν΄λž˜μŠ€λŠ” κ²Œμ‹œλ¬Ό μ„œλΉ„μŠ€ ν΄λž˜μŠ€μž„.
  • β†˜οΈŽ κ²Œμ‹œλ¬Ό 생성, μ—…λ°μ΄νŠΈ, μ‚­μ œ κ΄€λ ¨ λ©”μ„œλ“œμ—λŠ” @Transactional이 λΆ™μ–΄μžˆλŠ”λ° read λ©”μ„œλ“œμ—λŠ” λΆ™μ–΄μžˆμ§€ μ•ŠμŒ.

βœ…1️⃣ @Transactional이 read λ©”μ„œλ“œμ— λΆ™μ–΄μžˆμ§€ μ•Šμ€ 이유.

βœ…1️⃣ @Transactional κΈ°λ³Έ κ°œλ….

  • β†˜οΈŽ @Transactional : νŠΈλžœμž­μ…˜ 관리λ₯Ό μœ„ν•΄ μ‚¬μš©λ˜λ©°, 주둜 λ°μ΄ν„°λ² μ΄μŠ€μ˜ 일관성을 μœ μ§€ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ¨.
  • β†˜οΈŽ 읽기 μ „μš©(read-only) νŠΈλžœμž­μ…˜ : readOnly = true둜 μ„€μ •ν•˜λ©΄ 데이터 λ³€κ²½ 없이 쑰회만 μˆ˜ν–‰ν•˜κ²Œ 됨.

βœ…2️⃣ read λ©”μ„œλ“œμ— @Transactional이 ν•„μš”ν•˜μ§€ μ•Šμ€ 이유

1️⃣ 데이터 λ³€κ²½ μ—†μŒ.

  • β†˜οΈŽ read λ©”μ„œλ“œλŠ” λ‹¨μˆœνžˆ 데이터λ₯Ό 읽기만 ν•˜κ³  λ³€κ²½ν•˜μ§€ μ•ŠλŠ”λ‹€.
    • β†˜οΈŽ λ°μ΄ν„°λ² μ΄μŠ€μ— μ“°κΈ° μž‘μ—…(INSERT, UPDATE, DELETE)이 μ—†κΈ° λ•Œλ¬Έμ— νŠΈλžœμž­μ…˜μ΄ ν•„μš”ν•˜μ§€ μ•Šλ‹€.

      2️⃣ μ„±λŠ₯ μ΅œμ ν™”

  • β†˜οΈŽ νŠΈλžœμž­μ…˜μ€ λ¦¬μ†ŒμŠ€λ₯Ό μ†Œλͺ¨ν•¨.
    • β†˜οΈŽ λΆˆν•„μš”ν•œ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜λ©΄ μ˜€λ²„ν—€λ“œκ°€ λ°œμƒν•  수 있음.
  • β†˜οΈŽ read λ©”μ„œλ“œλŠ” νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•  ν•„μš”κ°€ μ—†μ–΄, 더 가볍고 λΉ λ₯΄κ²Œ μž‘λ™ν•¨.

    3️⃣ 기본적으둜 JPAλŠ” 읽기 μž‘μ—…μ—μ„œ νŠΈλžœμž­μ…˜μ΄ ν•„μš” μ—†μŒ

  • β†˜οΈŽ JPAλŠ” 읽기 μž‘μ—…λ§Œ μˆ˜ν–‰ν•  경우 기본적으둜 νŠΈλžœμž­μ…˜μ„ ν•„μš”λ‘œ ν•˜μ§€ μ•ŠμŒ.
  • β†˜οΈŽ findByIdλŠ” 읽기 μ „μš© μž‘μ—…μ΄κΈ° λ•Œλ¬Έμ— λ³„λ„μ˜ νŠΈλžœμž­μ…˜ 섀정이 ν•„μš” μ—†μŒ.

βœ…3️⃣ @Transactional(readOnly = true)λ₯Ό μ‚¬μš©ν•  경우.

  • β†˜οΈŽ @Transactional(readOnly = true)λ₯Ό μ‚¬μš©ν•˜λ©΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” 읽기 μ „μš©μœΌλ‘œ μ„€μ •λœλ‹€.
  • β†˜οΈŽ read λ©”μ„œλ“œλŠ” λͺ…μ‹œμ μœΌλ‘œ @Transactional(readOnly = true)λ₯Ό μΆ”κ°€ν•  경우, λ‹€μŒκ³Ό 같은 이점이 μžˆλ‹€.
    • β†˜οΈŽ μ„±λŠ₯ μ΅œμ ν™” : μ“°κΈ° 연산을 μ°¨λ‹¨ν•˜κ³ , 읽기 μ „μš©μœΌλ‘œ μ΅œμ ν™”λœλ‹€.
    • β†˜οΈŽ μ•ˆμ „μ„± : 잘λͺ»λœ μ“°κΈ° 연산이 λ°œμƒν•˜λŠ” 것을 λ°©μ§€ν•œλ‹€.

🎯 μ˜ˆμ‹œ: read λ©”μ„œλ“œμ— @Transactional(readOnly = true) 적용

@Transactional(readOnly = true)
public ArticleResponse read(Long articleId) {
    return Articleresponse.from(
        articleRepository.findById(articleId)
        .orElseThrow(
            () -> new CustomException(ResponseCode.NOT_FOUND_ARTICLE)
        ),
        ResponseCode.SUCCESS_READ_ARTICLE
    );
}

βœ…4️⃣ 상황별 @Transactional μ‚¬μš© μ—¬λΆ€.

λ©”μ„œλ“œ μœ ν˜• @Transactional ν•„μš” μ—¬λΆ€ 이유
Create ν•„μš”ν•¨ 데이터 λ³€κ²½ λ°œμƒ(INSERT)
Update ν•„μš”ν•¨ 데이터 λ³€κ²½ λ°œμƒ(UPDATE)
Delete ν•„μš”ν•¨ 데이터 λ³€κ²½ λ°œμƒ(DELETE)
Read(쑰회만) λΆˆν•„μš” 데이터 λ³€κ²½ μ—†μŒ(SELECT)
Read(λ³΅μž‘ν•œ 쑰회) @Transactional(readOnly = true) ꢌμž₯ 읽기 μ „μš© μ΅œμ ν™” 및 μ•ˆμ „μ„±

πŸš€ κ²°λ‘ .

  • β†˜οΈŽ read λ©”μ„œλ“œμ—λŠ” 기본적으둜 @Transactional이 ν•„μš”ν•˜μ§€ μ•ŠμŒ.
    • β†˜οΈŽ ν•˜μ§€λ§Œ 읽기 μ „μš©μœΌλ‘œ μ•ˆμ „μ„±μ„ 높이고 μ΅œμ ν™”λ₯Ό μ›ν•œλ‹€λ©΄ @Transactional(readOnly = true)λ₯Ό μ‚¬μš©ν•΄λ„ 됨.

πŸ‘‰ ꢌμž₯ 접근법.

  • β†˜οΈŽ λ‹¨μˆœ 쑰회라면 @Transactional을 μƒλž΅ν•΄λ„ 무방함.
  • β†˜οΈŽ 읽기 μ΅œμ ν™”λ‚˜ μΆ”κ°€ μ•ˆμ „μ„±μ΄ ν•„μš”ν•˜λ‹€λ©΄ @Transactional(readOnly = true)λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŒ.