테스트를 위해 테이블 b1 생성

 

 

@Transactional 없을 때

 

 

TxService.java

 

package com.jcy.usedhunter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TxService {
	@Autowired A1Dao a1Dao;
	@Autowired B1Dao b1Dao;
	
	public void insertA1WithoutTx() throws Exception {
		a1Dao.insert(1, 100);
		a1Dao.insert(1, 200);
		
	}

}

 

TxServiceTest.java

package com.jcy.usedhunter;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class TxServiceTest {
	@Autowired
	TxService txService;

	@Test
	public void InsertA1WithoutTxTest() throws Exception{
		txService.insertA1WithoutTx();
	}

}
conn = com.mysql.cj.jdbc.ConnectionImpl@78411116
conn = com.mysql.cj.jdbc.ConnectionImpl@2ca6546f

 

 

 

@Transactional 있을 때

 

같은 key 값이 주어 졌으니 실행 시 rollback 되서 저장된 값이 없어야 한다.

하지만 같은 Connection 임에도 rollback 이 되지 않았다.

 

TxService.java

package com.jcy.usedhunter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TxService {
	@Autowired A1Dao a1Dao;
	@Autowired B1Dao b1Dao;
	
	public void insertA1WithoutTx() throws Exception {
		a1Dao.insert(1, 100);
		a1Dao.insert(1, 200);
	}
	
	@Transactional
	public void insertA1WithTx() throws Exception {
		a1Dao.insert(1, 100);
		a1Dao.insert(1, 200);
	}

}

 

TxService.java

package com.jcy.usedhunter;

import static org.junit.Assert.*;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class TxServiceTest {
	@Autowired
	TxService txService;

	@Ignore
	@Test
	public void InsertA1WithoutTxTest() throws Exception{
		txService.insertA1WithoutTx();;
	}
	
	@Test
	public void InsertA1WithTxTest() throws Exception{
		txService.insertA1WithTx();;
	}

}
conn = com.mysql.cj.jdbc.ConnectionImpl@5fb7183b
conn = com.mysql.cj.jdbc.ConnectionImpl@5fb7183b

 

 

해결 방법

 

@Transactional(rollbackFor = Exception.class) 을 어노테이션에 추가해주자

@Transactional 어노테이션은 RuntimeException, Error 만 rollback 한다. 그래서 따로 Exception.class 를 추가 해주어야 한다.

 

 

 

 

@Transactional 의 속성

 

속성 설명
propagation Tx의 경계(boundary) 를 설정하는 방법을 지정
isolation Tx의 isolation level 을 지정. DEFAULT(DB 설정을 따름), READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
readOnly Tx 가 데이터를 읽기만 하는 경우, True 로 설정하면 성능이 향상
rollbackFor 지정된 예외가 발생하면 , Tx을 rollback 하고 RuntimeException 과 Error 는 자동 rollback
noRollbackFor 지정된 예외가 발생해도 , Tx 를 rollback 하지 않음
timeout 지정된 시간(초) 내에 Tx 이 종료되지 않으면, Tx 를 강제종료

 

 

propagation 속성의 값

 

설명
REQUIRED Tx 이 진행 중이면 참여하고, 없으면 새로운 Tx 시작(디폴트)
REQUIRED_NEW Tx 이 진행 중이건 아니건, 새로운 Tx 시작 ( Tx 안에 다른 Tx)
NESTED Tx 이 진행 중이면, Tx의 내부 Tx 로 실행 (Tx 안에 subTx, save point)
MANDATORY 반드시 진행 중인 Tx 내에서만 실행가능. 아니면 예외 발생
SUPPORTS Tx 이 진행 중이건 아니건 상관없이 실행
NOT_SUPPORTED Tx 없이 처리, Tx 이 진행 중이면 잠시 중단(suspend)
NEVER Tx 없이 처리. Tx 이 진행 중이면 예외 발생

 

 

 

REQUIRED

 

 

 

REQUIRED_NEW

 

REQUIRED_NEW 테스트

 

실행하면 B1 의 값은 다 저장되고, A1 은 rollback 되어야 한다.

package com.jcy.usedhunter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TxService {
	@Autowired A1Dao a1Dao;
	@Autowired B1Dao b1Dao;
	
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public void insertA1WithTx() throws Exception {
		a1Dao.insert(1, 100); // A1
		insertB1WithTx();
		a1Dao.insert(1, 200); // A2
	}
	
	@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
	public void insertB1WithTx() throws Exception {
		b1Dao.insert(1, 100); // B1
		b1Dao.insert(2, 200); // B2
	}

}
package com.jcy.usedhunter;

import static org.junit.Assert.*;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class TxServiceTest {
	@Autowired
	TxService txService;


	
	@Test
	public void InsertA1WithTxTest() throws Exception{
		txService.insertA1WithTx();
	}

}

 

하지만 같은 모두 같은 Connection 이며, A1, B1 값도 모두 rollback 되었다.

conn = com.mysql.cj.jdbc.ConnectionImpl@5f80fa43
conn = com.mysql.cj.jdbc.ConnectionImpl@5f80fa43
conn = com.mysql.cj.jdbc.ConnectionImpl@5f80fa43

 

 

해결

 

@Transactional 이 동작하지 않는 이유는 같은 클래스에 속한 메서드 끼리의 호출(내부 호출) 이기 때문이다.
프록시 방식(Default)의 AOP는 내부 호출인 경우, Advice 가 적용되지 않는다. 그래서 Tx가 적용 되지 않는다.
따라서 두 메서드를 별도의 클래스로 분리하면 Tx 가 적용된다.
근본적인 해결 방법은 프록시 방식이 아닌 다른 방식을 사용 해야한다.

 

TxService2 를 만들어 메서드를 분리 했다. A1은 rollback 되고 B1의 값은 저장되었다.

 

TxService2.java

package com.jcy.usedhunter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TxService2 {
	@Autowired B1Dao b1Dao;
	
	@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
	public void insertB1WithTx() throws Exception {
		b1Dao.insert(1, 100); // B1
		b1Dao.insert(2, 200); // B2
	}

}

 

TxService.java

package com.jcy.usedhunter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TxService {
	@Autowired A1Dao a1Dao;
	@Autowired TxService2 txService2;
	
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public void insertA1WithTx() throws Exception {
		a1Dao.insert(1, 100); // A1
		txService2.insertB1WithTx();
		a1Dao.insert(1, 200);
	}
	

}
conn = com.mysql.cj.jdbc.ConnectionImpl@6e106680
conn = com.mysql.cj.jdbc.ConnectionImpl@6d4c273c
conn = com.mysql.cj.jdbc.ConnectionImpl@6d4c273c
conn = com.mysql.cj.jdbc.ConnectionImpl@6e106680

 

'Spring > Ch3. Spring DI, AOP' 카테고리의 다른 글

Spring - 서비스 계층의 분리와 @Transactional  (0) 2022.09.30
Spring - AOP  (0) 2022.09.30
Spring - Transaction  (0) 2022.09.29
Spring DI - Spring DI 흉내내기  (0) 2022.09.25
Spring - Spring DI(1)  (0) 2022.09.13
복사했습니다!