本文主要介绍 JdbcTempalte、Mybatis 在动态数据源下的配置及使用,实际的应用里可以根据情况选择其中之一或同时使用;动态数据源使用自定义的注解通过切面来实现数据源的切换;事务使用基于 Atomicos 的 XA 事务。文中所使用到的软件版本:Spring Boot 2.4.4、jdk1.8.0_181、Mybatis 3.5.6、Atomicos 4.0.6。

1、工程整体结构

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring

2、pom.xml

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring_02Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_ide_03


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.abc.demo</groupId>
<artifactId>demosb-dd</artifactId>
<version>1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath />
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>

<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.2.0</version>
<!--systemPath>E:/bin/jar/oracle/ojdbc6.jar</systemPath>
<scope>system</scope-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>

<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

pom.xml

3、application.yml


spring:
datasource:
atomic:
datasource1:
uniqueResourceName: datasource1
xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
testQuery: select 1
maxPoolSize: 2
minPoolSize: 2
poolSize: 2
borrowConnectionTimeout: 60
reapTimeout: 360
maxIdleTime: 60
maintenanceInterval: 60
loginTimeout: 60
xaProperties:
URL: jdbc:mysql://10.198.0.10:3306/itest?useUnicode=true&characterEncoding=UTF-8
user: root
password: 123456
datasource2:
uniqueResourceName: datasource2
xaDataSourceClassName: oracle.jdbc.xa.client.OracleXADataSource
testQuery: select 1 from dual
maxPoolSize: 2
minPoolSize: 2
poolSize: 2
borrowConnectionTimeout: 60
reapTimeout: 360
maxIdleTime: 60
maintenanceInterval: 60
loginTimeout: 60
xaProperties:
URL: jdbc:oracle:thin:@10.198.0.10:1521:test
user: test
password: 123456


4、配置

4.1、动态数据源配置

4.1.1、自定义数据源注解


package com.abc.demo.datasource;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
String value() default "";
}


4.1.2、自定义数据源切面


package com.abc.demo.datasource;


import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DataSourceAspect {
private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

@Pointcut("@annotation(com.abc.demo.datasource.DataSource) || @within(com.abc.demo.datasource.DataSource)")
public void dataSourcePointCut() {
Object d;
}

//@Around("dataSourcePointCut()")
@Around("execution(* com.abc.demo.dao..*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Class targetClass = point.getTarget().getClass();
Method method = signature.getMethod();

String dataSourceName = "";
DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
DataSource methodDataSource = method.getAnnotation(DataSource.class);
if (targetDataSource != null || methodDataSource != null){
if (methodDataSource != null) {
dataSourceName = methodDataSource.value();
} else {
dataSourceName = targetDataSource.value();
}

//从接口中找注解
} else {
for (Class<?> clazz : targetClass.getInterfaces()) {
Class<?>[] types = method.getParameterTypes();

//接口上的注解
if (clazz.isAnnotationPresent(DataSource.class)) {
DataSource source = clazz.getAnnotation(DataSource.class);
dataSourceName = source.value();
}

//接口方法注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
dataSourceName = source.value();
}

if (StringUtils.isNotBlank(dataSourceName)) {
break;
}
}
}

if (StringUtils.isNotBlank(dataSourceName)) {
DynamicContextHolder.push(dataSourceName);
logger.info("the datasource is {}", dataSourceName);
}
try {
return point.proceed();
} finally {
if (StringUtils.isNotBlank(dataSourceName)) {
DynamicContextHolder.poll();
}
logger.info("clean datasource");
}
}
}


4.1.3、动态数据源线程中实际数据源保存


package com.abc.demo.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayDeque;
import java.util.Deque;

public class DynamicContextHolder {
private static Logger logger = LoggerFactory.getLogger(DynamicContextHolder.class);

private static final ThreadLocal<Deque<String>> CONTEXT_HOLDER = new ThreadLocal() {
@Override
protected Object initialValue() {
return new ArrayDeque();
}
};

/**
* 获得当前线程数据源
* @return 数据源名称
*/
public static String peek() {
String key = CONTEXT_HOLDER.get().peek();
logger.info("key={}", key);
return key;
}

/**
* 设置当前线程数据源
* @param dataSource 数据源名称
*/
public static void push(String dataSource) {
CONTEXT_HOLDER.get().push(dataSource);
}

/**
* 清空当前线程数据源
*/
public static void poll() {
Deque<String> deque = CONTEXT_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
CONTEXT_HOLDER.remove();
}
}

}


4.1.4、自定义动态数据源


package com.abc.demo.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicContextHolder.peek();
}

public javax.sql.DataSource getTargetDataSource() {
return this.determineTargetDataSource();
}

}


4.1.5、事务及动态数据源配置


package com.abc.demo.datasource;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DynamicDataSourceConfig {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceConfig.class);

@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}

@Bean(name = "userTransactionManager", initMethod = "init", destroyMethod = "close")
public UserTransactionManager userTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}

@Bean(name = "xatm")
@DependsOn({"userTransaction", "userTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
return new JtaTransactionManager(userTransaction(), userTransactionManager());
}

@Bean(name = "dataSource1")
@ConfigurationProperties(prefix="spring.datasource.atomic.datasource1")
public DataSource dataSource1() {
return new AtomikosDataSourceBean();
}

@Bean(name = "dataSource2")
@ConfigurationProperties(prefix="spring.datasource.atomic.datasource2")
public DataSource dataSource2() {
return new AtomikosDataSourceBean();
}

@Bean(name = "dynamicDataSource")
@DependsOn({"xatm"})
public DynamicDataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("dataSource1", dataSource1());
targetDataSources.put("dataSource2", dataSource2());
dynamicDataSource.setTargetDataSources(targetDataSources);

dynamicDataSource.setDefaultTargetDataSource(dataSource1());

return dynamicDataSource;
}
}


4.2、JdbcTemplate 配置


package com.abc.demo.config;

import com.abc.demo.datasource.DynamicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;


@Configuration
public class JdbcTempalteConfig {
// @Bean
// public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) {
// return new JdbcTemplate(dynamicDataSource);
// }

@Bean
public MyJdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) {
return new MyJdbcTemplate(dynamicDataSource);
}

/**
* 需重写JdbcTemplate的getDataSource()方法,使之获取实际的数据源,否则在启动事务时不能切换数据源
*/
class MyJdbcTemplate extends JdbcTemplate {
public MyJdbcTemplate(DataSource dataSource) {
super(dataSource);
}

@Override
public DataSource getDataSource() {
DynamicDataSource dynamicDataSource = (DynamicDataSource) super.getDataSource();
DataSource targetDataSource = dynamicDataSource.getTargetDataSource();
return targetDataSource;
}
}
}


4.3、Mybatis配置

4.3.1、重写 SqlSessionTemplate

拷贝一份SqlSessionTemplate代码进行重写,主要需要重写getSqlSessionFactory、getConfiguration方法及内部类SqlSessionInterceptor。


@Override
public SqlSessionFactory getSqlSessionFactory() {
String key = DynamicContextHolder.peek();
if (key == null) {
return defaultSqlSessionFactory;
}
return sqlSessionFactorys.get(key);
}

@Override
public org.apache.ibatis.session.Configuration getConfiguration() {
return this.getSqlSessionFactory().getConfiguration();
}

private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory)) {
// 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 (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
sqlSession = null;
Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
}
}
}
}


完整的代码如下:

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring_02Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_ide_03


package com.abc.demo.config;

import com.abc.demo.datasource.DynamicContextHolder;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.*;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.dao.support.PersistenceExceptionTranslator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.*;
import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;

/**
* 重写SqlSessionTemplate
*/
public class DynamicSqlSessionTemplate extends SqlSessionTemplate {
private SqlSessionFactory sqlSessionFactory;
private ExecutorType executorType;
private SqlSession sqlSessionProxy;
private PersistenceExceptionTranslator exceptionTranslator;

private SqlSessionFactory defaultSqlSessionFactory = null;
private Map<String, SqlSessionFactory> sqlSessionFactorys = null;

public DynamicSqlSessionTemplate(SqlSessionFactory defaultSqlSessionFactory, Map<String, SqlSessionFactory> sqlSessionFactorys) {
this(defaultSqlSessionFactory);

this.defaultSqlSessionFactory = defaultSqlSessionFactory;
this.sqlSessionFactorys = sqlSessionFactorys;
}

public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public DynamicSqlSessionTemplate(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());
}

@Override
public SqlSessionFactory getSqlSessionFactory() {
String key = DynamicContextHolder.peek();
if (key == null) {
return defaultSqlSessionFactory;
}
return sqlSessionFactorys.get(key);
}

@Override
public org.apache.ibatis.session.Configuration getConfiguration() {
return this.getSqlSessionFactory().getConfiguration();
}

public ExecutorType getExecutorType() {
return this.executorType;
}

public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
return this.exceptionTranslator;
}

/**
* {@inheritDoc}
*/
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}

/**
* {@inheritDoc}
*/
@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}

/**
* {@inheritDoc}
*/
@Override
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.sqlSessionProxy.selectMap(statement, mapKey);
}

/**
* {@inheritDoc}
*/
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);
}

/**
* {@inheritDoc}
*/
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
}

/**
* {@inheritDoc}
*/
@Override
public <T> Cursor<T> selectCursor(String statement) {
return this.sqlSessionProxy.selectCursor(statement);
}

/**
* {@inheritDoc}
*/
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter) {
return this.sqlSessionProxy.selectCursor(statement, parameter);
}

/**
* {@inheritDoc}
*/
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
}

/**
* {@inheritDoc}
*/
@Override
public <E> List<E> selectList(String statement) {
return this.sqlSessionProxy.selectList(statement);
}

/**
* {@inheritDoc}
*/
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}

/**
* {@inheritDoc}
*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
}

/**
* {@inheritDoc}
*/
@Override
public void select(String statement, ResultHandler handler) {
this.sqlSessionProxy.select(statement, handler);
}

/**
* {@inheritDoc}
*/
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, handler);
}

/**
* {@inheritDoc}
*/
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
}

/**
* {@inheritDoc}
*/
@Override
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}

/**
* {@inheritDoc}
*/
@Override
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}

/**
* {@inheritDoc}
*/
@Override
public int update(String statement) {
return this.sqlSessionProxy.update(statement);
}

/**
* {@inheritDoc}
*/
@Override
public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
}

/**
* {@inheritDoc}
*/
@Override
public int delete(String statement) {
return this.sqlSessionProxy.delete(statement);
}

/**
* {@inheritDoc}
*/
@Override
public int delete(String statement, Object parameter) {
return this.sqlSessionProxy.delete(statement, parameter);
}

/**
* {@inheritDoc}
*/
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}

/**
* {@inheritDoc}
*/
@Override
public void commit() {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
}

/**
* {@inheritDoc}
*/
@Override
public void commit(boolean force) {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
}

/**
* {@inheritDoc}
*/
@Override
public void rollback() {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
}

/**
* {@inheritDoc}
*/
@Override
public void rollback(boolean force) {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
}

/**
* {@inheritDoc}
*/
@Override
public void close() {
throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
}

/**
* {@inheritDoc}
*/
@Override
public void clearCache() {
this.sqlSessionProxy.clearCache();
}


/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() {
return this.sqlSessionProxy.getConnection();
}

/**
* {@inheritDoc}
*
* @since 1.0.2
*
*/
@Override
public List<BatchResult> flushStatements() {
return this.sqlSessionProxy.flushStatements();
}

/**
* Allow gently dispose bean:
*
* <pre>
* {@code
*
* <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
* <constructor-arg index="0" ref="sqlSessionFactory" />
* </bean>
* }
* </pre>
*
* The implementation of {@link DisposableBean} forces spring context to use {@link DisposableBean#destroy()} method
* instead of {@link SqlSessionTemplate#close()} to shutdown gently.
*
* @see SqlSessionTemplate#close()
* @see "org.springframework.beans.factory.support.DisposableBeanAdapter#inferDestroyMethodIfNecessary(Object, RootBeanDefinition)"
* @see "org.springframework.beans.factory.support.DisposableBeanAdapter#CLOSE_METHOD_NAME"
*/
@Override
public void destroy() throws Exception {
// This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives
// UnsupportedOperationException
}

private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory)) {
// 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 (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
sqlSession = null;
Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
}
}
}
}
}

DynamicSqlSessionTemplate.java

4.3.2、Mybatis 配置


package com.abc.demo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@MapperScans(value = {@MapperScan(basePackages = {"com.abc.demo.dao"}, sqlSessionTemplateRef = "dynamicSqlSessionTemplate")})
public class MybatisConfig {
@Qualifier("dataSource1")
@Autowired
private DataSource dataSource1;

@Qualifier("dataSource2")
@Autowired
private DataSource dataSource2;

@Bean("sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory1() {
try {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource1);
factoryBean.setConfigLocation(
new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/abc/demo/dao/*.xml"));
return factoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

@Bean("sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactory2() {
try {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource2);
factoryBean.setConfigLocation(
new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/abc/demo/dao/*.xml"));
return factoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

@Bean("dynamicSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate() {
Map<String, SqlSessionFactory> sqlSessionFactorys = new HashMap<>();
//这边设置的key需与动态数据源里设置的key一致
sqlSessionFactorys.put("dataSource1", sqlSessionFactory1());
sqlSessionFactorys.put("dataSource2", sqlSessionFactory2());
return new DynamicSqlSessionTemplate(sqlSessionFactory1(), sqlSessionFactorys);
}
}


5、业务代码

5.1、实体类

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring_02Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_ide_03


package com.abc.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;


@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class School {
private String name;

private String location;
}

School.java

School实体类对应a_school表,放在第一个数据库里面。

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring_02Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_ide_03


package com.abc.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class Student {
private String name;

private Integer age;
}

Student.java

Student实体类对应a_student表,放在第一二个数据库里面。

5.2、DAO


package com.abc.demo.dao;

import com.abc.demo.datasource.DataSource;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;

import java.util.List;

public interface IBusinessDao {
@DataSource("dataSource1")
void insertSchool(School school);

@DataSource("dataSource1")
List<School> selectSchool();

@DataSource("dataSource2")
void insertStudent(Student student);

@DataSource("dataSource2")
List<Student> selectStudent();
}


5.1.1、Jdbc实现


package com.abc.demo.dao.impl;

import com.abc.demo.dao.IBusinessDao;
import com.abc.demo.datasource.DynamicDataSource;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;


@Repository("businessDaoImplJdbc")
public class BusinessDaoImplJdbc implements IBusinessDao {
private static Logger logger = LoggerFactory.getLogger(BusinessDaoImplJdbc.class);

@Qualifier("dynamicDataSource")
@Autowired
private DataSource dataSource;

private Connection getConnection() {
DynamicDataSource dynamicDataSource = (DynamicDataSource)dataSource;
return DataSourceUtils.getConnection(dynamicDataSource.getTargetDataSource());
}
private void releaseConnection(Connection con) {
DynamicDataSource dynamicDataSource = (DynamicDataSource)dataSource;
DataSourceUtils.releaseConnection(con, dynamicDataSource.getTargetDataSource());
}

@Override
public void insertSchool(School school) {
logger.info("insertSchool");
Connection con = getConnection();
PreparedStatement pst = null;
try {
pst = con.prepareStatement("insert into a_school(name,location) values(?,?)");
pst.setString(1, school.getName());
pst.setString(2, school.getLocation());

pst.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
releaseConnection(con);
}
}

@Override
public List<School> selectSchool() {
logger.info("selectSchool");
Connection con = getConnection();
Statement st = null;
ResultSet rs = null;
List<School> schools = new ArrayList<>();
try {
st = con.createStatement();
rs = st.executeQuery("select * from a_school");
while (rs.next()) {
School school = new School();
school.setName(rs.getString("name"));
school.setLocation(rs.getString("location"));
schools.add(school);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
releaseConnection(con);
}
return schools;
}

@Override
public void insertStudent(Student student) {
logger.info("insertStudent");
Connection con = getConnection();
PreparedStatement pst = null;
try {
pst = con.prepareStatement("insert into a_student(name,age) values(?,?)");
pst.setString(1, student.getName());
pst.setInt(2, student.getAge());
pst.addBatch();

pst.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
} finally {
releaseConnection(con);
}
}

@Override
public List<Student> selectStudent() {
logger.info("selectStudent");
Connection con = getConnection();
Statement st = null;
ResultSet rs = null;
List<Student> students = new ArrayList<>();
try {
st = con.createStatement();
rs = st.executeQuery("select * from a_student");
while (rs.next()) {
Student student = new Student();
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
students.add(student);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
releaseConnection(con);
}
return students;
}
}


5.1.2、JdbcTemplate实现


package com.abc.demo.dao.impl;

import com.abc.demo.dao.IBusinessDao;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository("businessDaoImplJdbcTemplate")
public class BusinessDaoImplJdbcTemplate implements IBusinessDao {
private static Logger logger = LoggerFactory.getLogger(BusinessDaoImplJdbcTemplate.class);

@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void insertSchool(School school) {
logger.info("insertSchool");
jdbcTemplate.update("insert into a_school(name,location) values(?,?)", school.getName(), school.getLocation());
}

@Override
public List<School> selectSchool() {
logger.info("selectSchool");
return jdbcTemplate.query("select * from a_school", new BeanPropertyRowMapper<>(School.class));
}

@Override
public void insertStudent(Student student) {
logger.info("insertStudent");
jdbcTemplate.update("insert into a_student(name,age) values(?,?)", student.getName(), student.getAge());
}

@Override
public List<Student> selectStudent() {
logger.info("selectStudent");
return jdbcTemplate.query("select * from a_student", new BeanPropertyRowMapper<>(Student.class));
}
}


5.1.3、Mybatis实现


<?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="com.abc.demo.dao.IBusinessDao" >
<insert id="insertSchool" parameterType="com.abc.demo.entity.School">
insert into a_school(name,location) values(#{name}, #{location})
</insert>

<select id="selectSchool" resultType="com.abc.demo.entity.School">
select name,location from a_school
</select>

<insert id="insertStudent" parameterType="com.abc.demo.entity.Student">
insert into a_student(name,age) values(#{name}, #{age})
</insert>

<select id="selectStudent" resultType="com.abc.demo.entity.Student">
select name,age from a_student
</select>
</mapper>


5.3、Service


package com.abc.demo.service;

import java.util.List;
import java.util.Map;

public interface IBusinessService {
void addSchoolAndStudent();

Map<String, List<Object>> querySchoolAndStudent();
}



package com.abc.demo.service.impl;

import com.abc.demo.dao.IBusinessDao;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;
import com.abc.demo.service.IBusinessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class BusinessServiceImpl implements IBusinessService {
private static Logger logger = LoggerFactory.getLogger(BusinessServiceImpl.class);

/**
* 可以切换不同的DAO实现,都可以实现同样的效果
*/
//@Qualifier("IBusinessDao") //Mybatis实现
//@Qualifier("businessDaoImplJdbc")
@Qualifier("businessDaoImplJdbcTemplate")
@Autowired
private IBusinessDao businessDao;

@Transactional
@Override
public void addSchoolAndStudent() {
businessDao.insertSchool(new School("南京大学", "南京"));
businessDao.insertSchool(new School("北京大学", "北京"));
businessDao.insertStudent(new Student("李白", 20));
businessDao.insertStudent(new Student("杜甫", 21));
}

@Override
public Map<String, List<Object>> querySchoolAndStudent() {
List<School> schools = businessDao.selectSchool();
List<Student> students = businessDao.selectStudent();
return new HashMap(){{
put("school", schools);
put("student", students);
}};
}
}


6、Controller

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring_02Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_ide_03


package com.abc.demo.controller;

import com.abc.demo.entity.R;
import com.abc.demo.service.IBusinessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/business")
public class BusinessController {
protected static Logger logger = LoggerFactory.getLogger(BusinessController.class);

@Autowired
private IBusinessService businessService;


@RequestMapping("addSchoolAndStudent")
public R addSchoolAndStudent() {
businessService.addSchoolAndStudent();
return R.ok();
}

@RequestMapping("querySchoolAndStudent")
public R querySchool() {
Map<String, List<Object>> map = businessService.querySchoolAndStudent();
return R.ok(map);
}

}

BusinessController.java

Controller用到的返回对象R:

Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_spring_02Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)_ide_03


package com.abc.demo.entity;

/**
* 返回数据
*/
public class R {
private static final long serialVersionUID = 1L;

/**
* 返回码
* 0 正常,其他异常
*/
private int returnCode = 0;

/**
* 描述
*/
private String description = "OK";

/**
* 结果数据
*/
private Object result;

public int getReturnCode() {
return returnCode;
}
public String getDescription() {
return description;
}
public Object getResult() {
return result;
}

public static R ok() {
return new R();
}

public static R ok(String description) {
R r = new R();
r.description = description;
return r;
}

public static R ok(Object result) {
R r = new R();
r.result = result;
return r;
}

public static R error() {
R r = new R();
r.returnCode = -1;
r.description = "未知异常,请联系管理员";
return r;
}

public static R error(int returnCode, String description) {
R r = new R();
r.returnCode = returnCode;
r.description = description;
return r;
}

}

R.java