본문 바로가기
👨‍💻 2. 웹개발_Back end/2-6 Spring

[Spring] 7장 트랜잭션 처리 - 트랜잭션 어드바이스 설정과 AOP 설정을 통한 트랜잭션 적용 및 테스트

by 달님🌙 2021. 10. 27.
반응형

 

실습 코드 참조

 

moonhy7/SpringFramework: Spring Framework 실습 코드 정리 (github.com)

 

GitHub - moonhy7/SpringFramework: Spring Framework 실습 코드 정리

Spring Framework 실습 코드 정리. Contribute to moonhy7/SpringFramework development by creating an account on GitHub.

github.com

 

 

7.1절 트랜잭션 네임스페이스 등록

 

1. 스프링의 트랜잭션 설정 ( _050_BoardWeb_JDBC_Transaction )

 

1) 선언적 트랜잭션

- 스프링과 비교되는 EJB는 모든 비즈니스 메소드에 대한 트랜잭션 관리를 EJB 컨테이너가 자동으로 처리해준다.

- 스프링에서도 트랜잭션 처리를 컨테이너가 자동으로 처리하도록 설정할 수 있는데 이를 선언적 트랜잭션이라 함

 

2) 스프링의 트랜잭션 설정

- 스프링의 트랜잭션 설정에서는 XML 기반의 AOP 설정만 사용할 수 있고 어노테이션은 사용할 수 없다.

- 또한 <aop:aspect> 엘리먼트 대신 <aop:advisor> 엘리먼트를 사용해야 한다.

 

2. 트랜잭션 네임스페이스 등록

 

1) 트랜잭션 네임스페이스 등록

 

 

7.2절 트랜잭션 관리자 등록

 

1. PlatformTransactionManager 인터페이스

 

- 트랜잭션 관련 설정에서 가장 먼저 등록하는 것은 트랜잭션 관리자 클래스이다.

- 스프링은 다양한 트랜잭션 관리자를 지원하는데 어떤 기술을 이용하여 데이터베이스 연동을 처리했느냐에 따라 트랜잭션 관리자가 달라진다.

- 모든 트랜잭션 관리자는 PlatformTransactionManager 인터페이스를 구현한 클래스들이다.

- 따라서 스프링이 제공하는 모든 트랜잭션 관리자는 트랜잭션 관리에 필요한 commit(), rollback() 메소드를 가진다.

 

2. DataSourceTransactionManager 클래스

 

- 현재 우리가 가지고 있는 두 개의 DAO 클래스는 모두 JDBC를 기반으로 동작하기 떄문에 당분간 DataSourceTransactionManager 클래스를 이용하여 트랜잭션을 처리할 것이다.

- 예를 들어 JPA를 이용하여 DAO 클래스를 구현했다면 JPATransactionManager를 등록해주면 된다.

- 이제 트랜잭션을 커밋, 롤백하기 위한 객체로 DataSourceTransactionManager를 <bean> 등록해주어면 된다.

- 그 후 트랜잭션 관리자가 가지고 있는 메소드를 호출하면서 실질적인 트랜잭션 관리 기능을 제공하는 어드바이스를 등록해주면 된다.

<!-- DataSource 설정 -->
	<!-- 외부 properties파일 참조 -->
	<context:property-placeholder location="classpath:config/datasource.properties"/>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<!-- setter 메소드를 이용한 의존성 주입 -->
		<!-- Oracle -->
		<property name="driverClassName" value="${jdbc.driver}"></property>
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<!-- MySQl -->
		<!--  
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/studydb?serverTimezone=UTC"></property>
		<property name="username" value="study"></property>
		<property name="password" value="study"></property>
		-->
	</bean>
	
	<!-- JDBCTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- Transaction 설정 -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

 

 

7.3절 트랜잭션 어드바이스 설정

 

1. 트랜잭션 관리 어드바이스 설정

 

- 트랜잭션에선 commit이나  rollback에 어떤 어드바이스 메소드가 호출될지 모르기 때문에 어드바이스 메소드를 지정할 수 없고 aspect가 아닌 advisor로 aop 설정해야됨

- 지금까지의 AOP 관련 설정에 사용한 모든 어드바이스 클래스는 직접 구현했지만 트랜잭션 관리 기능의 어드바이스는 스프링 컨테이너가 <tx:advice> 설정을 참조하여 자동으로 생성한다.

- tx:attributes : Transaction advice 동작 시점을 설정, 포인트컷 지정없이 메소드 지정가능

- tx:method : advice 동작할 메소드 지정

- read-only : transaction이 동작하지 않음 (get으로 시작하는 메소드들에서는 트랜잭션 동작 X)

- get*으로 시작하는 메소드들을 제외한 모든 메소드들에서는 트랜잭션이 동작 O
 

<!-- Transaction 설정 -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- Trasaction advice 등록 -->
	<!-- 트랜잭션에선 commit이나  rollback 어떤 어드바이스 메소드가 호출될지 모르기 때문에 
    	어드바이스 메소드를 지정할 수 없고 aspect가 아닌 advisor로 aop 설정해야됨 -->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<!-- tx:attributes : Transaction advice 동작시점을 설정, 포인트컷 지정없이 메소드 지정가능-->
		<tx:attributes>
		<!-- 
		tx:method : advice 동작할 메소드 지정
		read-only : transaction이 동작하지 않음(get으로 시작하는 메소드들에서는 트랜잭션 동작x)
		get*으로 시작하는 메소드들을 제외한 모든 메소드들에서는 트랜잭션이 동작o
		 -->
			<tx:method name="get*" read-only="true"/>
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>

 

 

2. <tx:method> 속성 종류

 

 

 

7.4절 AOP 설정을 통한 트랜잭션 적용

 

1. 트랜잭션 관리 설정

 

