[국비학원 기록/Spring] 트랜잭션(Transaction), 두 개의 계좌에 대한 동시 계좌이체 구현 예제
my code archive
article thumbnail
반응형

트랜잭션 하니 예전에 오라클 배울 때 적어두었던 필기가 생각나서..ㅎㅎ

트랜잭션은 역시 계좌이체가 대표적인 예시인가...

 

트랜잭션(Transaction)

1.트랜잭션(Transaction)

1)여러 개의 DML 명령문을 하나의 논리적인 작업 단위로 묶어서 관리

2)All 또는 Nothing 방식으로 작업을 처리 -> 작업의 일관성 유지

3)웹 애플리케이션에서 Service 클래스의 각 메서드가 애플리케이션의 단위 기능 수행

 

2.단위 기능의 예

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

 

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

@Transactional(propagation = Propagation.REQUIRED)

 

1)propagation
-트랜잭션 전파 규칙 설정

-속성 값


-REQUIRED : 트랜잭션 필요, ★★★★★
                 진행 중인 트랜잭션이 있는 경우 해당 트랜잭션 사용
                 트랜잭션이 없으면 새로운 트랜잭션 생성
                 디폴트값

 

-MANDATORY : 트랜잭션 필요,
                      진행 중인 트랜잭션이 없는 경우 예외 발생
  
-REQUIRES_NEW : 항상 새로운 트랜잭션 생성
  
-SUPPORTS : 트랜잭션 필요 없음
  
-NOT_SUPPORTED : 트랜잭션 필요 없음
  
-NEVER : 트랜잭션 필요 없음
           진행 중인 트랜잭션이 있는 경우 예외 발생
   


4-1.이체를 통한 트랜잭션 기능 구현

 

1)트랜잭션 적용 전 은행 계좌 이체
-이순신 계좌에서 돈 500만 원 인출함
 =>이순신 계좌에서 돈 500만원 차감함 (update)
 => 이순신 계좌 잔고 갱신함 (commit)
 
-신사임당 계좌에 500 만원을 이체함 
 => 신사임당 계좌에 500 만원 증가 시킴 (update)  
 => 신사임당 계좌 잔고 갱신함 (commit)
 
2) 트랜잭션 적용 후 은행 계좌 이체          
- 이순신 계좌에서 돈 500만원 인출함
- 이순신 계좌에서 돈 500만원 차감함 (update)
- 신사임당 계좌에 500 만원을 이체함 
- 신사임당 계좌에 500 만원 증가 시킴 (update)  
- 전체 계좌 잔고를 반영함 (commit)
 

=> 이순신과 신사임당 계좌 잔고의 갱신이 모두 정상적으로 이루어지면 최종적으로
        반영(commit)함 
=> 중간에 이상이 발생할 경우 이전의 모든 작업 취소, 즉 롤백(rollback) 시킴.

 

4-2.계좌이체 예제

 

1. account 테이블 생성,

각각의 계좌에 동일하게 천 만원씩 넣었다.

2. web.xml 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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>
cs

 

3.action-servlet.xml 작성

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?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/" />    <!-- JSP 파일 위치 지정 -->
        <property name="suffix" value=".jsp" />
    </bean>
    
    <bean id="accController" class="kr.co.ezenac.account.AccountController">
        <property name="methodNameResolver">
            <ref local="methodResolver"/>
        </property>
        <!-- ref = "accService" : accService 빈을 여기에 넣어주겠다 -->
        <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>
cs

 

4..action-mybatis.xml 작성

--스프링의 DataSourceTransactionManager클래스를 이용해 트랜잭션 처리빈 생성

->DataSource 속성에 dateSource 빈 주입하여 트랜잭션 적용

->txManager 빈에 <tx:annotation-driven> 태그 설정해 어노테이션 적용할 수 있게 함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?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>
 
    <!-- 마이바티스에서 제공하는 PooledDataSource 이용해서 dataSource 빈을 생성 -->
    <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>
    
    <!-- SqlSessionFactoryBean 이용해 dataSource 속성에 dataSource 빈을 설정함 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations"
            value="classpath:mybatis/mappers/*.xml" />
    </bean>
    
    <!-- SqlSessionTemplate 이용해 sqlSession 빈을 생성함 -->
    <bean id="sqlSession"
        class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
    </bean>
    
    <!-- sqlSession 빈을 accDAO 빈 속성에 주입함 -->
    <bean id="accDAO" class="kr.co.ezenac.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>
