본문 바로가기

자바 웹/스프링

스프링 트랜잭션

728x90

트랜잭션 기능


스프링은 트랜잭션 기능을 마이바티스 기능과 연동해서 사용한다.

 

트랜잭션 기능은 XML 파일에서 설정하는 방법과 애너테이션을 사용하는 방법이 있으며, 현재는 애너테이션으로 트랜잭션을 적용하는 방법이 선호된다.

 

트랜잭션은 여러개의 DML 명령문을 하나의 논리적인 작업 단위로 묶어서 관리하는 것으로, All 또는 Nothing 방식으로 작업 단위가 처리된다. 

즉, SQL 명령문들이 모두 정상적으로 처리되었다면 모든 작업의 결과를 테이터베이스에 반영하지만 그 중 하나라도 잘못된 것이 있으면 모두 취소한다.

 

예를 들어 Service 클래스에서 각 메서드가 애플리케이션의 단위 기능을 수행할 때 여러 개의 SQL문을 묶어서 작업을 처리하는 단위 기능이 있을 것이다. 이러한 경우에 어느 하나의 SQL문이라도 잘못되면 이전에 수행한 모든 작업을 취소해야 작업의 일관성이 유지된다. 따라서 트랜잭션은 각 단위 기능 수행 시 이와 관련된 데이터베이스 연동 작업을 한꺼번에 묶어서 관리한다는 개념이다.

 

보통 웹 애플리케이션에서 묶어서 처리하는 단위 기능은 아래와 같다.

  • 게시판 글 조회 시 해당 글을 조회하는 기능과 조회 수를 갱신하는 기능
  • 쇼핑몰에서 상품 주문 시 주문 상품을 테이블에 등록하는 기능과 주문자의 포인트를 갱신하는 기능
  • 은행에서 송금 시 송금자의 잔고를 갱신하는 기능과 수신자의 잔고를 갱신하는 기능

 

스프링의 트랜잭션 속성


스프링의 여러가지 트랜잭션 속성들

속성 기능
propagation 트랜잭션 전파 규칙 설정
isolation 트랜잭션 격리 레벨 설정
readOnly 읽기 전용 여부 설정
rollbackFor 트랜잭션을 롤백할 예외 타입 설정
norollbackFor 트랜잭션을 롤백하지 않을 예외 타입 설정
timeout 트랜잭션 타임아웃 시간 설정

 

propagation 속성이 가지는 값

의미
REQUIRED - 트랜잭션 필요, 진행 중인 트랜잭션이 있는 경우 해당 트랜잭션 사용
- 트랜잭션이 없으면 새로운 트랜잭션 생성, 디폴트 값
MANDATORY - 트랜잭션 필요
- 진행 중인 트랜잭션 없는 경우 예외 발생
REQUIRED_NEW - 항상 새로운 트랜잭션 생성
- 진행 중인 트랜잭션이 있는 경우 기존 트랜잭션을 일시 중지 시킨 후 새로운 트랜잭션 시작
- 새로 시작된 트랜잭션이 종료되면 기존 트랜잭션 계속 진행
SUPPORTS - 트랜잭션 필요 없음
- 진행 중인 트랜잭션이 있는 경우 해당 트랜잭션 사용
NOT_SUPPORTED - 트랜잭션 필요 없음
- 진행 중인 트랜잭션이 있는 경우 기존 트랜잭션을 일시중지 시킨 후 메서드 실행
- 메서드 실행이 종료되면 기존 트랜잭션 계속 진행
NEVER - 트랜잭션 필요 없음
- 진행 중인 트랜잭션 있는 경우 예외 발생
NESTED - 트랜잭션 필요
- 진행 중인 트랜잭션이 있는 경우 기존 트랜잭션에 중첩된 트랜잭션에서 메서드 실행
- 트랜잭션 없으면 새로운 트랜잭션 생성

 

isolation 속성이 가지는 값

기능
DEFAULT 데이터베이스에서 제공하는 기본 설정 사용
READ_UNCOMMITED 다른 트랜잭션에서 커밋하지 않은 데이터 읽기 가능
READ_COMMITED 커밋한 데이터만 읽기 가능
REPEATABLE_READ 현재 트랜잭션에서 데이터를 수정하지 않았다면 처음 읽어온 데이터와 두 번째 읽어온 데이터가 동일
SERIALIZABLE 같은 데이터에 대해 한 개의 트랜잭션만 수행

 

스프링 트랜잭션 기능 적용해 계좌 이체 실습


create table cust_account(
    accountNo VARCHAR2(20) PRIMARY KEY, --계좌 번호
    custName VARCHAR2(50), --예금자
    balance number(20,4) --계좌 잔고
);

insert into cust_account(accountNo, custName, balance) values('70-490-930','홍길동',1000000);
insert into cust_account(accountNo, custName, balance) values('70-490-911','김유신',1000000);
commit;

 

먼저 위와 같이 테이블을 생성하고 데이터를 넣어준다.

 

 

 

트랜잭션 관련 XML 파일 설정

 

action-servlet.xml