- txPointcut으로 지정한 메소드가 호출될 때 txAdvice로 등록한 어드바이스가 동작하여 트랜잭션을 관리하도록 설정함

<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.springbook.biz..*(..))" id="txPointcut"/>
		<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
	</aop:config>

 

2. 스프링 설정 파일 전체 소스

 

	<!-- DataSource 설정 -->
	<!-- 외부 properties파일 참조 -->
	<context:property-placeholder location="classpath:config/datasource.properties"/>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<!-- setter 메소드를 이용한 의존성 주입 -->
		<!-- Oracle -->
		<property name="driverClassName" value="${jdbc.driver}"></property>
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<!-- MySQl -->
		<!--  
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/studydb?serverTimezone=UTC"></property>
		<property name="username" value="study"></property>
		<property name="password" value="study"></property>
		-->
	</bean>
	
	<!-- JDBCTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- Transaction 설정 -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- Trasaction advice 등록 -->
	<!-- 트랜잭션에선 commit이나  rollback 어떤 어드바이스 메소드가 호출될지 모르기 때문에 어드바이스 메소드를 지정할 수 없고
		 aspect가 아닌 advisor로 aop 설정해야됨 -->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<!-- tx:attributes : Transaction advice 동작시점을 설정, 포인트컷 지정없이 메소드 지정가능-->
		<tx:attributes>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.springbook.biz..*(..))" id="txPointcut"/>
		
		<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
	</aop:config>

 

 

3. 트랜잭션 설정 동작 순서

① 클라이언트가 BoardServiceImpl 객체의 insertBoard() 메소드 호출

② insertBoard() 메소드의 비즈니스 로직이 수행됨

③ insertBoard() 메소드 수행 중 문제 발생시  txAdvice로 등록한 어드바이스가 동작함

④ txAdvice가 참조하는 txManager의 rollback() 메소드를 호출함 (문제없이 정상적으로 수행되었다면 commit() 메소드 호출)

 

7.5절 트랜잭션 설정 테스트

 

1. BoardServiceClient 설정

 

- BoardServiceClient에서 명시적으로 100번 글을 등록하도록 설정

public class BoardServiceClient {
	public static void main(String[] args) {
		...
        
		//3. 글 등록 테스트
		BoardVO vo = new BoardVO();
		vo.setSeq(100);	// seq를 100으로 지정
		vo.setTitle("임시 제목");
		vo.setWriter("홍길동");
		vo.setContent("임시 내용.........");
		boardService.insertBoard(vo);

		...
	}
}

 

2. BoardServiceImpl 클래스 설정 에서 명시적으로 100번 글을 등록하도록

 

- BoardServiceImpl 클래스의 insertBoard() 메소드에서 boardDAOSpring의 insertBoard() 메소드를 연속으로 두 번 호출

- 첫 번째 입력은 성공하자먼 BOARD 테이블에 SEQ 컬럼이 기본키로 지정되어있으므로 두 번째 입력에서 예외 발생

- 트랜잭션은 메소드 단위로 관리되므로 발생한 예외 때문에 insertBoard() 메소드의 작업 결과는 모두 롤백 처리됨

@Service("boardService")
public class BoardServiceImpl implements BoardService{
	@Autowired
//	BoardDAO boardDAO;
	private BoardDAOSpring boardDAO;
	
	public void insertBoard(BoardVO vo) {		
		boardDAO.insertBoard(vo); // 100번 글 등록 성공
		boardDAO.insertBoard(vo); //예외 발생
	}
}

 

3. BoardDAOSpring 수정

 

- BoardServiceImpl 에서 BoardDAOSpring 객체의 insertBoard() 메소드를 호출하기 때문에 BoardDAOSpring의 insertBoard() 메소드와 SQL 구문을 수정한다.

- SEQ 컬럼을 자동 증가하도록 작성했던 걸 수정해서 사용자가 입력한 100번 글이 INSERT 되도록 수정한다.

//SQL 관련 명령어
	//private final String BOARD_INSERT = "INSERT INTO BOARD(SEQ, TITLE, WRITER, CONTENT) "
	//		                    + "VALUES((SELECT NVL(MAX(SEQ), 0) + 1 FROM BOARD), ?, ?, ?)";
	private final String BOARD_INSERT = "INSERT INTO BOARD(SEQ, TITLE, WRITER, CONTENT) "
            + "VALUES(?, ?, ?, ?)";

	//CRUD 기능의 메소드 구현
	//글 등록
	//getJdbcTemplate() : JdbcTemplate객체를 리턴. JdbcDaoSupport 클래스에서 상속받아 사용하는 메소드
	public void insertBoard(BoardVO vo) {
		System.out.println("====> JDBC로 insertBoard() 기능 처리");
		jdbcTemplate.update(BOARD_INSERT, vo.getSeq(), vo.getTitle(), vo.getWriter(), vo.getContent());
	}

 

4. 실행 결과

 

- SEQ는 primary key라서 100이라는 같은 데이터가 들어가게 되면 무결성 에러가 발생하게 됨

 

5. 실행 결과 - 롤백 확인

 

- insertBoard() 메소드 호출 부분을 주석처리하고 다시 글 목록 출력만 실행해보기

public class BoardServiceClient {
	public static void main(String[] args) {
		...
        
		//3. 글 등록 테스트
		BoardVO vo = new BoardVO();
		vo.setSeq(100);	// seq를 100으로 지정
		vo.setTitle("임시 제목");
		vo.setWriter("홍길동");
		vo.setContent("임시 내용.........");
//		boardService.insertBoard(vo);

		...
	}
}

 

- 글 목록에 100번 글이 입력되지 않은 것을 확인

- 아예 실행이 안되서 롤백이 된 상태로 100번째 글은 등록되지않음

 

 

반응형

댓글