spring+mybatis+tkmapper+atomikos实现分布式事务-动态切换数据源

【spring+mybatis+tkmapper+atomikos实现分布式事务-动态切换数据源】行是知之始,知是行之成。这篇文章主要讲述spring+mybatis+tkmapper+atomikos实现分布式事务-动态切换数据源相关的知识,希望能为你提供帮助。
本文介绍基于spring+mybatis+tkmapper+atomikos+jta实现分布式事务,由程序动态切换数据源,通过atomikos可实现分布式事务一致性。通过继承MapperScannerConfigurer、MapperFactoryBean等类,解决多数据源时,切换到第一个数据源之外的数据源时,找不到tk的Mapper上的方法的问题。版本:spring-3.2.9.RELEASE、mybatis-3.4.4、atomikos-4.0.5、jdk1.8、tk.mybatis-3.4.3
1,maven配置文件pom.xml如下:

< !-- test --> < dependency> < groupId> junit< /groupId> < artifactId> junit< /artifactId> < version> 4.12< /version> < scope> test< /scope> < exclusions> < exclusion> < artifactId> hamcrest-core< /artifactId> < groupId> org.hamcrest< /groupId> < /exclusion> < /exclusions> < /dependency> < dependency> < groupId> org.hamcrest< /groupId> < artifactId> hamcrest-all< /artifactId> < version> 1.3< /version> < scope> test< /scope> < /dependency> < dependency> < groupId> org.mockito< /groupId> < artifactId> mockito-core< /artifactId> < version> 1.9.5< /version> < scope> test< /scope> < exclusions> < exclusion> < artifactId> hamcrest-core< /artifactId> < groupId> org.hamcrest< /groupId> < /exclusion> < /exclusions> < /dependency> < dependency> < groupId> com.jayway.jsonpath< /groupId> < artifactId> json-path< /artifactId> < version> 0.8.1< /version> < scope> test< /scope> < /dependency> < !-- spring --> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-aop< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-beans< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-context< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-core< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-expression< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-web< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-webmvc< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-webmvc-portlet< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-tx< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-jdbc< /artifactId> < version> ${spring.version}< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-test< /artifactId> < version> 3.2.18.RELEASE< /version> < /dependency> < dependency> < groupId> org.springframework< /groupId> < artifactId> spring-struts< /artifactId> < version> ${spring.version}< /version> < /dependency> < !-- mybatis --> < dependency> < groupId> org.mybatis< /groupId> < artifactId> mybatis< /artifactId> < version> 3.4.4< /version> < /dependency> < dependency> < groupId> org.mybatis< /groupId> < artifactId> mybatis-spring< /artifactId> < version> 1.3.1< /version> < /dependency> < !-- tk.mybatis --> < dependency> < groupId> tk.mybatis< /groupId> < artifactId> mapper< /artifactId> < version> 3.4.3< /version> < /dependency> < !-- mysql-jdbc --> < dependency> < groupId> mysql< /groupId> < artifactId> mysql-connector-java< /artifactId> < version> 5.1.27< /version> < /dependency> < !-- atomikos begin --> < dependency> < groupId> com.atomikos< /groupId> < artifactId> transactions-jdbc< /artifactId> < version> 4.0.5< /version> < /dependency> < !-- atomikos end --> < !-- jta begig --> < dependency> < groupId> javax.transaction< /groupId> < artifactId> jta< /artifactId> < version> 1.1< /version> < /dependency> < !-- jta end --> < !-- jackson --> < dependency> < groupId> org.codehaus.jackson< /groupId> < artifactId> jackson-mapper-asl< /artifactId> < version> 1.9.13< /version> < /dependency> < dependency> < groupId> org.codehaus.jackson< /groupId> < artifactId> jackson-core-asl< /artifactId> < version> 1.9.13< /version> < /dependency> < !-- jackson end --> < dependency> < groupId> org.aspectj< /groupId> < artifactId> aspectjweaver< /artifactId> < version> 1.8.13< /version> < /dependency> < dependency> < groupId> log4j< /groupId> < artifactId> log4j< /artifactId> < version> 1.2.16< /version> < /dependency> < dependency> < groupId> javax.servlet< /groupId> < artifactId> servlet-api< /artifactId> < version> 2.5< /version> < scope> provided< /scope> < /dependency>

2,数据库连接配置文件:jdbc.properties
jdbc.xaDataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource validationQuery=select 1 ds1.jdbc.url=jdbc:mysql://xx.xx.xx.xx:3306/test1?characterEncoding=UTF-8 ds1.jdbc.username=xxxx ds1.jdbc.password=xxxxds2.jdbc.url=jdbc:mysql://xx.xx.xx.xx:3306/test2?characterEncoding=UTF-8 ds2.jdbc.username=xxxx ds2.jdbc.password=xxxx

