Spring分布式事务在service中动态切换数据源
项目采用的是struts2的+弹簧+ ibatis的架构,下面是关键部分代码:
applicationContext.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:context = “http://www.springframework.org/schema/context”
- 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.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-2.5.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd“
- default-autowire = “byName” default-lazy-init = “false” >
- < context:component-scan base-package = “com.ssi。*” />
- <! - 属性文件读入 - >
- < bean id = “propertyConfigurer” class = “org.springframework.beans.factory.config.PropertyPlaceholderConfigurer” >>
- < property name = “locations” >
- < list >
- < value > classpath *:jdbc.properties </ value >
- </ list >
- </ property >
- </ bean >
- <! - 配置sqlMapclient - >
- < bean id = “sqlMapClient” class = “org.springframework.orm.ibatis.SqlMapClientFactoryBean” >
- < property name = “configLocation” value = “classpath:ibatis-sqlmap-config.xml” />
- < property name = “dataSource” ref = “dataSource” />
- </ bean >
- < bean id = “sqlMapClient1” class = “org.springframework.orm.ibatis.SqlMapClientFactoryBean” >
- < property name = “configLocation” value = “classpath:ibatis-sqlmap-config.xml” />
- < property name = “dataSource” ref = “db1” />
- </ bean >
- < bean id = “sqlMapClient2” class = “org.springframework.orm.ibatis.SqlMapClientFactoryBean” >
- < property name = “configLocation” value = “classpath:ibatis-sqlmap-config.xml” />
- < property name = “dataSource” ref = “db2” />
- </ bean >
- < bean id = “sqlMapClientCenter” class = “org.springframework.orm.ibatis.SqlMapClientFactoryBean” >
- < property name = “configLocation” value = “classpath:ibatis-sqlmap-config.xml” />
- < property name = “dataSource” ref = “center” />
- </ bean >
- < bean id = “dynamicSqlMapClientDaoSupport” class = “com.ssi.dao.DynamicSqlClientDaoSupport” >>
- < property name = “targetSqlMapClients” >
- < map >
- < entry key = “db1” value-ref = “sqlMapClient1” />
- < entry key = “db2” value-ref = “sqlMapClient2” />
- < entry key = “center” value-ref = “sqlMapClientCenter” />
- </ map >
- </ property >
- < property name = “defaultSqlMapClient” ref = “sqlMapClientCenter” />
- </ bean >
- < bean id = “ibatisDaoSupport” class = “com.ssi.dao.IbatisDaoSupport” parent = “dynamicSqlMapClientDaoSupport” > </bean >
- < bean id = “userDao” class = “com.ssi.dao.impl.UserDaoImpl” parent = “ibatisDaoSupport” > </ bean >
- <! - 支持@AspectJ标记 - >
- < aop:aspectj-autoproxy proxy-target-class = “true” />
- <! - 配置JTA的事务管理器 - >
- < bean id = “atomikosTransactionManager” class = “com.atomikos.icatch.jta.UserTransactionManager” init-method = “init” destroy-method = “close” >
- < property name = “forceShutdown” value = “true” />
- </ bean >
- < bean id = “atomikosUserTransaction” class = “com.atomikos.icatch.jta.UserTransactionImp” >
- < property name = “transactionTimeout” value = “300” />
- </ bean >
- < bean id = “springTransactionManager” class = “org.springframework.transaction.jta.JtaTransactionManager”>
- < property name = “transactionManager” ref = “atomikosTransactionManager” />
- < property name = “userTransaction” ref = “atomikosUserTransaction” />
- </ bean >
- <! - 配置通知 - >
- < tx:advice id = “txAdvice” transaction-manager = “springTransactionManager” >
- < tx:attributes >
- < tx:method name = “*” rollback-for = “Exception,RuntimeException,com.ssi.exception.SystemException” propagation = “REQUIRED” />
- </ tx:attributes >
- </ tx:advice >
- <! - 以AspectJ方式定义AOP - >
- < aop:config >
- < aop:advisor pointcut = “execution(* com.ssi.service .. * Service *。*(..))” advice-ref = “txAdvice”/>
- </ aop:config >
- <! - spring定时器任务开始 - >
- < bean name = “job” class = “org.springframework.scheduling.quartz.JobDetailBean” >
- < property name = “jobClass” >
- < value > com.ssi.action.TimerAction </ value >
- </ property >
- < property name = “jobDataAsMap” >
- < map >
- <! - timeout属性设定了当服务器启动后过10秒钟首次调用你的JobAction - >
- < entry key = “timeout” >
- < value > 10 </ value >
- </ entry >
- </ map >
- </ property >
- </ bean >
- < bean id = “cronTrigger” class = “org.springframework.scheduling.quartz.CronTriggerBean” >
- < property name = “jobDetail” >
- < ref bean = “job” />
- </ property >
- < property name = “cronExpression” >
- < 值> 0 53 15?* MON-FRI </ value >
- </ property >
- </ bean >
- < bean class = “org.springframework.scheduling.quartz.SchedulerFactoryBean” autowire = “no” >
- < property name = “triggers” >
- < list >
- < ref local = “cronTrigger” />
- </ list >
- </ property >
- </ bean >
- <! - spring定时器任务结束 - >
- </ beans >
的applicationContext-datasource.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-3.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd“ >
- <! - 指定春天配置中用到的属性文件 - >
- < bean id = “propertyConfig” class = “org.springframework.beans.factory.config.PropertyPlaceholderConfigurer”>>
- < property name = “locations” >
- < list >
- < value > classpath:jdbc.properties </ value >
- </ list >
- </ property >
- </ bean >
- <! - JTA数据源配置 - >
- < bean id = “center” class = “com.atomikos.jdbc.AtomikosDataSourceBean” init-method = “init” destroy-method = “close” >
- < property name = “uniqueResourceName” >
- < 值> mysql / center </ value >
- </ property >
- < property name = “xaDataSourceClassName” >
- < value > $ {jta.driver.className} </ value >
- </ property >
- < property name = “xaProperties” >
- < 道具>
- < prop key = “url” > $ {center.jdbc.driver.url} </ prop >
- < prop key = “user” > $ {center.sql.user.name} </ prop >
- < prop key = “password” > $ {center.sql.user.password} </ prop >
- </ 道具>
- </ property >
- < property name = “testQuery” value = “select 1” />
- < property name = “poolSize” >
- < value > $ {poolsize} </ value >
- </ property >
- < property name = “maxPoolSize” >
- < value > $ {maxPoolSize} </ value >
- </ property >
- < property name = “borrowConnectionTimeout” > < value > $ {borrowConnectionTimeout} </ value > </ property >
- </ bean >
- < bean id = “db1” class = “com.atomikos.jdbc.AtomikosDataSourceBean” init-method = “init” destroy-method = “close”>
- < property name = “uniqueResourceName” >
- < value > mysql / db1 </ value >
- </ property >
- < property name = “xaDataSourceClassName” >
- < value > $ {jta.driver.className} </ value >
- </ property >
- < property name = “xaProperties” >
- < 道具>
- < prop key = “url” > $ {db1.jdbc.driver.url} </ prop >
- < prop key = “user” > $ {company.sql.user.name} </ prop >
- < prop key = “password” > $ {company.sql.user.password} </ prop >
- </ 道具>
- </ property >
- < property name = “testQuery” value = “select 1” />
- < property name = “poolSize” >
- < value > $ {poolsize} </ value >
- </ property >
- < property name = “maxPoolSize” >
- < value > $ {maxPoolSize} </ value >
- </ property >
- < property name = “borrowConnectionTimeout” > < value > $ {borrowConnectionTimeout} </ value > </ property >
- </ bean >
- < bean id = “db2” class = “com.atomikos.jdbc.AtomikosDataSourceBean” init-method = “init” destroy-method = “close”>
- < property name = “uniqueResourceName” >
- < value > mysql / db2 </ value >
- </ property >
- < property name = “xaDataSourceClassName” >
- < value > $ {jta.driver.className} </ value >
- </ property >
- < property name = “xaProperties” >
- < 道具>
- < prop key = “url” > $ {db2.jdbc.driver.url} </ prop >
- < prop key = “user” > $ {company.sql.user.name} </ prop >
- < prop key = “password” > $ {company.sql.user.password} </ prop >
- </ 道具>
- </ property >
- < property name = “testQuery” value = “select 1” />
- < property name = “poolSize” >
- < value > $ {poolsize} </ value >
- </ property >
- < property name = “maxPoolSize” >
- < value > $ {maxPoolSize} </ value >
- </ property >
- < property name = “borrowConnectionTimeout” > < value > $ {borrowConnectionTimeout} </ value > </ property >
- </ bean >
- < bean id = “dataSource” class = “com.ssi.datasource.DynamicDataSource” >
- < property name = “targetDataSources” >
- < map key-type = “java.lang.String” >
- < entry key = “db1” value-ref = “db1” />
- < entry key = “db2” value-ref = “db2” />
- < entry key = “center” value-ref = “center” />
- </ map >
- </ property >
- < property name = “defaultTargetDataSource” ref = “center” />
- </ bean >
- </ beans >
DynamicSqlClientDaoSupport.java
- 包 com.ssi.dao;
- import java.util.Map;
- import javax.sql.DataSource;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.dao.support.DaoSupport;
- import org.springframework.orm.ibatis.SqlMapClientTemplate;
- import org.springframework.util.Assert;
- import com.ibatis.sqlmap.client.SqlMapClient;
- import com.ssi.datasource.DbContextHolder;
- 公共类 DynamicSqlClientDaoSupport 扩展 DaoSupport 实现 InitializingBean {
- 私人 SqlMapClientTemplate sqlMapClientTemplate = 新的 SqlMapClientTemplate();
- 私人 地图<String,SqlMapClient> targetSqlMapClients;
- 私人 SqlMapClient的defaultSqlMapClient;
- private boolean externalTemplate = false ;
- / **
- *设置此DAO使用的JDBC数据源。
- *不需要:SqlMapClient可能携带共享数据源。
- * @see #setSqlMapClient
- * /
- public final void setDataSource(DataSource dataSource){
- 如果 (!this .externalTemplate){
- 这个.sqlMapClientTemplate.setDataSource(dataSource);
- }
- }
- / **
- *返回此DAO使用的JDBC数据源。
- * /
- public final DataSource getDataSource(){
- 返回这个.sqlMapClientTemplate.getDataSource();
- }
- / **
- *设置iBATIS数据库层SqlMapClient使用。
- *这个或者“sqlMapClientTemplate”是必需的。
- * @see #setSqlMapClientTemplate
- * /
- public final void setSqlMapClient(SqlMapClient sqlMapClient){
- 如果 (!this .externalTemplate){
- 这个.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
- }
- }
- / **
- *返回该模板使用的iBATIS数据库层SqlMapClient。
- * /
- public final SqlMapClient getSqlMapClient(){
- 返回这个.sqlMapClientTemplate.getSqlMapClient();
- }
- / **
- *明确地为此DAO设置SqlMapClientTemplate,
- *作为指定SqlMapClient的替代方法。
- * @see #setSqlMapClient
- * /
- public final void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate){
- Assert.notNull(sqlMapClientTemplate, “SqlMapClientTemplate不能为空” );
- 这个.sqlMapClientTemplate = sqlMapClientTemplate;
- 这个.externalTemplate = true ;
- }
- / **
- *返回此DAO的SqlMapClientTemplate,
- *用SqlMapClient预先初始化或明确设置。
- * /
- public final SqlMapClientTemplate getSqlMapClientTemplate(){
- 字符串dbtype = DbContextHolder.getDbType();
- if (targetSqlMapClients!= null && targetSqlMapClients.containsKey(dbtype)){
- SqlMapClient sqlMapClient = targetSqlMapClients.get(dbtype);
- sqlMapClientTemplate = 新的 SqlMapClientTemplate(sqlMapClient);
- }
- 返回这个.sqlMapClientTemplate;
- }
- @覆盖
- protected final void checkDaoConfig(){
- 如果 (!this .externalTemplate){
- 这个.sqlMapClientTemplate.afterPropertiesSet();
- }
- }
- public Map <String,SqlMapClient> getTargetSqlMapClients(){
- 返回 targetSqlMapClients;
- }
- public void setTargetSqlMapClients(Map <String,SqlMapClient> targetSqlMapClients){
- 这个.targetSqlMapClients = targetSqlMapClients;
- }
- public SqlMapClient getDefaultSqlMapClient(){
- 返回 defaultSqlMapClient;
- }
- public void setDefaultSqlMapClient(SqlMapClient defaultSqlMapClient){
- 这个.defaultSqlMapClient = defaultSqlMapClient;
- }
- }
IbatisDaoSupport.java
- 包 com.ssi.dao;
- import java.io.Serializable;
- import java.sql.SQLException;
- import java.util.List;
- import java.util.Map;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.orm.ibatis.SqlMapClientCallback;
- import com.ibatis.sqlmap.client.SqlMapExecutor;
- @SuppressWarnings (“未选中” )
- 公共类 IbatisDaoSupport <实体> 扩展 DynamicSqlClientDaoSupport 实现 IEntityDao <实体> {
- 受保护的最终 日志日志= LogFactory.getLog(getClass());
- 公共 实体get(String sqlId,Serializable id){
- return (Entity)getSqlMapClientTemplate()。queryForObject(sqlId,id);
- }
- 公共 实体getByParamMap(字符串sqlId,对象参数){
- return (Entity)getSqlMapClientTemplate()。queryForObject(sqlId,param);
- }
- public Object save(String sqlId,Object o){
- 返回 getSqlMapClientTemplate()。insert(sqlId,o);
- }
- public Object batchSave(final String sqlId,final List <Entity> entityList) throws Exception {
- //执行回调
- 返回 getSqlMapClientTemplate()。execute(新的 SqlMapClientCallback(){
- //实现回调接口
- 公共 对象doInSqlMapClient(SqlMapExecutor执行器) throws SQLException {
- //开始批处理
- executor.startBatch();
- for (Entity entity:entityList){
- executor.insert(sqlId,entity);
- }
- return executor.executeBatch();
- }
- });
- }
- public Integer remove(String sqlId,Object o){
- 返回 getSqlMapClientTemplate()。delete(sqlId,o);
- }
- public Integer removeById(String sqlId,Serializable id){
- 返回 getSqlMapClientTemplate()。delete(sqlId,id);
- }
- public Integer update(String sqlId,Object o){
- 返回 getSqlMapClientTemplate()。update(sqlId,o);
- }
- public long totalCount(String sqlId,Object o){
- return (Long)getSqlMapClientTemplate()。queryForObject(sqlId,o);
- }
- public List <Entity> pagedList(String sqlId,Map <String,Object> map,int pageSize, int pageNum){
- int start =(pageNum - 1 )* pageSize;
- map.put(“开始” ,开始);
- map.put(“pageSize” ,pageSize);
- List <Entity> list = getSqlMapClientTemplate()。queryForList(sqlId,map);
- 退货 清单;
- }
- public List <Entity> list(String sqlId,Object o){
- 返回 getSqlMapClientTemplate()。queryForList(sqlId,o);
- }
- public List <Entity> list(String sqlId){
- 返回 getSqlMapClientTemplate()。queryForList(sqlId);
- }
- }
UserDaoImpl.java:
- 包 com.ssi.dao.impl;
- import org.springframework.stereotype.Repository;
- import com.ssi.dao.IUserDao;
- import com.ssi.dao.IbatisDaoSupport;
- import com.ssi.model.User;
- @Repository (“userDAO的” )
- 公共类 UserDaoImpl 扩展 IbatisDaoSupport <用户> 实现 IUserDao {
- 公共 整数addUser(用户用户) 抛出 异常{
- 返回 (整数) 这个.save(“User.insert” ,用户);
- }
- }
UserServiceImpl.java
- 包 com.ssi.service.impl;
- import javax.annotation.Resource;
- import org.springframework.stereotype.Service;
- import com.ssi.dao.IUserDao;
- import com.ssi.datasource.DbContextHolder;
- import com.ssi.model.User;
- import com.ssi.service.IUserService;
- @Service (“userService” )
- 公共类 UserServiceImpl 实现 IUserService {
- @Resource private IUserDao userDao;
- / **
- *测试在服务中切换数据源异常是否回滚
- * /
- 公共无效 addUser(用户用户) 抛出 异常{
- DbContextHolder.setDbType(“db1” );
- userDao.addUser(用户);
- DbContextHolder.setDbType(“db2” );
- user.setUserName(“user2” );
- userDao.addUser(用户);
- DbContextHolder.setDbType(“center” );
- user.setUserName(“user3” );
- userDao.addUser(用户);
- //System.out.println(1/0);
- }
- }
DynamicDataSource.java:
- public class DynamicDataSource extends AbstractRoutingDataSource {
- 静态 记录仪日志= Logger.getLogger(DynamicDataSource。类);
- protected Object determineCurrentLookupKey(){
- 返回 DbContextHolder.getDbType();
- }
- }
DbContextHolder.java:
- 公共类 DbContextHolder {
- private static final ThreadLocal contextHolder = new ThreadLocal();
- public static void setDbType(String dbType){
- contextHolder.set(DBTYPE);
- }
- public static String getDbType(){
- return (String)contextHolder.get();
- }
- public static void clearDbType(){
- contextHolder.remove();
- }
- }
三个数据库:dbcenter,db1,db2表结构均相同
脚本:
- DROP TABLE IF EXISTS`tb_user`;
- CREATE TABLE `tb_user`(
- `id` int (11) NOT NULL AUTO_INCREMENT,
- `userName` varchar (20) DEFAULT NULL ,
- ` 密码` VARCHAR (60) DEFAULT NULL ,
- PRIMARY KEY (`id`)
- )ENGINE = InnoDB DEFAULT CHARSET = utf8;
单元测试:
- 公共类 JunitTest {
- public ApplicationContext cxt;
- @测试
- 公共无效的 init() 抛出 异常{
- cxt = new ClassPathXmlApplicationContext(new String [] { “applicationContext.xml” ,“applicationContext-datasource.xml” });
- testInsertUser();
- }
- 私人无效 testInsertUser() 抛出 异常{
- IUserService userService =(IUserService)cxt.getBean(“userService” );
- 用户用户= 新 用户();
- user.setUserName(“user1” );
- user.setPassword(“0” );
- userService.addUser(用户);
- }
- 私人无效 testInsertUser2() 抛出 异常{
- }