- 뷰 관련 빈과 각 URL 요청명에 대해 호출될 메서드들을 설정

 

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	
		
	<bean id="viewResolver"   
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="viewClass" 
                          value="org.springframework.web.servlet.view.JstlView"/>
      <property name="prefix" value="/WEB-INF/views/account/" /> 
      <property name="suffix" value=".jsp"/>
   </bean>
   
   	<bean id="accController" class="com.spring.account.AccountController">
      <property name="methodNameResolver">
         <ref local="methodResolver"/>
      </property>
      <property name="accService" ref="accService"/>
	</bean>

   <bean  id="methodResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver" >
      <property  name="mappings" >
        <props>
        	<!-- /account/sendMoney.do로 요청하면 sendMoney메서드 호출 -->
          <prop key="/account/sendMoney.do" >sendMoney</prop>
       </props>
      </property>
   </bean>
   
   
   <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
      <property name="mappings">
         <props>
         	<!-- /account/*.do로 요청하면 accController 빈 실행 -->
            <prop key="/account/*.do">accController</prop>
         </props>
      </property>
   </bean>
   
</beans>

 

action-mybatis.xml

  • 스프링의 DataSourceTransactionManager 클래스를 이용해 트랜잭션 처리 빈을 생성한 후 dataSource 속성에 dataSource 빈을 주입하여 데이터베이스 연동 시 트랜잭션을 적용
  • txManager 빈에 <tx:annotation-driven>태그를 설정해 애너테이션 적용할 수 있도록 설정

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

	<bean id="propertyPlaceholderConfigurer"
	class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
	<value>/WEB-INF/config/jdbc.properties</value>
	</property>
	</bean>

	<bean id="dataSource"	class="org.apache.ibatis.datasource.pooled.PooledDataSource">
		<property name="driver" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations"
			value="classpath:mybatis/mappers/*.xml" />
	</bean>

	<bean id="sqlSession"
		class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
	</bean>


	  <bean id="accDAO" class="com.spring.account.AccountDAO">
      <property name="sqlSession" ref="sqlSession" />
   </bean>
    
    <!-- DataSourceTransactionManager 클래스를 이용해 dataSource빈에 트랜잭션 적용 -->
	<bean id="txManager"  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	       <property name="dataSource" ref="dataSource" />
    </bean>
     
   <!-- 애너테이션을 사용해 트랜잭션을 적용하기 위해 txManager빈을 생성 -->  
   <tx:annotation-driven transaction-manager="txManager" /> 
</beans>

 

action-service.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
	
   	<bean id="accService" class="com.spring.account.AccountService">
      	<property name="accDAO" ref="accDAO"/>
   	</bean>
</beans>

 

jdbc.properties

 

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:XE
jdbc.username=scott
jdbc.password=tiger

 

web.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
	<listener>
      <listener-class>
         org.springframework.web.context.ContextLoaderListener
     </listener-class>
   </listener>
	<context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
          /WEB-INF/config/action-mybatis.xml
          /WEB-INF/config/action-service.xml
      </param-value>
   </context-param>
	
	
	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>action</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

</web-app>

 

마이바티스 관련 XML 파일 설정

 

account.xml

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
   
	<mapper namespace="mapper.account">
		<update id="updateBalance1">
			<![CDATA[
				update cust_account
				set 
					balance = balance-500000
				where 
					accountNo = '70-490-930'
			]]>
		</update>
		<update id="updateBalance2">
			<![CDATA[
				update cust_account
				set 
					balance = balance+500000
				where 
					accountNo = '70-490-911'
			]]>
		</update>
	</mapper>

 

트랜잭션 관련 자바 클래스와 JSP 파일 구현

 

AccountController

 

public class AccountController extends MultiActionController{
	
	private AccountService accService;

	//빈 주입하기 위해 setter 생성
	public void setAccService(AccountService accService) {
		this.accService = accService;
	}
	
	public ModelAndView sendMoney(HttpServletRequest request, HttpServletResponse response) throws Exception{
		ModelAndView mav = new ModelAndView();
		
		accService.sendMoney();//금액을 이체
		mav.setViewName("result");
		return mav;
	}
}

 

AccountDAO

 

public class AccountDAO {

	private SqlSession sqlSession;

	//빈 주입하기 위해 setter 생성
	public void setSqlSession(SqlSession sqlSession) {
		this.sqlSession = sqlSession;
	}
	
	public void updateBalance1() {
		sqlSession.update("mapper.account.updateBalance1");
	}
	
	public void updateBalance2() {
		sqlSession.update("mapper.account.updateBalance2");
	}
}

 

AccountService

 

//AccountService 클래스의 모든 메서드에 트랜잭션 적용
@Transactional(propagation = Propagation.REQUIRED)
public class AccountService {
	
	private AccountDAO accDAO;

	//빈 주입하기 위해 setter 생성
	public void setAccDAO(AccountDAO accDAO) {
		this.accDAO = accDAO;
	}
	
	//sendMoney()메서드 호출 시 accDAO의 두 개의 SQL문 실행
	public void sendMoney() throws Exception{
		accDAO.updateBalance1();
		accDAO.updateBalance2();
	}
}

 

AccountVO

 

public class AccountVO {
	
	private String accountNo;
	private String custName;
	private int balance;
	
	public AccountVO() {
	}

	public AccountVO(String accountNo, String custName, int balance) {
		this.accountNo = accountNo;
		this.custName = custName;
		this.balance = balance;
	}
    
    ... getter/setter
 }

 

http://localhost:8090/pro25/account/sendMoney.do

 

위의 경로로 요청하면 송금이 완료되었다는 메세지가 출력되고, 데이터베이스에서 값을 확인해보면

 

 

위와 같이 돈이 제대로 이체된 것을 확인할 수 있다.

 

 

대부분의 애플리케이션에서는 이렇게 Sevice 클래스에 트랜잭션을 적용한다. 

728x90

'자바 웹 > 스프링' 카테고리의 다른 글

다중 파일 업로드  (0) 2022.07.04
메이븐과 STS  (0) 2022.07.04
스프링 애너테이션 기능  (0) 2022.07.01
마이바티스  (0) 2022.06.30
스프링 MVC 기능  (0) 2022.06.29