3,atomikos配置文件:jta.properties
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.console_file_name=tm.release.out com.atomikos.icatch.log_base_name=tm.releaselog com.atomikos.icatch.tm_unique_name=com.atomikos.spring.jdbc.tm.release com.atomikos.icatch.console_log_level=INFO

4,spring-datasource-jta.xml
< ?xml version="1.0" encoding="UTF-8"?> < !DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> < beans> < bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> < property name="locations"> < list> < value> classpath*:jdbc.properties< /value> < /list> < /property> < /bean> < bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"> < property name="transactionManager"> < bean class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> < property name="forceShutdown" value="https://www.songbingjia.com/android/true"> < /property> < /bean> < /property> < property name="userTransaction"> < bean class="com.atomikos.icatch.jta.UserTransactionImp"> < property name="transactionTimeout" value="https://www.songbingjia.com/android/300"> < /property> < /bean> < /property> < /bean> < !-- datasource --> < bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close" init-method="init" abstract="true"> < property name="poolSize" value="https://www.songbingjia.com/android/10" /> < property name="minPoolSize" value="https://www.songbingjia.com/android/10" /> < property name="maxPoolSize" value="https://www.songbingjia.com/android/30" /> < property name="borrowConnectionTimeout" value="https://www.songbingjia.com/android/60" /> < property name="reapTimeout" value="https://www.songbingjia.com/android/20" /> < property name="maxIdleTime" value="https://www.songbingjia.com/android/60" /> < property name="maintenanceInterval" value="https://www.songbingjia.com/android/60" /> < property name="loginTimeout" value="https://www.songbingjia.com/android/60" /> < property name="testQuery" value="https://www.songbingjia.com/android/${validationQuery}" /> < /bean> < bean id="dataSource" parent="abstractXADataSource"> < property name="uniqueResourceName" value="https://www.songbingjia.com/android/ds1"> < /property> < property name="xaDataSourceClassName" value="https://www.songbingjia.com/android/${jdbc.xaDataSourceClassName}"> < /property> < property name="xaProperties"> < props> < prop key="url"> ${ds1.jdbc.url}< /prop> < prop key="user"> ${ds1.jdbc.username}< /prop> < prop key="password"> ${ds1.jdbc.password}< /prop> < /props> < /property> < /bean> < bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close" init-method="init"> < property name="uniqueResourceName" value="https://www.songbingjia.com/android/ds2"> < /property> < property name="xaDataSourceClassName" value="https://www.songbingjia.com/android/${jdbc.xaDataSourceClassName}"> < /property> < property name="xaProperties"> < props> < prop key="url"> ${ds2.jdbc.url}< /prop> < prop key="user"> ${ds2.jdbc.username}< /prop> < prop key="password"> ${ds2.jdbc.password}< /prop> < prop key="pinGlobalTxToPhysicalConnection"> true< /prop> < /props> < /property> < property name="minPoolSize" value="https://www.songbingjia.com/android/10"> < /property> < property name="maxPoolSize" value="https://www.songbingjia.com/android/20"> < /property> < property name="borrowConnectionTimeout" value="https://www.songbingjia.com/android/30"> < /property> < property name="testQuery" value="https://www.songbingjia.com/android/select 1"> < /property> < /bean> < /beans>

