일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- GRANT VIEW
- 컬렉션 타입
- exception
- NestedFor
- abstract
- cursor문
- Java
- 자동차수리시스템
- 환경설정
- 사용자예외클래스생성
- 객체 비교
- 추상메서드
- 예외처리
- 정수형타입
- 오라클
- 한국건설관리시스템
- oracle
- 대덕인재개발원
- 제네릭
- 어윈 사용법
- 예외미루기
- 컬렉션프레임워크
- 자바
- 다형성
- 인터페이스
- 참조형변수
- 생성자오버로드
- EnhancedFor
- 메소드오버로딩
- 집합_SET
- Today
- Total
거니의 velog
(17) 스프링 트랜잭션 기능 사용하기 2 본문
4. 스프링 트랜잭션 기능 적용해 계좌 이체 실습하기
* 이번에는 계좌 이체 기능을 스프링의 트랜잭션 기능을 적용하여 실습해 보자.
* 먼저 SQL Developer 로 예금자 계좌 정보를 저장하는 테이블을 생성한다. 그리고 예금자의 계좌 정보를 다음과 같이 추가한다.
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', '홍길동', 10000000);
insert into cust_account(accountNo, custName, balance)
values('70-490-911', '김유신', 10000000); -- 홍길동과 김유신의 계좌 정보를 생성한다.
commit; -- insert 문 실행 후 반드시 커밋을 해야 한다.
select * from cust_account;
* 다음은 계좌 정보를 조회한 결과이다. 모든 예금자의 계좌 잔고는 10,000,000 원이다.
(1) 트랜잭션 관련 XML 파일 설정하기
1. 스프링과 연동해 트랜잭션 기능을 구현하는 데 필요한 XML 파일들을 다음과 같이 준비한다. web.xml은 이전의 것을 복사해 붙여 넣는다.
2. 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" /> <!-- accService 빈을 주입한다. -->
</bean>
<bean id="methodResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
<property name="mappings">
<props>
<prop key="/account/sendMoney.do">sendMoney</prop> <!-- /account/sendMoney.do 요청 시 sendMoney 메서드를 호출한다. -->
</props>
</property>
</bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/account/*.do">accController</prop> <!-- /account/*.do로 요청 시 accController 빈을 실행한다. -->
</props>
</property>
</bean>
</beans>
3. 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>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> <!-- DataSourceTransactionManager 클래스를 이용해 dataSource 빈에 트랜잭션을 적용한다. -->
<tx:annotation-driven transaction-manager="txManager" /> <!-- 애너테이션을 사용하여 트랜잭션을 적용하기 위해 txManager 빈을 설정한다. -->
</beans>
4. action-service.xml에서는 AccountService의 accDAO 속성에 accDAO 빈을 주입하도록 구현한다.
<?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> <!-- accService 빈의 속성에 accDAO 빈을 주입한다. -->
</beans>
(2) 마이바티스 관련 XML 파일 설정하기
* 이번에는 계좌 이체 기능을 SQL문으로 구현한 매퍼 파일을 설정해 보자.
1. 다음과 같이 매퍼 파일인 account.xml을 준비한다.
2. 매퍼 파일에서는 두 개의 update 문으로 두 명의 계좌 잔고를 갱신한다.
<?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"> <!-- 잔고를 5000000원 감액한다. -->
<![CDATA[
update cust_account
set balance=balance-5000000
where
accountNo = '70-490-930'
]]>
</update>
<update id="updateBalance2"> <!-- 잔고를 5000000원 증액한다. -->
<![CDATA[
update cust_account
set balance=balance+5000000
where
accountNo ='70-490-911'
]]>
</update>
</mapper>
(3) 트랜잭션 관련 자바 클래스와 JSP 파일 구현하기
1. 계좌 이체 기능에 필요한 자바 파일과 JSP 파일들을 다음과 같이 준비한다.
2. 컨트롤러에서는 속성 accService에 빈을 주입하기 위해 setter를 구현한다. /account/sendMoney.do로 요청 시 sendMoney() 메서드를 호출해 계좌 이체 작업을 수행한다.
package com.spring.account;
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 ;
public void setAccService(AccountService accService){
this.accService = accService;
} // 속성 accService에 빈을 주입하기 위해 setter를 구현한다.
public ModelAndView sendMoney(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mav=new ModelAndView();
accService.sendMoney(); // 금액을 이체한다.
mav.setViewName("result");
return mav;
}
}
3. AccountService 클래스를 다음과 같이 작성한다. 서비스 클래스의 메서드는 단위 기능을 수행하므로 @Transactional 애너테이션을 서비스 클래스에 적용해 메서드별로 트랜잭션을 적용한다.
package com.spring.account;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
// @Transactional을 이용해 AccountService 클래스의 모든 메서드에 트랜잭션을 적용한다.
@Transactional(propagation=Propagation.REQUIRED)
public class AccountService {
private AccountDAO accDAO;
public void setAccDAO(AccountDAO accDAO) {
this.accDAO = accDAO;
} // 속성 accDAO에 빈을 주입하기 위해 setter를 구현한다.
public void sendMoney() throws Exception {
accDAO.updateBalance1();
accDAO.updateBalance2();
} // sendMoney() 메서드 호출 시 accDAO의 두 개의 SQL문을 실행한다.
}
4. AccountDAO 클래스에서는 각 예금자 계좌를 갱신하는 메서드를 구현한다.
package com.spring.account;
import org.apache.ibatis.session.SqlSession;
import org.springframework.dao.DataAccessException;
public class AccountDAO {
private SqlSession sqlSession;
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
} // 속성 sqlSession에 빈을 주입하기 위해 setter를 구현한다.
public void updateBalance1() throws DataAccessException {
sqlSession.update("mapper.account.updateBalance1");
} // 첫 번째 update 문을 실행해 홍길동 계좌에서 5000000원을 차감한다.
public void updateBalance2() throws DataAccessException {
sqlSession.update("mapper.account.updateBalance2");
} // 두 번째 update 문을 실행해 김유신 계좌에 5000000원을 증액한다.
}
5. 트랜잭션을 적용하지 않은 경우와 적용한 경우의 실행 결과를 각각 확인해 보자. 먼저 다음의 주소로 요청하여 정상적으로 계좌 이체가 이루어진 경우의 결과를 확인한다.
- http://localhost:8090/pro25/account/sendMoney.do
6. SQL Developer로 조회하면 홍길동의 계좌에서 김유신의 계좌로 5,000,000 만원이 이체된 것을 확인할 수 있다.
7. 이번에는 트랜잭션을 적용하지 않은 경우의 실행 결과를 보자. AccountService.java에서 다음 부분을 주석 처리한다.
/*@Transactional(propagation=Propagation.REQUIRED)*/
public class AccountService {
8. account.xml의 두 번째 SQL 문에 일부러 문법 오류를 발생시킨다.
<?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-5000000
where
accountNo = '70-490-930'
]]>
</update>
<update id="updateBalance2">
<![CDATA[
update cust_account
set balance=balance+5000000
where
accountNo =70-490-911
]]>
</update>
<!-- 계좌 번호 양쪽 작은 따옴표(')를 삭제하여 오류를 발생시킨다. -->
</mapper>
9. SQL Developer에서 예금자들의 잔고를 원래대로 되돌린 후, 즉 10,000,000 만원으로 갱신한 후 브라우저에서 다음의 주소로 요청하면 다음과 같은 오류가 발생한다.
UPDATE cust_account
SET
balance = 10000000
WHERE
accountno = '70-490-930';
UPDATE cust_account
SET
balance = 10000000
WHERE
accountno = '70-490-911';
commit;
select * from cust_account;
- http://localhost:8090/pro25/account/sendMoney.do
10. SQL Developer로 각 계좌 잔고를 조회해 보면 홍길동의 잔고는 5,000,000원이 감소했으나, 김유신의 잔고는 10,000,000원 그대로인 것을 확인할 수 있다.
11. 트랜잭션을 적용한 후 브라우저에서 요청한 결과를 확인하기에 앞서 원래대로 주석을 해제한다. 그리고 SQL Developer로 다시 예금자들의 잔고를 10,000,000원으로 변경한다.
@Transactional(propagation=Propagation.REQUIRED)
public class AccountService {
UPDATE cust_account
SET
balance = 10000000
WHERE
accountno = '70-490-930';
UPDATE cust_account
SET
balance = 10000000
WHERE
accountno = '70-490-911';
commit;
select * from cust_account;
12. 다음의 주소로 요청하면 또 다시 오류가 발생한다.
- http://localhost:8090/pro25/account/sendMoney.do
13. SQL Developer로 각 계좌 잔고를 조회한다. 이번에는 트랜잭션이 적용되었으므로 김유신의 잔고는 물론이고 오류가 발생하지 않은 홍길동의 잔고도 원래의 금액으로 롤백이 된다.
* 대부분의 애플리케이션에서는 이처럼 Service 클래스에 트랜잭션을 적용한다. 사실 우리가 이전에 구현한 회원 기능 프로그램에서 MemberServiceImpl 클래스에도 트랜잭션 애너테이션을 적용했었다.
@Transactional(propagation=Propagation.REQUIRED)
public class MemberServiceImpl implements MemberService{
...
}
* 지금까지 애너테이션을 이용해 기본적인 트랜잭션 기능을 알아봤다. 더 세부적인 스프링 트랜잭션 기능은 전문적인 스프링 서적을 참고하길 바란다.
'Java_Spring Framework part1' 카테고리의 다른 글
(19) 스프링 애너테이션 기능 2 (0) | 2023.11.11 |
---|---|
(18) 스프링 애너테이션 기능 1 (0) | 2023.11.11 |
(16) 스프링 트랜잭션 기능 사용하기 1 (0) | 2023.11.11 |
(15) 스프링과 마이바티스 연동하기 (0) | 2023.11.10 |
(14) 마이바티스 프레임워크 사용하기 5 (0) | 2023.11.10 |