웹 애플리케이션에서 묶어 처리하는 단위기능의 예는 아래와 같다
이렇게 거의 동시에 처리되어야하는 기능들을 묶어 처리한다.
+
nested : 트랜 잭션 필요 / 진행 중인 트랜잭션이 있는 경우 기존 트랜잰션에 중첩된 트랜잭션에서 메소드 실행 / 트랜잭션이 없으면 새로운 트랜잭션 생성
create table cust_account (
accountNo varchar2(20) primary key,
cusName varchar2(50),
balance number(20, 4)
);
-- 천만원씩 넣어놓기
insert into cust_account values('70-490-930', '홍길동', 100000000);
insert into cust_account values('70-490-912', '김유민', 100000000);
-- 커밋 필수... 반드시
commit;
-- 확인
select * from cust_account;
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:XE
jdbc.username=System
jdbc.password=hb0317vd
<?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>
뷰 관련 빈과 각 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>
<!-- 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>
스프링의 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>
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-912'
]]>
</update>
</mapper>
<?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>
package com.spring.account;
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;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
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 ;
// 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;
}
}
package com.spring.account;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
// AccountService 믈래스의 모든 메소드에게 트랜잭션 적용
// REQUIRED : 진행중인 트랜잭션이 있는 경우 해당 트랜잭션 사용
// : 트랜젝션이 없으면 새로운 트랜잭션을 생성하고 그걸 디폴트(기본)으로 사용해라
@Transactional(propagation = Propagation.REQUIRED)
public class AccountService {
private AccountDAO accDAO;
// setter
public void setAccDAO(AccountDAO accDAO) {
this.accDAO = accDAO;
}
public void sendMoney() {
accDAO.updateBalance1();
accDAO.updateBalance2();
}
}
각 예금자 계좌를 갱신하는 메소드 구현
package com.spring.account;
import org.apache.ibatis.session.SqlSession;
import org.springframework.dao.DataAccessException;
public class AccountDAO {
private SqlSession sqlSession;
//setter
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
// 첫번째 update문을 실행해 '홍길동'계좌에서 차감
public void updateBalance1() throws DataAccessException {
sqlSession.update("mapper.account.updateBalance1");
}
// 두번째 update문을 실행해 '김유민'계좌에서 차감
public void updateBalance2() throws DataAccessException {
sqlSession.update("mapper.account.updateBalance2");
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<% request.setCharacterEncoding("UTF-8"); %>
<html>
<head>
<meta charset="UTF-8">
<title>결과창</title>
</head>
<body>
<h1>송금이 완료되었습니다.</h1>
</body>
</html>
실행하는데 계속 에러가 나서보니 서버 문제였다. 정확히는 안쓰는 애들까지 다 서버 실행에 연결해놔서 거기서 난 오류가 계속 이어지며 난리난 것. 그래서
Add and Remove
들어가서 쓰는 것만 남기고 다 정리했더니 나온다!
서버를 돌린 후
http://localhost:8700/pro25/account/sendMoney.do
를 주소창에 검색해주면 아래와 같이 jsp에 작성한 대로 문구가 출력된다
그리고 연결해놓은 오라클에 접속해 테이블을 확인하면 이런 식으로 나온다. 몇번 새로고침 해줬더니 지정해준만큼 (5000000원)
홍길동에게서는 빠져나가고
김유민에게는 들어간 모습을 확인할 수 있었다!
sql문에서 김유민의 계좌번호가 잘못 적혔다고 생각해보자. 당연히 실행시 오류가 난다. 하지만 트랜잭션을 적용한것과 적용하지 않은 경우 sql에서의 변화차이가 눈여겨볼 지점인데
만약 트랜잭션을 적용하지 않았다면 오라클에서 select * from cust_account;
을 실행시켜 잔고를 확인했을 때, 아래와 같이 맞게 작성한 홍길동의 경우는 실행되고, 틀리게 작성된 김유민의 잔고는 그대로인 것을 확인할 수 있다.
트랜잭션을 적용한 경우 오라클에서 동일하게 실행문을 돌려보면 이런 식으로 둘 다 변화없는 것을 확인할 수 있다.
-> 말 그대로 '묶어서' 처리하는 것이다. 묶은 둘 중 하나라도 오류가 있으면 실행문 자체를 무시해버린다