5,spring-mybatis-tk-dynamic.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-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> < tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" /> < context:component-scan base-package="mybatis.tk.service" /> < context:component-scan base-package="mybatis.tk.controller" /> < bean id="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"> < property name="dataSource" ref="dataSource" /> < property name="mapperLocations"> < array> < value> classpath:mybatis/tk/model/*.xml< /value> < /array> < /property> < /bean> < bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean"> < property name="dataSource" ref="dataSource2" /> < property name="mapperLocations"> < array> < value> classpath:mybatis/tk/model/*.xml< /value> < /array> < /property> < property name="typeAliasesPackage" value="https://www.songbingjia.com/android/com.isea533.mybatis.model" /> < /bean> < bean id="mySqlSessionTemplate" class="spring.jta.util.CustomSqlSessionTemplate"> < constructor-arg ref="sqlSessionFactory"> < /constructor-arg> < property name="targetSqlSessionFactorys"> < map> < entry key="dataSource" value-ref="sqlSessionFactory" /> < entry key="dataSource2" value-ref="sqlSessionFactory2" /> < /map> < /property> < property name="typeAliasesPackage" value="https://www.songbingjia.com/android/com.isea533.mybatis.model" /> < /bean> < bean class="spring.jta.util.MyMapperScannerConfigurer"> < property name="basePackage" value="https://www.songbingjia.com/android/mybatis.tk.model" /> < property name="sqlSessionTemplateBeanName" value="https://www.songbingjia.com/android/mySqlSessionTemplate" /> < property name="markerInterface" value="https://www.songbingjia.com/android/tk.mybatis.mapper.common.Mapper" /> < /bean> < bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> < !-- Object-> json,解决 Could not find acceptable representation 问题 --> < property name="messageConverters"> < list> < bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" /> < /list> < /property> < /bean> < /beans>

6,编写mybatis.tk.model代码,HelloDO.java、HelloMapper.java
package mybatis.tk.model; import javax.persistence.Id; import javax.persistence.Table; @Table(name = "HELLO") public class HelloDO { @Id private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }HelloMapper.java package mybatis.tk.model; import org.springframework.stereotype.Repository; import tk.mybatis.mapper.common.Mapper; @Repository(value="https://www.songbingjia.com/android/helloMapper") public interface HelloMapper extends Mapper< HelloDO> { }

7,编写service类:HelloWorldService.java
package mybatis.tk.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import mybatis.tk.model.HelloDO; import mybatis.tk.model.HelloMapper; @Service("helloWorldService") public class HelloWorldService { @Autowired private HelloMapper helloMapper; public void addHello(HelloDO helloDO) { // TODO Auto-generated method stub helloMapper.insert(helloDO); } }

8,编写controller类:HelloWorldController.java
package mybatis.tk.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import mybatis.tk.model.HelloDO; import mybatis.tk.service.HelloWorldService; import spring.jta.util.DataSourceContextHolder; @Controller(value = "https://www.songbingjia.com/android/helloWorldController") @RequestMapping(value = "https://www.songbingjia.com/hello") public class HelloWorldControler { @Autowired private HelloWorldService helloWorldService; @Transactional @RequestMapping(value="https://www.songbingjia.com/android/create",method=RequestMethod.POST) public @ResponseBody long createHelloDynamic(@RequestBody HelloDO helloDO) { DataSourceContextHolder.setDBType("dataSource"); helloWorldService.addHello(helloDO); DataSourceContextHolder.setDBType("dataSource2"); helloWorldService.addHello(helloDO); return helloDO.getId(); } }

9,编写测试类:HelloWorldControlerDynamicTest.java
package mybatis.controller; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectWriter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import mybatis.model.HelloDO; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "classpath:spring-datasource-jta.xml", "classpath:spring-mybatis-dynamic.xml" }) public class HelloWorldControlerDynamicTest { @Autowired private WebApplicationContext context; private MockMvc mocMvc; @Before public void setUp() throws Exception { mocMvc = MockMvcBuilders.webAppContextSetup(context).build(); }@Test public void test() throws Exception { String data = "https://www.songbingjia.com/android/{/"id\":1,\"name\":\"abc\"}"; HelloDO hello = new HelloDO(); hello.setId(1L); hello.setName("abc"); ObjectMapper mapper = new ObjectMapper(); ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter(); java.lang.String requestJson = ow.writeValueAsString(hello); mocMvc.perform(post("/hello/create_dynamic").contentType(MediaType.APPLICATION_JSON).content(data)).andExpect(status().isOk()); } }

10,编写数据源切换类 DataSourceContextHolder.java
package spring.jta.util; public class DataSourceContextHolder { private static final ThreadLocal< String> contextHolder = new ThreadLocal< String> (); public static void setDBType(String dbType) { contextHolder.set(dbType); }public static String getDBType() { return ((String) contextHolder.get()); }public static void clearDBType() { contextHolder.remove(); } }

11,编写CustomSqlSessionTemplate.java,该类扩展SqlSessionTemplate,结合DataSourceContextHolder实现sqlSession的切换
package spring.jta.util; import static java.lang.reflect.Proxy.newProxyInstance; import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; import static org.mybatis.spring.SqlSessionUtils.closeSqlSession; import static org.mybatis.spring.SqlSessionUtils.getSqlSession; import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.Connection; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.MyBatisExceptionTranslator; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.util.Assert; /** * < b> function:< /b> 继承SqlSessionTemplate 重写相关方法 * @author hoojo * @createDate 2013-10-18 下午03:07:46 * @file CustomSqlSessionTemplate.java * @package com.hoo.framework.mybatis.support * @project SHMB * @blog http://blog.csdn.net/IBM_hoojo * @email [email  protected] * @version 1.0 */ public class CustomSqlSessionTemplate extends SqlSessionTemplate {private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; private Map< Object, SqlSessionFactory> targetSqlSessionFactorys; private SqlSessionFactory defaultTargetSqlSessionFactory; public void setTargetSqlSessionFactorys(Map< Object, SqlSessionFactory> targetSqlSessionFactorys) { this.targetSqlSessionFactorys = targetSqlSessionFactorys; }public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) { this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; }public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); }public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration() .getEnvironment().getDataSource(), true)); }public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {super(sqlSessionFactory, executorType, exceptionTranslator); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); this.defaultTargetSqlSessionFactory = sqlSessionFactory; }@Override public SqlSessionFactory getSqlSessionFactory() {SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DataSourceContextHolder.getDBType()); if (targetSqlSessionFactory != null) { return targetSqlSessionFactory; } else if (defaultTargetSqlSessionFactory != null) { return defaultTargetSqlSessionFactory; } else { Assert.notNull(targetSqlSessionFactorys, "Property ‘targetSqlSessionFactorys‘ or ‘defaultTargetSqlSessionFactory‘ are required"); Assert.notNull(defaultTargetSqlSessionFactory, "Property ‘defaultTargetSqlSessionFactory‘ or ‘targetSqlSessionFactorys‘ are required"); } return this.sqlSessionFactory; }public List< Configuration> getAllConfigurations() { List< Configuration> list = new ArrayList< Configuration> (); for(Map.Entry< Object, SqlSessionFactory> entry : targetSqlSessionFactorys.entrySet()) { SqlSessionFactory sqlSessionFactory = entry.getValue(); list.add(sqlSessionFactory.getConfiguration()); } return list; }@Override public Configuration getConfiguration() { return this.getSqlSessionFactory().getConfiguration(); }public ExecutorType getExecutorType() { return this.executorType; }public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { return this.exceptionTranslator; }/** * {@inheritDoc} */ public < T> T selectOne(String statement) { return this.sqlSessionProxy.< T> selectOne(statement); }/** * {@inheritDoc} */ public < T> T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.< T> selectOne(statement, parameter); }/** * {@inheritDoc} */ public < K, V> Map< K, V> selectMap(String statement, String mapKey) { return this.sqlSessionProxy.< K, V> selectMap(statement, mapKey); }/** * {@inheritDoc} */ public < K, V> Map< K, V> selectMap(String statement, Object parameter, String mapKey) { return this.sqlSessionProxy.< K, V> selectMap(statement, parameter, mapKey); }/** * {@inheritDoc} */ public < K, V> Map< K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { return this.sqlSessionProxy.< K, V> selectMap(statement, parameter, mapKey, rowBounds); }/** * {@inheritDoc} */ public < E> List< E> selectList(String statement) { return this.sqlSessionProxy.< E> selectList(statement); }/** * {@inheritDoc} */ public < E> List< E> selectList(String statement, Object parameter) { return this.sqlSessionProxy.< E> selectList(statement, parameter); }/** * {@inheritDoc} */ public < E> List< E> selectList(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.< E> selectList(statement, parameter, rowBounds); }/** * {@inheritDoc} */ public void select(String statement, ResultHandler handler) { this.sqlSessionProxy.select(statement, handler); }/** * {@inheritDoc} */ public void select(String statement, Object parameter, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, handler); }/** * {@inheritDoc} */ public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); }/** * {@inheritDoc} */ public int insert(String statement) { return this.sqlSessionProxy.insert(statement); }/** * {@inheritDoc} */ public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); }/** * {@inheritDoc} */ public int update(String statement) { return this.sqlSessionProxy.update(statement); }/** * {@inheritDoc} */ public int update(String statement, Object parameter) { return this.sqlSessionProxy.update(statement, parameter); }/** * {@inheritDoc} */ public int delete(String statement) { return this.sqlSessionProxy.delete(statement); }/** * {@inheritDoc} */ public int delete(String statement, Object parameter) { return this.sqlSessionProxy.delete(statement, parameter); }/** * {@inheritDoc} */ public < T> T getMapper(Class< T> type) { return getConfiguration().getMapper(type, this); }/** * {@inheritDoc} */ public void commit() { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); }/** * {@inheritDoc} */ public void commit(boolean force) { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); }/** * {@inheritDoc} */ public void rollback() { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); }/** * {@inheritDoc} */ public void rollback(boolean force) { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); }/** * {@inheritDoc} */ public void close() { throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); }/** * {@inheritDoc} */ public void clearCache() { this.sqlSessionProxy.clearCache(); }/** * {@inheritDoc} */ public Connection getConnection() { return this.sqlSessionProxy.getConnection(); }/** * {@inheritDoc} * @since 1.0.2 */ public List< BatchResult> flushStatements() { return this.sqlSessionProxy.flushStatements(); }/** * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring‘s Transaction Manager It also * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to * the {@code PersistenceExceptionTranslator}. */ private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final SqlSession sqlSession = getSqlSession( CustomSqlSessionTemplate.this.getSqlSessionFactory(), CustomSqlSessionTemplate.this.executorType, CustomSqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (CustomSqlSessionTemplate.this.exceptionTranslator != null & & unwrapped instanceof PersistenceException) { Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory()); } } } }

