[Spring] Transaction 설정

username-bb·2021년 11월 25일
0

목차

1. Transaction 이란?
2. 그래서 내 프로젝트엔 어떻게 반영할 것인가
3. XML 파일에 transaction 설정 반영하기
4. 왜 이렇게 설정하게 됐을까?
5. 그래서...
6. 도움을 주신 감사한 블로그들...



OJT 프로젝트를 진행하는데... transaction 설정 관련해서 피드백을 받았다. 그래서 트랜잭션 설정을 Spring에 추가해보기로 했다.

1. Transaction 이란?


기사 공부할 때 외웠었는데... 합격하고 나서 다 까먹었다. 그래서 다시 찾아봤다. transaction은 더이상 쪼개질 수 없는 하나의 논리적인 기능을 이야기하는데 일반적으로 은행 입출금을 예로 많이 들어 설명한다.

A의 통장에서 B의 통장으로 100만원이 이체되는 기능을 구현할 때,
(1) A의 통장에서 이루어지는 100만원의 출금과 (2) B의 통장에서 이루어지는 100만원의 입금으로 메서드를 나눌 수 있다.
하지만 (1)메서드와 (2)메서드는 이체라는 하나의 기능을 수행한다. 따라서 (1) 또는 (2)의 메서드가 수행을 실패하면 이체라는 기능 자체는 전체가 rollback되어야 한다는 논리이다.

유식하게 말하면 All Or Nothing이라고도 표현한다고 한다.



2. 그래서 내 프로젝트엔 어떻게 반영할 것인가


내 프로젝트엔 게시판이 있다. 게시글 정보를 DB에 전달한 후, 사용자에게 결과 페이지를 보여주는 과정 도중에 강제로 Exception을 일으켜보면 결과 페이지 자체는 Exception 때문에 에러 페이지로 보여지지만 막상 DB를 열어보면 관련 게시글 정보는 제대로 INSERT 되어있는 것을 확인할 수 있었다. 이것을 기준 삼아서 transaction 설정을 반영해보기로 했다.

transaction test code는 다음과 같다

//커뮤니티 게시글 등록
	@Override
	public int insertCommunity(Community community) throws Exception {
		communityDao.insertCommunity(sqlSession, community);
		throw new Exception("exception 발생");
		//community 객체에 있는 data를 insert해주기 위해 community 객체를 전달
		//return communityDao.insertCommunity(sqlSession, community);
	}



3. XML 파일에 transaction 설정 반영하기


transaction 설정을 반영하기 위해 두 번의 시도가 있었고 두번째 방법으로 프로젝트에 최종 반영했다.

3-1. servlet-context.xml에 설정하기

  • pom.xml에 트랜잭션 설정을 위한 dependency를 추가한다.
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>
		
		<!-- 트랜잭션 설정 -->
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>aspectjweaver</artifactId>
		    <version>1.9.7</version>
		    <scope>runtime</scope>
		</dependency>

  • servlet-context.xml에 transaction설정을 한다.
	<!-- 트랜잭션 설정 -->
	<beans:bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<beans:property name="dataSource" ref="dataSource"></beans:property>
	</beans:bean>
	
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 이 이름으로 시작하는 method를 트랙잭션으로 설정함 -->
			<!-- 어디에 있는 메서드인지는 config 태그에서 설정 -->
			<tx:method name="count*" read-only="true" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="get*" read-only="true" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="insert*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="increase*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="delete*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="update*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 여기 있는 메서드에 트랜잭션 설정을 해주세요 -->
	<!-- 현재는 serviceImpl에 있는 메서드에 트랜잭션 설정이 걸려있다. -->
	<!-- 따라서  servuceImpl수준에서 발생한 Exception에 대해서 rollback하게 된다. -->
	<aop:config>
		<aop:pointcut expression="execution(* com.XXX.YYY..*ServiceImpl.*(..))" id="serviceMethod"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" id="transactionAdvisor"/>
	</aop:config>


3-2. root-context.xml에 설정하기

  • pom.xml에 트랜잭션 설정을 위한 dependency를 추가한다.
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>
		
		<!-- 트랜잭션 설정 -->
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>aspectjweaver</artifactId>
		    <version>1.9.7</version>
		    <scope>runtime</scope>
		</dependency>

  • root-context.xml에 transaction 설정을 한다.
	<!-- 트랜잭션 설정 -->
	<!-- 특정 package만을 scan만 한다. -->
	<context:component-scan base-package="com.XXX.YYY" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>
	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 이 이름으로 시작하는 method를 트랙잭션으로 설정함 -->
			<!-- 어디에 있는 메서드인지는 config 태그에서 설정 -->
			<tx:method name="count*" read-only="true" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="get*" read-only="true" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="insert*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="increase*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="delete*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
			<tx:method name="update*" rollback-for="Exception" propagation="REQUIRED" isolation="READ_COMMITTED"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 여기 있는 메서드에 트랜잭션 설정을 해주세요 -->
	<!-- 현재는 serviceImpl에 있는 메서드에 트랜잭션 설정이 걸려있다. -->
	<!-- 따라서  servuceImpl수준에서 발생한 Exception에 대해서 rollback하게 된다. -->
	<aop:config>
		<aop:pointcut expression="execution(* com.XXX.YYY..*ServiceImpl.*(..))" id="serviceMethod"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" id="transactionAdvisor"/>
	</aop:config>

  • servlet-context.xml에서 package scan 설정을 변경 해준다.
	<!-- 특정 package만을 scan에서 제외 한다. -->
	<context:component-scan base-package="com.XXX.YYY">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>



4. 왜 이렇게 설정하게 됐을까?


고작 transaction 설정일 뿐인데... 나름대로 이것을 설정하는 과정 중에 다양한 걸 배웠다.

  • root-context.xml에서는 servelt-context.xml에서 등록된 bean에 접근할 수 없다.

  • servlet-context.xml에서는 root-contet.xml에서 등록된 bean에 접근할 수 있다.

위와 같은 사유로

root-context.xml에서 transaction 설정을 해줄 때는 transaction에 사용되는 package를 scan하는 설정을 넣어주어야한다. 그렇지 않으면 transaction 설정이 먹질 않는다.
(servlet-context.xml에서 scan한 package에는 접근할 수 없기 때문에...)

위에 해당되는 어노테이션은 @Service와 @Repository이다.
@Service가 달린 class는 transaction 설정과 직결된 class이기 때문이고 @Service가 달린 class에서 @Autowired 로 Dao class를 사용하기 때문에 @Repository도 @Service와 동일하게 package scan에 설정해줘야했다.



5. 그래서...


현재까진 이렇게 설정돼있는 상태인데... 이게 맞는지 모르겠다. 나중에 업데이트 될 일이 있을 수도 있겠다 싶다....


6. 도움을 주신 감사한 블로그들...


  1. https://www.egovframe.go.kr/home/qainfo/qainfoRead.do?menuNo=69&qaId=QA_00000000000015945
  2. https://sinna94.tistory.com/entry/Spring-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0
  3. https://mangkyu.tistory.com/154
  4. https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=writer0713&logNo=220579572336
  5. https://granya.tistory.com/43
  6. https://seongilman.tistory.com/119
  7. https://velog.io/@hyun-jii/스프링-component-scan-개념-및-동작-과정
  8. https://velog.io/@chb1828/root-context와-servlet-context-에서의-component-scan의-방법
  9. https://zzangprogrammer.tistory.com/214
profile
공부하는 주니어

0개의 댓글