스프링 Transaction(3) 스키마2개 처리
개요
❗WAS내의 마이바티스로 운영되는 DB1 DB2 트랜잭션이 작동하지 않는다.
💡 1.서로의 세션이 다르기 때문임을 확인 함.
해결방안
방법1
두 데이터소스 자체의 트랜잭션1,2 를 묶어서 처리
❗ChainedTransactionManager XML 처리
<!-- transaction manager setting --> <bean id="transactionManager" class="org.springframework.data.transaction.ChainedTransactionManager"> <constructor-arg> <list> <ref bean="mybatisTransactionManager"/> <ref bean="jpaTransactionManager"/> </list> </constructor-arg> </bean>
💡 위 코드로 다중 데이터소스(dataSource)에 대해 트랜잭션을 묶을 수 있다고 한다.
💡 (여러개의 트랜잭션 매니저를 하나로 묶어(Chain)사용하는 방식인데 트랜잭션의 시작과 끝에서 연결된 트랜잭션들을 순차로 Start/Commit 시킴으로써 하나의 트랜잭션으로 실행되는것 처럼 동작)# 이상적인 트랜잭션 [트랜잭션1 (Tx1), 트랜잭션2 (Tx2))] # Tx1 (Start) -> Tx2 (Start) -> Logic -> Tx2 (COMMIT) -> Tx1 (COMMIT) # Tx1 (Start) -> Tx2 (Start) -> Logic -> Tx2 (ROLLBACK) -> Tx1 (ROLLBACK) # 하지만 완벽하지 않아서 아래와 같이 동작가능성이 있다. # Tx1 (Start) -> Tx2 (Start) -> Logic -> Tx2 (COMMIT) -> Tx1 (ROLLBACK) # 체이닝 순서가 중요요하다. # 더 나중에 선언된 트랜잭션에서 에러가 날 경우에는 트랜잭션 ROLLBACK이 보장되기에, # 에러가 날 확률이 높은 트랜잭션을 후순위 chain으로 묶어줘야한다 (그래야 조금 더 안전한 트랜잭션을 구성 할 수 있다.)
💡 하지만 구조적인 이유로 완벽한 롤백은 보장하지 않는다고 함.
현재 비권장되어 사용불가능함을 확인.
방법2: 각각의 트랜잭션 명시
root-context.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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<!-- 데이터베이스1 -->
<bean id="dataSource1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:DSBDB" />
<property name="username" value="oracleMaster" />
<property name="password" value="1234" />
</bean>
<!-- 데이터베이스2 -->
<bean id="dataSource2" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://blang.co.kr:3306/DSBCKO0" />
<property name="username" value="mriaMaster" />
<property name="password" value="4321" />
</bean>
<!-- ================================================================================= -->
<!-- SQL 세션 팩토리 1 -->
<bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource1"/>
<property name="configLocation" value="classpath:/mybatis-config.xml" /><!-- myBatis 설정 파일의 위치를 지정한다. -->
<property name="mapperLocations" value="classpath:/mappers/**/*Mapper.xml" /><!-- SQL 파일의 위치를 지정한다. -->
</bean>
<!-- SQL 세션 팩토리 2 -->
<bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource2" />
<property name="configLocation" value="classpath:/mybatis-config.xml" />
<property name="mapperLocations" value="classpath:/mappers/**/*Mapper.xml" />
</bean>
<!-- ================================================================================= -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory1"/>
</bean>
<!-- SqlSessionTemplate 2 -->
<bean id="sqlSession2" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory2"/>
</bean>
<!-- ================================================================================= -->
<!-- 트랜잭션 매니저 1 -->
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1"/>
</bean>
<!-- 트랜잭션 매니저 2 -->
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource2"/>
</bean>
<!-- ================================================================================= -->
<!-- Common DAO -->
<bean id="commonDao" class="com.blang.bck.comm.dao.CommonDao">
<property name="sqlSession2" ref="sqlSession2" /> <!-- 다른 DAO에서 주입된 sqlSession 사용 -->
</bean>
<!-- ================================================================================= -->
<util:properties id="globalProperties" location="classpath:/properties/global-properties.xml" />
</beans>
DAO
@EnableAsync
public class CommonDao
{
@Autowired
private SqlSession sqlSession2;
//Insert
public int insert(String statementName, Object parameter)
{
try
{
return sqlSession2.insert(statementName, parameter);
}
catch (Exception e)
{
throw new SysException(SysErrorCode.INTERNAL_SERVER_ERROR, e);
}
}
//Update
public int update(String statementName, Object parameter)
{
try
{
return sqlSession2.update(statementName, parameter);
}
catch (Exception e)
{
throw new SysException(SysErrorCode.INTERNAL_SERVER_ERROR, e);
}
}
//중략...
}
Service
/**
* 결재 요청
* (트랜잭션 추가)
* @param params
* @return
*/
@Transactional(propagation = Propagation.REQUIRED, transactionManager = "transactionManager2")
public int insertRqstData(Map<String, Object> params)
{
//결재요청 데이터를 insert
int inCount = dao.insert(namespace+".insertRqstData", map);
int upCount = dao.update(namespace+".updateRqstData", map);
...
}
Controller
/**
* 결재 요청
* @param params
* @return
*/
@ResponseBody
@RequestMapping(value = "/insertRqstData", method = RequestMethod.POST)
public Map<String, Object> insertRqstData(@RequestBody Map<String, Object> params, HttpServletRequest req)
{
int count = workFlowRqstService.insertRqstData(params);
if(count > 0) {
resultMap.put("status", "SUCCESS");
resultMap.put("count", count);
}
return resultMap;
}
❗명시 후 트랜잭션 정상 작동 확인
💡 2번 데이터베이스에 대한 트랜잭션이 정상적으로 작동한다.
궁금한 점 : 명시하지 않으면 트랜잭션이 사용되는 순서는?
찾아보니 방법은 3가지가 있다고 한다.
방법1. 명시적 지정이 없는 경우
❗만약 트랜잭션 매니저가 명시적으로 지정되지 않았고, @Primary 애노테이션도 사용하지 않았다면, 스프링은 트랜잭션 매니저 빈이 하나만 존재하는 경우 그 빈을 기본 트랜잭션 매니저로 사용한다.
💡 하지만 둘 이상의 트랜잭션 매니저가 존재하는데 명시적으로 지정하지 않으면, NoUniqueBeanDefinitionException 예외가 발생한다.
방법2. @Primary 어노테이션
<!-- 트랜잭션 매니저 1 -->
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1"/>
</bean>
<!-- 트랜잭션 매니저 2 -->
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" primary="true">
<property name="dataSource" ref="dataSource2"/>
</bean>
❗@Primary 애노테이션을 사용하여 특정 트랜잭션 매니저를 기본 트랜잭션 매니저로 지정
💡 1.transactionManager2가 기본 트랜잭션 매니저로 설정된다.
방법3. 트랜잭션 매니저 빈 이름을 통해 기본값 설정
<!-- 트랜잭션 매니저 1 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1"/>
</bean>
<!-- 트랜잭션 매니저 2 -->
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource2"/>
</bean>
❗@Primary 애노테이션을 사용하여 특정 트랜잭션 매니저를 기본 트랜잭션 매니저로 지정
💡 1.transactionManager가 기본 트랜잭션 매니저로 설정된다.
Leave a comment