Spring 프레임워크와 MyBatis를 사용할 때 Transaction 처리 예
개요
Spring 프레임워크와 MyBatis 를 연동하면서 트랜잭션을 처리하려면,
서블릿 설정파일에 DataSourceTransactionManager 빈과 <tx:annotation-driven />을 설정하고, 서비스 클래스에서 다수개의 SQL문장을 호출하는 메소드에 @Transactional 을 선언해주면 된다.
@Transactional 으로 선언된 메소드 안에서 어떠한 오류가 발생할 경우에는 그 메소드 안에서 호출된 모든 SQL문장은 모두 취소되고 오류가 없다면 모두 실행된다
참고 : 수동으로 트랜잭션을 처리하려면 http://mybatis.org/spring/ko/transactions.html
테스트 환경
Windows 7
JDK 1.8, Spring 4, STS, Maven
Oracle 11g xe
Eclipse
서블릿 설정파일에 다음과 같이 빈을 설정한다
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
........
..........
<beans:bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource">
<beans:property name="dataSourceName" value="ds"/>
<beans:property name="URL" value="jdbc:oracle:thin:@localhost:1521:xe"/>
<beans:property name="user" value="scott"/>
<beans:property name="password" value="TIGER"/>
</beans:bean>
<beans:bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<beans:property name="mapperLocations" value="classpath:org/kdea/spring/mybatis/*.xml"/>
<beans:property name="dataSource" ref="dataSource" />
</beans:bean>
<beans:bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<beans:constructor-arg index="0" ref="sqlSessionFactory"/>
</beans:bean>
<beans:bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<beans:property name="dataSource" ref="dataSource" />
</beans:bean>
<tx:annotation-driven transaction-manager="transactionManager" />
..........
..............
서비스 메소드에서 다수개의 SQL문장이 실행되는 경우 한개의 트랜잭션으로 처리를 하고자 한다면...
@Transactional
DAO 의 메소드를 호출하여 데이터베이스 작업을 수행하는 서비스 메소드에 @Transactional 을 선언하면 스프링은 적절히 commit, rollback기능을 수행한다
DAO에 별도의 코드를 추가할 필요가 없다
package org.kdea.spring.mybatis;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("empService")
public class EmpService {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public List<EmpVO> getList(){
EmpDAO dao = sqlSessionTemplate.getMapper(EmpDAO.class);
List<EmpVO> list = dao.list();
return list;
}
@Transactional //메소드 실행시 오류가 발생하면 여기서 호출된 모든 SQL문장은 실행취소됨
public boolean addEmp(EmpVO[] emps) {
EmpDAO dao = sqlSessionTemplate.getMapper(EmpDAO.class);
// 아래의 라인에서 다수개의 SQL문장이 호출됨
dao.insert(emps[0]);
dao.insert(emps[1]);
return true;
}
}
EmpDAO.java
package org.kdea.spring.mybatis;
import java.util.List;
public interface EmpDAO {
public List<EmpVO> list();
public EmpVO info(int empno);
public int insert(EmpVO emp);
public int update(EmpVO emp);
public int delete(int empno);
public String getNameByEmpno(int empno);
}
EmpMapper.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="org.kdea.spring.mybatis.EmpDAO"> <!--이 sql 문장과 매핑될 인터페이스의 완전한 경로-->
<select id="list" resultType="org.kdea.spring.mybatis.EmpVO">
select * from emp2
</select>
<insert id="insert" parameterType="org.kdea.spring.mybatis.EmpVO">
insert into emp2 (empno, ename, deptno, job, sal)
values ( #{empno}, #{ename}, #{deptno}, #{job}, #{sal} )
</insert>
<select id="info" resultType="org.kdea.spring.mybatis.EmpVO">
select * from emp2 where empno=#{empno}
</select>
<update id="update" parameterType="org.kdea.spring.mybatis.EmpVO">
update emp2 set deptno=#{deptno}, sal=#{sal} where empno=#{empno}
</update>
<delete id="delete" >
delete from emp2 where empno=#{empno}
</delete>
<select id="getNameByEmpno" resultType="String">
select ename from emp2 where empno=#{empno}
</select>
</mapper>
EmpController.java
package org.kdea.spring.mybatis;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/mybatis/")
public class EmpController {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
@Autowired
private EmpService empService;
@RequestMapping("empList")
public String getEmpList(Model model) {
List<EmpVO> list = empService.getList();
model.addAttribute("empList", list);
return "mybatis/empList";
}
@RequestMapping(value="txInsert", method=RequestMethod.GET)
public String txInsert() {
EmpVO[] emps = new EmpVO[2]; // 2명의 사원정보 샘플 데이터
emps[0] = new EmpVO();
emps[0].setEmpno(101);
emps[0].setEname("김도현");
emps[0].setDeptno(20);
emps[0].setJob("부장");
emps[0].setSal(3500);
emps[1] = new EmpVO();
emps[1].setEmpno(101); // 두번째 사원정보를 저장할 때 오류를 발생시켜 트랜잭션이 제대로 작동하는지 테스트함
emps[1].setEname("박노현");
emps[1].setDeptno(20);
emps[1].setJob("과장");
emps[1].setSal(3000);
boolean saved = false;
try{
saved = empService.addEmp(emps); // 서비스 메소드 호출 ( 다수개의 SQL문장이 실행됨 )
}catch(Exception ex){
saved = false;
ex.printStackTrace();
}
System.out.println(saved ? "모두 저장" : "모두 취소");
return "redirect:empList";
}
}