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)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ.