本文基于以下多位大神的成果进行调整,在此感谢。
参考文章:https://www.cnblogs.com/zhaojiatao/p/8407276.html
参考文章:https://blog.csdn.net/yy417168602/article/details/89015752
本项目采用Mybatis的XML方式,非注解方式
1.数据库配置文件src\main\resources\application.properties
spring.jmx.default-domain=项目名称
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
spring.http.multipart.maxFileSize=100Mb
spring.http.multipart.maxRequestSize=1000Mb
spring.datasource.type=com.alibaba.druid.pool.xa.DruidXADataSource
mybatis.config-locations=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/*.xml
mybatis.type-aliases-package=model
#mysql
sql1.datasource.name=数据库标识需唯一不能重复
sql1.datasource.url=jdbc:mysql://localhost:3306/数据库名称?allowMultiQueries=true&characterEncoding=utf-8
sql1.datasource.username=root
sql1.datasource.password=密码
sql1.datasource.driver-class-name=com.mysql.jdbc.Driver
sql1.datasource.test-on-borrow=false
sql1.datasource.test-while-idle=true
sql1.datasource.time-between-eviction-runs-millis=3600000
sql1.datasource.initialSize= 5
sql1.datasource.minIdle=5
sql1.datasource.maxActive=20
# 配置获取连接等待超时的时间
sql1.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
sql1.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
sql1.datasource.minEvictableIdleTimeMillis=30
sql1.datasource.validationQuery=SELECT 1
sql1.datasource.validationQueryTimeout=10000
sql1.datasource.testWhileIdle=true
sql1.datasource.testOnBorrow=false
sql1.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
sql1.datasource.poolPreparedStatements=true
sql1.datasource.maxPoolPreparedStatementPerConnectionSize= 20
#sql server
sql3.datasource.name=数据库标识需唯一不能重复
sql3.datasource.url=jdbc:sqlserver://127.0.0.1:1433;databaseName=数据库名称
sql3.datasource.username=sa
sql3.datasource.password=密码
sql3.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
sql3.datasource.test-on-borrow=false
sql3.datasource.test-while-idle=true
sql3.datasource.time-between-eviction-runs-millis=3600000
sql3.datasource.initialSize= 5
sql3.datasource.minIdle=5
sql3.datasource.maxActive=20
# 配置获取连接等待超时的时间
sql3.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
sql3.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
sql3.datasource.minEvictableIdleTimeMillis=30
sql3.datasource.validationQuery=SELECT 1
sql3.datasource.validationQueryTimeout=10000
sql3.datasource.testWhileIdle=true
sql3.datasource.testOnBorrow=false
sql3.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
sql3.datasource.poolPreparedStatements=true
sql3.datasource.maxPoolPreparedStatementPerConnectionSize= 20
#sql server
sql4.datasource.name=数据库标识需唯一不能重复
sql4.datasource.url=jdbc:sqlserver://127.0.0.1:1433;databaseName=数据库名称
sql4.datasource.username=sa
sql4.datasource.password=密码
sql4.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
sql4.datasource.test-on-borrow=false
sql4.datasource.test-while-idle=true
sql4.datasource.time-between-eviction-runs-millis=3600000
sql4.datasource.initialSize= 5
sql4.datasource.minIdle=5
sql4.datasource.maxActive=20
# 配置获取连接等待超时的时间
sql4.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
sql4.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
sql4.datasource.minEvictableIdleTimeMillis=30
sql4.datasource.validationQuery=SELECT 1
sql4.datasource.validationQueryTimeout=10000
sql4.datasource.testWhileIdle=true
sql4.datasource.testOnBorrow=false
sql4.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
sql4.datasource.poolPreparedStatements=true
sql4.datasource.maxPoolPreparedStatementPerConnectionSize= 20
2.SpringBoot启动项,
package Application;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement(proxyTargetClass = true)
@EnableAutoConfiguration
@EnableScheduling
@ComponentScan(basePackages={"controller","service","dao","baseUtils","common","model"})//包含配置信息的包一定要包含在内
public class StartApplication extends SpringBootServletInitializer {
@Autowired
private Environment env;
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(StartApplication.class);
}
public StartApplication(){
super();
setRegisterErrorPageFilter(false);
}
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
3.多数据源配置文件:
AbstractDataSourceConfig
package baseUtils;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.microsoft.sqlserver.jdbc.SQLServerXADataSource;
import org.springframework.core.env.Environment;
import javax.sql.DataSource;
import java.util.Properties;
public abstract class AbstractDataSourceConfig {
protected DataSource getDataSource(Environment env,String dataSourceName) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = new Properties();
String prefix=dataSourceName+".datasource.";
if(env.getProperty(prefix + "driver-class-name").equals("com.microsoft.sqlserver.jdbc.SQLServerDriver")){
SQLServerXADataSource datasource = new SQLServerXADataSource();
datasource.setURL(env.getProperty(prefix + "url"));
datasource.setUser(env.getProperty(prefix + "username"));
datasource.setPassword(env.getProperty(prefix + "password"));
ds.setXaDataSource(datasource);
}else {
prop = build(env, prefix);
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setPoolSize(5);
ds.setXaProperties(prop);
}
ds.setUniqueResourceName("sql1");
return ds;
}
private Properties build(Environment env, String prefix) {
Properties prop = new Properties();
prop.put("url", env.getProperty(prefix + "url"));
prop.put("username", env.getProperty(prefix + "username"));
prop.put("password", env.getProperty(prefix + "password"));
prop.put("driverClassName", env.getProperty(prefix + "driver-class-name", ""));
prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
prop.put("maxPoolPreparedStatementPerConnectionSize",
env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
prop.put("maxPoolPreparedStatementPerConnectionSize",
env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
prop.put("timeBetweenEvictionRunsMillis",
env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
return prop;
}
}
MybatisConfig
package baseUtils;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.microsoft.sqlserver.jdbc.SQLServerXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.*;
@Configuration
@Component
@MapperScan(basePackages = MybatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sql1SessionTemplate")
public class MybatisConfig extends AbstractDataSourceConfig {
@Autowired
private Environment env;
//mapper模式下的接口层
static final String BASE_PACKAGE = "dao";//mapper包
@Primary
@Bean(name = "sql1")
public DataSource dataSourceOne(Environment env) {
return getDataSource(env,"sql1"); //主数据库
}
@Bean(name = "sql1SessionFactory")
public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("sql1") DataSource dataSource)
throws Exception {
return createSqlSessionFactory(dataSource);
}
@Bean(name = "sql1SessionTemplate")
public CustomSqlSessionTemplate sql1SessionTemplate(@Qualifier("sql1SessionFactory") SqlSessionFactory factorySystem) throws Exception {
Map
CustomSqlSessionTemplate
package baseUtils;
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.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;
public class CustomSqlSessionTemplate extends SqlSessionTemplate {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
private Map
DataSourceContextHolder
package baseUtils;
import java.util.ArrayList;
import java.util.List;
//数据库上下文切换
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
public static List dataSourceKeys = new ArrayList<>();
/**
* 设置当前数据库。
*
* @param dbType
*/
public static void setDatasourceType(String dbType) {
contextHolder.set(dbType);
}
public static void setDefaultDataSource() {
}
/**
* 取得当前数据源。
*
* @return
*/
public static String getDatasourceType() {
String str = contextHolder.get();
return str;
}
/**
* 清除上下文数据
*/
public static void clearDatasourceType() {
contextHolder.remove();
}
/**
* @param dataSourceId
* @return
*/
public static boolean containsDataSource(String dataSourceId) {
return dataSourceKeys.contains(dataSourceId);
}
}
DataSourceManager
package baseUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Component
public class DataSourceManager {
@Autowired
private CustomSqlSessionTemplate sqlSessionTemplate;
@Autowired
private MybatisConfig mybatisConfig;
/**
* 初始或者重载入数据源
*
* @throws Exception
*/
@PostConstruct
public void loadDataSource() throws Exception {
String[] dbArr={"sql3","sql4"};//不包括主数据库
Map newSqlSessionFactoryMap = new HashMap<>();
Map sqlSessionFactoryMap = sqlSessionTemplate.getTargetSqlSessionFactorys();
for(int i=0;i
4.AOP切换数据源
DataSourceAspect
package baseUtils;
import baseUtils.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import service.BaiseService;
import service.OAService;
@Aspect
@Component
public class DataSourceAspect {
/**
* 使用空方法定义切点表达式
*/
@Pointcut("execution(* service.*.*(..))") //配置包
public void declareJointPointExpression() {
}
/**
* 使用定义切点表达式的方法进行切点表达式的引入
*/
@Before("declareJointPointExpression()")
public void setDataSourceKey(JoinPoint point) {
if (point.getTarget() instanceof 指定Service1) {
DataSourceContextHolder.setDatasourceType("sql4");
}else if (point.getTarget() instanceof Service2) {
DataSourceContextHolder.setDatasourceType("sql3");
}else {
DataSourceContextHolder.setDatasourceType("sql1"); //其他采用默认数据库
}
}
}
5.事务中切换数据源,service代码片段
@Service("testService")
public class testService {
//测试事务中切换数据源,分布式事务
@Transactional(rollbackFor = Exception.class) //注意这一行,没有rollbackFor会报错
public int test(){
DataSourceContextHolder.setDatasourceType("sql1"); //切换数据源
adminMapper.AddAdmin(77,"test","test","test",198); //数据库1的插入操作
DataSourceContextHolder.setDatasourceType("sql3"); //切换数据源
baiseMapper.CRMlockUser(1,"test"); //数据库3的更新操作
System.out.println(1/0); //注释则成功执行,不注释测报错回滚
return 1;
}
}
6.使用SQLServer做为数据源时,会碰到一些问题,例如不支持SQLServer的提示,函数 RECOVER: 失败。状态为: -3等。请参考Atomikos XA分布式事务支持SQLServer的配置操作
7.如果批量更新报错:java.sql.SQLException: sql injection violation, multi-statement not allow
请去除关于filters的配置,采用默认配置。Mysql配置的url中请追加参数allowMultiQueries=true