๐Ÿƒ ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ ์†์„ฑ, ๊ฒฉ๋ฆฌ์ˆ˜์ค€ (in Spring @Transactional)

 

1. @Transactional

@Transactional ์–ด๋…ธํ…Œ์ด์…˜์€ ํŠน์ • method์— ํŠธ๋žœ์žญ์…˜์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ ์†์„ฑ, ๊ฒฉ๋ฆฌ ์ˆ˜์ค€, timeout, read-only, rollback conditions์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

1.1 @Transactional ๋™์ž‘ ๋ฐฉ์‹

์Šคํ”„๋ง์€ ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„์„ค์ •๊ณผ ๊ฐ™์€ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด Proxy๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋ฐ”์ดํŠธ ์ฝ”๋“œ ์กฐ์ž‘๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ๊ธฐ์ˆ ์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

1.2 @Transactional ์‚ฌ์šฉ๋ฒ•

interface, class์˜ ์„ ์–ธ๋ถ€ ๋˜๋Š” method ์„ ์–ธ๋ถ€์— ์ง์ ‘ ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

interface, class์˜ ์„ ์–ธ๋ถ€์— ์‚ฌ์šฉ๋  ๊ฒฝ์šฐ public method์— ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋˜๋ฉฐ, private ๋˜๋Š” protected method์— ์ง์ ‘ @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ค์ •ํ•˜๋”๋ผ๋„ ์Šคํ”„๋ง์€ ์ด๋ฅผ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ interface ์„ ์–ธ๋ถ€์— ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Spring Data Repository Interface(@Repository)๋กœ ์„ค์ •๋œ interface์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Transactional
public interface UserService {
	void add(User user);
}

2. ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ(Transaction Propagation)

2.1 ํŠธ๋žœ์žญ์…˜์ด๋ž€?

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜(Database Transaction)์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๋˜๋Š” ์œ ์‚ฌํ•œ ์‹œ์Šคํ…œ์—์„œ ์ƒํ˜ธ์ž‘์šฉ์˜ ๋‹จ์œ„์ด๋‹ค. ์—ฌ๊ธฐ์„œ ์œ ์‚ฌํ•œ ์‹œ์Šคํ…œ์ด๋ž€ ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต๊ณผ ์‹คํŒจ๊ฐ€ ๋ถ„๋ช…ํ•˜๊ณ  ์ƒํ˜ธ ๋…๋ฆฝ์ ์ด๋ฉฐ, ์ผ๊ด€๋˜๊ณ  ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์‹œ์Šคํ…œ์„ ์˜๋ฏธํ•œ๋‹ค.

์ด๋ก ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ์€ ๊ฐ๊ฐ์˜ ํŠธ๋žœ์žญ์…˜์— ๋Œ€ํ•ด ์›์ž์„ฑ(Atomicity), ์ผ๊ด€์„ฑ(Consistency), ๋…๋ฆฝ์„ฑ(Isolation), ์˜๊ตฌ์„ฑ(Durability)์„ ๋ณด์žฅํ•œ๋‹ค. ์ด ์„ฑ์งˆ์„ ์ฒซ๊ธ€์ž๋ฅผ ๋”ฐย ACID๋ผ ๋ถ€๋ฅธ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์‹ค์ œ๋กœ๋Š” ์„ฑ๋Šฅํ–ฅ์ƒ์„ ์œ„ํ•ด ์ด๋Ÿฐ ํŠน์„ฑ๋“ค์ด ์ข…์ข… ์™„ํ™”๋˜๊ณค ํ•œ๋‹ค.

์–ด๋–ค ์‹œ์Šคํ…œ๋“ค์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜๋“ค์€ ๋…ผ๋ฆฌ์  ์ž‘์—… ๋‹จ์œ„(LUW, Logical Units of Work)๋กœ ๋ถˆ๋ฆฐ๋‹ค. [์œ„ํ‚ค๋ฐฑ๊ณผ]

2.2 ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ๋ž€?

ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„์—์„œ ์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ธ ํŠธ๋žœ์žญ์…˜์˜ ์œ ๋ฌด์— ๋”ฐ๋ผ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•  ๊ฒƒ์ธ๊ฐ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์Šคํ”„๋ง์€ ์ „ํŒŒ ์†์„ฑ์„ ์ฐธ์กฐํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ  ์ผ์‹œ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค.