cs

 

5.action-service.xml 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="kr.co.ezenac.account.AccountService">
        <property name="accDAO" ref="accDAO"/>
    </bean>
</beans>
cs

 

6.jdbc.properties 작성

--DB 정보

1
2
3
4
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:XE
jdbc.username=wdsql
jdbc.password=0311
cs

 

7.AccountController.java 작성

--setter 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
 
 
public class AccountController extends MultiActionController{
    
    private AccountService accService;
    
    //속성 accService에 빈을 주입하기 위해 setter 구현함.
    public void setAccService(AccountService accService) {
        this.accService = accService;
    }
    
    // /account/sendMoney.do로 요청 시 sendMoney() 메소드를 호출해 계좌이체 작업 수행함
    public ModelAndView sendMoney(HttpServletRequest request, HttpServletResponse response) throws Exception{
        
        ModelAndView mav = new ModelAndView();
        accService.sendMoney();        //금액 이체함
        mav.setViewName("result");
        return mav;
    }
 
}
cs

 

8.AccountDAO.java 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.apache.ibatis.session.SqlSession;
 
public class AccountDAO {
    
    private SqlSession sqlSession;
    
    //속성 sqlSession에 빈을 주입하기 위해 setter를 구현함
    public void setSqlSession(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }
    
    public void updateBalance1() {
        //첫 번째 update문을 실행해 이순신 계좌에서 500만 원 차감함
        sqlSession.update("mapper.account.updateBalance1");
        
    }
 
    public void updateBalance2() {
        //두 번째 update문을 실행해 신사임당 계좌에 500만 원 입금함
        sqlSession.update("mapper.account.updateBalance2");
        
    }
 
    
 
}
cs

 

9.AccountService.java 작성

@Transactional 어노테이션 적용하여 메소드별로 트랜잭션 적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import org.springframework.transaction.annotation.Propagation;
 
import org.springframework.transaction.annotation.Transactional;
 
/*
 * 서비스 계층
 * -애플리케이션 비즈니스 로직 처리와 비즈니스와 관련된 도메인 모델의 적합성 검증
 * -트랜잭션(Transaction)처리
 * -프리젠테이션 계층과 데이터 엑세스 계층 사이를 연결하는 역할,
 *  두 계층이 직접적으로 통신하지 않게함
 */
 
//AccountService 클래스의 모든 메서드에 트랜잭션을 적용함
@Transactional(propagation = Propagation.REQUIRED)
public class AccountService {
    
    private AccountDAO accDAO;
    
    //속성 accDAO에 빈을 주입하기 위해 setter를 구현함
    public void setAccDAO(AccountDAO accDao) {
        this.accDAO = accDao;
    }
    
    //sendMoney()호출 시 accDAO의 두 개의 SQL문을 실행함
    public void sendMoney() {
        accDAO.updateBalance1();
        accDAO.updateBalance2();
        
    }
 
}
cs

 

10.account.xml 작성

--SQL 쿼리문이 있는 곳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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">
    <!-- 잔고에서 -5000000 -->
    <update id="updateBalance1">
        <![CDATA[
            update account
            set BALANCE = BALANCE - 5000000
            where accountNo = '22-0104-311'
        ]]>
    </update>
    <!-- 잔고에서 +5000000 -->
    <update id="updateBalance2">
        <![CDATA[
            update account
            set BALANCE = BALANCE + 5000000
            where accountNo = '22-0204-211'
        ]]>
    </update>
</mapper> 
cs

 

11.result.jsp 작성

--화면 출력 역할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
    request.setCharacterEncoding("utf-8");
%>    
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>결과창</title>
</head>
<body>
    <h1>이체가 완료되었습니다!</h1>
</body>
</html>
cs

 

실행 결과

실행 전

실행 후 - 잔액이 바뀌었다.

반응형
profile

my code archive

@얼레벌레 개발자👩‍💻

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

반응형