12,编写MapperFactoryBean.java,该类继承org.mybatis.spring.mapper.MapperFactoryBean,通过重写checkDaoConfig(),把tk的Mapper接口的所有方法配置到org.apache.ibatis.session.Configuration上,解决多个数据源时找不到方法的问题。
package spring.jta.util; import java.util.List; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.session.Configuration; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import tk.mybatis.mapper.mapperhelper.MapperHelper; public class MapperFactoryBean< T> extends org.mybatis.spring.mapper.MapperFactoryBean< T> implements ApplicationContextAware{ private MapperHelper mapperHelper; private String sqlSessionTemplateBeanName; private ApplicationContext applicationContext; public MapperFactoryBean() { super(); }public MapperFactoryBean(Class< T> mapperInterface) { super(mapperInterface); }public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) { this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName; }public void setMapperHelper(MapperHelper mapperHelper) { this.mapperHelper = mapperHelper; }@Override protected void checkDaoConfig() { CustomSqlSessionTemplate customSqlSessionTemplate = (CustomSqlSessionTemplate)this.applicationContext.getBean(this.sqlSessionTemplateBeanName); List< Configuration> configurations = customSqlSessionTemplate.getAllConfigurations(); for (Configuration configuration : configurations) { if (isAddToConfig() & & !configuration.hasMapper(getMapperInterface())) { try { configuration.addMapper(getMapperInterface()); } catch (Exception e) { logger.error("Error while adding the mapper ‘" + getMapperInterface() + "‘ to configuration.", e); throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } // 通用Mapper if (mapperHelper.isExtendCommonMapper(getObjectType())) { mapperHelper.processConfiguration(configuration, getObjectType()); } } }@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // TODO Auto-generated method stub this.applicationContext = applicationContext; } }

13,编写MyMapperScannerConfigurer.java,该类继承org.mybatis.spring.mapper.MapperScannerConfigurer
package spring.jta.util; import java.util.Properties; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import tk.mybatis.mapper.common.Marker; import tk.mybatis.mapper.mapperhelper.MapperHelper; import tk.mybatis.mapper.util.StringUtil; import org.mybatis.spring.mapper.MapperScannerConfigurer; public class MyMapperScannerConfigurer extends MapperScannerConfigurer { private MapperHelper mapperHelper = new MapperHelper(); private String sqlSessionTemplateBeanName; public void setMarkerInterface(Class< ?> superClass) { super.setMarkerInterface(superClass); if (Marker.class.isAssignableFrom(superClass)) { mapperHelper.registerMapper(superClass); } }public MapperHelper getMapperHelper() { return mapperHelper; }public void setMapperHelper(MapperHelper mapperHelper) { this.mapperHelper = mapperHelper; }public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) { super.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName); this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName; }/** * 属性注入 * * @param properties */ public void setProperties(Properties properties) { mapperHelper.setProperties(properties); }@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { super.postProcessBeanDefinitionRegistry(registry); // 如果没有注册过接口,就注册默认的Mapper接口 this.mapperHelper.ifEmptyRegisterDefaultInterface(); String[] names = registry.getBeanDefinitionNames(); GenericBeanDefinition definition; for (String name : names) { BeanDefinition beanDefinition = registry.getBeanDefinition(name); if (beanDefinition instanceof GenericBeanDefinition) { definition = (GenericBeanDefinition) beanDefinition; if (StringUtil.isNotEmpty(definition.getBeanClassName()) & & definition.getBeanClassName().equals("org.mybatis.spring.mapper.MapperFactoryBean")) { definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("mapperHelper", this.mapperHelper); definition.getPropertyValues().add("sqlSessionTemplateBeanName", this.sqlSessionTemplateBeanName); } } } } }

14,至此,基于tkmapper的可由程序动态切换数据源,并实现分布式事务一致性已经实现,对于单表的CRUD操作,可以省略xml映射文件的编写。

    推荐阅读