2.3 ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ ์†์„ฑ

  • PROPAGATION_REQUIRED(DefaultTransactionDefinition): default ์ „ํŒŒ ์†์„ฑ์ด๋ฉฐ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜์ด ์—†์œผ๋ฉด ์ƒˆ๋กœ ์‹œ์ž‘ํ•˜๊ณ , ์žˆ์œผ๋ฉด ์ฐธ์—ฌํ•ฉ๋‹ˆ๋‹ค.
    ์ฝ”๋“œ๋ฅผ ์ง„ํ–‰ํ•˜๋˜ ์ค‘์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ์ง„ํ–‰ ์‚ฌํ•ญ์ด ๋ชจ๋‘ Rollback ๋ฉ๋‹ˆ๋‹ค.

      @Transactional(propagation = Propagation.REQUIRED)
      public void requiredMethod(String s) {
          ...
      }
        
      @Transaction
      public void requiredMethod(String s) {
          ...
      }
    
  • PROPAGATION_SUPPORTS: ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜์ด ์žˆ์œผ๋ฉด ์ฐธ์—ฌํ•˜๊ณ , ์—†์œผ๋ฉด ํŠธ๋žœ์žญ์…˜ ์—†์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

      @Transaction(propagation = Propagation.SUPPORTS)
      public void supportsMethod(String s) {
          ...
      }
    
  • PROPAGATION_MANDATORY: ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜์ด ์žˆ์œผ๋ฉด ์ฐธ์—ฌํ•˜๊ณ , ์—†์œผ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

      @Transactional(propagation = Propagation.MANDATORY)
      public void mandatoryMethod(String s) {
          ...
      }
    
  • PROPAGATION_NEVER: ํŠธ๋žœ์žญ์…˜์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜์ด ์žˆ์œผ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

      @Transactional(propagation = Propagation.NEVER)
      public void neverMethod(String s) {
          ...
      }
    
  • PROPAGATION_NOT_SUPPORTED: ํŠธ๋žœ์žญ์…˜์ด ์žˆ์œผ๋ฉด, ๋จผ์ € ์Šคํ”„๋ง์€ ํŠธ๋žœ์žญ์…˜์„ ์ผ์‹œ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์žญ์…˜ ์—†์ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

      @Transactional(propagation = Propagation.NOT_SUPPORTED)
      public void notSupportedMethod(String user) { 
          ...
      }
    
  • PROPAGATION_REQUIRES_NEW: ํ•ญ์ƒ ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ์ง„ํ–‰ํ•˜๋˜ ์ค‘์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ(Rollback)ํ•˜๋”๋ผ๋„ ๊ฐ๊ฐ์˜ ํŠธ๋žœ์žญ์…˜์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

      @Transactional(propagation = Propagation.REQUIRES_NEW)
      public void requiresNewMethod(String user) { 
          ...
      }
    
  • PROPAGATION_NESTED: ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜์ด ์žˆ์œผ๋ฉด, ๋จผ์ € Savepoint๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์ค‘์ฒฉ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ค‘์ฒฉ ํŠธ๋žœ์žญ์…˜์—์„œ Rollback์ด ๋ฐœ์ƒํ•˜๋ฉด Savepoint๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜์ด Commit ๋  ๋•Œ ํ•จ๊ป˜ Commit ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํŠธ๋žœ์žญ์…˜์ด ์—†์œผ๋ฉด PROPAGATION_REQUIRED์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

      @Transactional(propagation = Propagation.NESTED)
      public void nestedMethod(String user) { 
          ...
      }
    

3. ๊ฒฉ๋ฆฌ์ˆ˜์ค€ (Transaction Isolation)

