트랜잭션 기능
스프링은 트랜잭션 기능을 마이바티스 기능과 연동해서 사용한다.
트랜잭션 기능은 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 클래스에 트랜잭션을 적용한다.
'자바 웹 > 스프링' 카테고리의 다른 글
다중 파일 업로드 (0) | 2022.07.04 |
---|---|
메이븐과 STS (0) | 2022.07.04 |
스프링 애너테이션 기능 (0) | 2022.07.01 |
마이바티스 (0) | 2022.06.30 |
스프링 MVC 기능 (0) | 2022.06.29 |