博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring+mybatis+tkmapper+atomikos实现分布式事务(3)-动态切换数据源
阅读量:6983 次
发布时间:2019-06-27

本文共 26348 字,大约阅读时间需要 87 分钟。

本文介绍基于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如下:

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

2,数据库连接配置文件:jdbc.properties

jdbc.xaDataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlXADataSourcevalidationQuery=select 1ds1.jdbc.url=jdbc:mysql://xx.xx.xx.xx:3306/test1?characterEncoding=UTF-8ds1.jdbc.username=xxxxds1.jdbc.password=xxxxds2.jdbc.url=jdbc:mysql://xx.xx.xx.xx:3306/test2?characterEncoding=UTF-8ds2.jdbc.username=xxxxds2.jdbc.password=xxxx

3,atomikos配置文件:jta.properties

com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactorycom.atomikos.icatch.console_file_name=tm.release.outcom.atomikos.icatch.log_base_name=tm.releaselogcom.atomikos.icatch.tm_unique_name=com.atomikos.spring.jdbc.tm.releasecom.atomikos.icatch.console_log_level=INFO

4,spring-datasource-jta.xml

classpath*:jdbc.properties
${ds1.jdbc.url}
${ds1.jdbc.username}
${ds1.jdbc.password}
${ds2.jdbc.url}
${ds2.jdbc.username}
${ds2.jdbc.password}
true

5,spring-mybatis-tk-dynamic.xml

classpath:mybatis/tk/model/*.xml
classpath:mybatis/tk/model/*.xml

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.javapackage mybatis.tk.model;import org.springframework.stereotype.Repository;import tk.mybatis.mapper.common.Mapper;@Repository(value="helloMapper")public interface HelloMapper extends Mapper
{}

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 = "helloWorldController")@RequestMapping(value = "/hello")public class HelloWorldControler {    @Autowired    private HelloWorldService helloWorldService;    @Transactional    @RequestMapping(value="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.SpringJUnit4Cla***unner;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(SpringJUnit4Cla***unner.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 = "{\"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
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(); }}

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;/** * function: 继承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 hoojo_@126.com * @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
targetSqlSessionFactorys; private SqlSessionFactory defaultTargetSqlSessionFactory; public void setTargetSqlSessionFactorys(Map
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
getAllConfigurations() { List
list = new ArrayList
(); for(Map.Entry
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 selectOne(String statement) { return this.sqlSessionProxy.
selectOne(statement); } /** * {@inheritDoc} */ public
T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.
selectOne(statement, parameter); } /** * {@inheritDoc} */ public
Map
selectMap(String statement, String mapKey) { return this.sqlSessionProxy.
selectMap(statement, mapKey); } /** * {@inheritDoc} */ public
Map
selectMap(String statement, Object parameter, String mapKey) { return this.sqlSessionProxy.
selectMap(statement, parameter, mapKey); } /** * {@inheritDoc} */ public
Map
selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { return this.sqlSessionProxy.
selectMap(statement, parameter, mapKey, rowBounds); } /** * {@inheritDoc} */ public
List
selectList(String statement) { return this.sqlSessionProxy.
selectList(statement); } /** * {@inheritDoc} */ public
List
selectList(String statement, Object parameter) { return this.sqlSessionProxy.
selectList(statement, parameter); } /** * {@inheritDoc} */ public
List
selectList(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.
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 getMapper(Class
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
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
extends org.mybatis.spring.mapper.MapperFactoryBean
implements ApplicationContextAware{ private MapperHelper mapperHelper; private String sqlSessionTemplateBeanName; private ApplicationContext applicationContext; public MapperFactoryBean() { super(); } public MapperFactoryBean(Class
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
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映射文件的编写。

转载于:https://blog.51cto.com/13166529/2082683

你可能感兴趣的文章
新年图书整理和相关的产品
查看>>
Struts2的核心文件
查看>>
Spring Boot集成Jasypt安全框架
查看>>
GIS基础软件及操作(十)
查看>>
HDOJ 2041 超级楼梯
查看>>
1108File Space Bitmap Block损坏能修复吗2
查看>>
遭遇DBD::mysql::dr::imp_data_size unexpectedly
查看>>
人人都会设计模式:03-策略模式--Strategy
查看>>
被忽视但很实用的那部分SQL
查看>>
解读阿里云oss-android/ios-sdk 断点续传(多线程)
查看>>
ML之监督学习算法之分类算法一 ——— 决策树算法
查看>>
骡夫电商地址
查看>>
亚信安全火力全开猎捕“坏兔子”,全歼详解
查看>>
智能家居——IoT零基础入门篇
查看>>
《Linux From Scratch》第一部分:介绍 第一章:介绍-1.3. 更新日志
查看>>
阿里将在雄安新区设3家子公司:涉AI、蚂蚁金服和菜鸟;北航设立全国首个人工智能专业,与百度合作办学...
查看>>
Powershell指令集_2
查看>>
归并排序算法
查看>>
北京第一个公共云计算平台即将诞生
查看>>
5G频谱相争“兵戎相见”各相部署风起云涌
查看>>