์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ๋Š” ๋งŽ์€ ์ˆ˜์˜ ํŠธ๋žœ์žญ์…˜์ด ์ง„ํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ํŠธ๋žœ์žญ์…˜์„ ๋…๋ฆฝ์ ์œผ๋กœ ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ข‹๊ฒ ์ง€๋งŒ, ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ๋–จ์–ด์ง‘๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ ์ ˆํ•œ ๊ฒฉ๋ฆฌ์ˆ˜์ค€์„ ์„ค์ •ํ•˜์—ฌ ๊ฐ€๋Šฅํ•œ ๋งŽ์€ ํŠธ๋žœ์žญ์…˜์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ๋„ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๊ฒฉ๋ฆฌ์ˆ˜์ค€์€ ๊ธฐ๋ณธ์ ์œผ๋กœ DB์— ์„ค์ •๋˜์–ด ์žˆ์ง€๋งŒ JDBC ๋“œ๋ผ์ด๋ฒ„๋‚˜ DataSource์—์„œ ๋‹ค์‹œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3.1 ๊ฒฉ๋ฆฌ์ˆ˜์ค€์— ๋”ฐ๋ผ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” Side Effects (๋ฐ์ดํ„ฐ ๋ถ€์ •ํ•ฉ)

  • Dirty read: ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š” ํŠธ๋žœ์žญ์…˜์˜ ์ปค๋ฐ‹๋˜์ง€ ์•Š์€ ๋ณ€๊ฒฝ์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

  • Nonrepeatable read: ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์กฐํšŒํ–ˆ์„ ๋•Œ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ€ ์กฐํšŒ๋˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๋™์‹œ์— ์ง„ํ–‰๋˜๋Š” ํŠธ๋žœ์žญ์…˜์ด ๋™์ผํ•œ Row๋ฅผ Update & Commit ํ•  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

  • Phantom read: ํŠน์ • ๋ฒ”์œ„๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•  ๋•Œ ๊ฒฐ๊ณผ Row ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ํ•ด๋‹น ๋ฒ”์œ„์— ์†ํ•˜๋Š” ํ–‰์„ ์ถ”๊ฐ€ ๋˜๋Š” ์ œ๊ฑฐํ•˜๊ณ  Commit ํ•  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

3.2 ๊ฒฉ๋ฆฌ์ˆ˜์ค€์˜ ์ข…๋ฅ˜

  • READ_UNCOMMITTED: ๊ฐ€์žฅ ๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค. Commit ๋˜์ง€ ์•Š์€ ๋ณ€๊ฒฝ์„ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋ถ€์ •ํ•ฉ ๋ฐœ์ƒ ์œ„ํ—˜์ด ๋†’์ง€๋งŒ ์„ฑ๋Šฅ์ด ๊ฐ€์žฅ ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. Oracle, Postgres์—์„œ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

      @Transactional(isolation = Isolation.READ_UNCOMMITTED)
      public void log(String m) {
          ...
      }
    
    • ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” Side Effects:
      • Dirty read
      • Non-Repeatable read
      • Phantom read
  • READ_COMMITTED: Second Level ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค. Commit ๋œ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Dirty read๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Postgres, SQL Server, Oracle์—์„œ default ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค.

      @Transactional(isolation = Isolation.READ_COMMITTED)
      public void log(String m) {
          ...
      }
    
    • ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” Side Effects:
      • Nonrepeatable read
      • Phantom read
  • REPEATABLE_READ: Third Level ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋˜์ง€ ์ „์— ์ปค๋ฐ‹๋œ ๋ฐ์ดํ„ฐ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Dirty read, Non-Repeatable read๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. MySQL์—์„œ default ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ด๋ฉฐ, Oracle์—์„œ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

      @Transactional(isolation = Isolation.REPEATABLE_READ)
      public void log(String m) {
          ...
      }
    
    • ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” Side Effects:
      • Phantom read
  • SERIALIZABLE: ๊ฐ€์žฅ ์—„๊ฒฉํ•œ ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์–ด๋– ํ•œ ๋ฐ์ดํ„ฐ ๋ถ€์ •ํ•ฉ๋„ ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ, ๋™์‹œ ์ฒ˜๋ฆฌ ์„ฑ๋Šฅ์ด ๋งค์šฐ ๋–จ์–ด์ง‘๋‹ˆ๋‹ค.

      @Transactional(isolation = Isolation.SERIALIZABLE)
      public void log(String m) {
          ...
      }
    

4. ์ œํ•œ์‹œ๊ฐ„

ํŠธ๋žœ์žญ์…˜์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์ œํ•œ์‹œ๊ฐ„์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PROPAGATION_REQUIRED๋‚˜ PROPAGATION_REQUIRES_NEW์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ ์˜๋ฏธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ์ฝ๊ธฐ์ „์šฉ (Read Only)

์ฝ๊ธฐ์ „์šฉ์œผ๋กœ ์„ค์ •ํ•  ๊ฒฝ์šฐ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ์‹œ๋„๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



References
https://www.baeldung.com