springboot mybatis多数据源的两种整合方法
阅读数:342 评论数:0
跳转到新版页面分类
python/Java
正文
一、概述
一个项目使用多个数据库(无论是主从复制--读写分离还是分布式数据库结构)的重要性变得越来越明显,整合的多数据源有两种方式:分包和aop。
SqlSessionTemplate是Mybatis—Spring的核心,是用来代替默认Mybatis实现的DefaultSqlSessionFactory,也可以说是DefaultSqlSessionFactory的优化版。SqlSessionTemplate是线程安全的。
(1)依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
2、DataSourceTransactionManager
对JDBC事务进行管理,事务可以参见 :springboot事务详解
SqlSession等可以参考:mybatis调用链路
二、分包
以分包的方式来区分不同的数据源,也就是不同的包,连接不同的数据库。
##数据源1
##driverClassName driver-class-name
spring.datasource.test1.driver-class-name: com.mysql.jdbc.Driver
spring.datasource.test1.jdbc-url: jdbc:mysql://127.0.0.1:3306/springboot?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.test1.username: root
spring.datasource.test1.password: root
#数据源2
spring.datasource.test2.driver-class-name: com.mysql.jdbc.Driver
spring.datasource.test2.jdbc-url: jdbc:mysql://127.0.0.1:3306/springboot1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.test2.username: root
spring.datasource.test2.password: root
DataSource1Config.java
@Configuration //注册到springboot 容器中
@MapperScan(basePackages = "com.jessDl.dataSource1",
sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config {
@Bean(name = "test1DataSource")
//数据源1的配置前缀
@ConfigurationProperties(prefix = "spring.datasource.test1")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
//由dataSource构建sqlSessionFactory
@Bean(name = "test1SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(
@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//加载其他文件,如mapper.xml
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath:mybatis/mapper/test1/*.xml"));
return bean.getObject();
}
//事务管理
@Bean(name = "test1TransactionManager")
public DataSourceTransactionManager testTransactionManager(
@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
这里我们要对mybatis sqlSession有一些认识和理解,mybatis进行持久化时有几个重要的类:
SqlSessionFactoryBuilder | build方法创建SqlSessionFactory实例。 |
SqlSessionFactory | 创建SqlSession实例的工厂。 |
SqlSession | 用于持久化操作的对象,类似于jdbc中的Connection。 |
SqlSessionTemplate | 持久化层访问模板化的工具,线程安全,可通过构造参数或依赖注入。 |
一层层的注入,先创建DataSource,再创建SqlSessionFactory,再创建事务,最后包装到SqlSessionTemplate中。其中需要制定分库的mapper文件地址,以及分库dao层
DataSource2Config.java
@Configuration //注册到springboot 容器中
@MapperScan(basePackages = "com.jessDl.dataSource2", sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class DataSource2Config {
@Bean(name = "test2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//加载其他文件,如mapper.xml
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
return bean.getObject();
}
//事务管理
@Bean(name = "test2TransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
dataSource1操作数据源1,dataSource2操作数据源2. dao和xml需要按照库来分在不同的目录。
三、AOP
实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,而AbstractRoutingDataSource又继承于AbstractDataSource,AbstractDataSource实现了统一的DataSource接口。
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
这里我们编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解。
配置的多个数据源会放在AbstractRoutingDataSource的targetDataSources和defaultTargetDataSource中。
AbstractRoutingDataSource的getConnection()的方法的时候,先调用determinTargetDataSource()方法返回DataSource在进行getConnection()
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
很简单就可以看到,我们通过自己实现的determineCurrentLookupKey()方法返回了lookupKey,根据配置的key就获取到对应的数据源达到切换的功能。
spring:
datasource:
druid:
db1:
url: jdbc:mysql://localhost:3306/eboot
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
db2:
url: jdbc:oracle:thin:@192.168.136.222:ORCL
username: sa
password: sa123456
driver-class-name: oracle.jdbc.OracleDriver
initialSize: 5
minIdle: 5
maxActive: 20
db3:
url: jdbc:oracle:thin:@192.168.136.223:ORCL
username: sb
password: sb123456
driver-class-name: oracle.jdbc.OracleDriver
initialSize: 5
minIdle: 5
maxActive: 20
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
@EnableTransactionManagement
@Configuration
public class DataSourceConfig {
private static final Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
@Bean(name="db1")
@ConfigurationProperties(prefix = "spring.datasource.druid.db1")
public DataSource db1(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name="db2")
@ConfigurationProperties(prefix = "spring.datasource.druid.db2")
public DataSource db2(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
@Qualifier("db2") DataSource db2){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object,Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.PG1.getName(),db1);
targetDataSources.put(DataSourceType.PG2.getName(),db2);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(db1);
return dynamicDataSource;
}
}
public enum DataSourceType {
PG1("pg1"),
PG2("pg2");
private String name;
DataSourceType(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
//此方法用于产生要选取的数据源逻辑名称
@Override
protected Object determineCurrentLookupKey(){
return BaseContextHandler.get(EvaConstants.DATA_SOURCE);
}
}
public class BaseContextHandler {
private final static Logger logger = LoggerFactory.getLogger(BaseContextHandler.class);
public static ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal<>();
public static void set(String key,Object value){
Map<String,Object> map = threadLocal.get();
if(map==null){
map = new HashMap<String,Object>();
threadLocal.set(map);
}
map.put(key,value);
}
public static Object get(String key){
Map<String,Object> map = threadLocal.get();
if(map==null){
map = new HashMap<String,Object>();
threadLocal.set(map);
}
return map.get(key);
}
public static void remove(){
threadLocal.remove();
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
DataSourceType value() default DataSourceType.PG1;
}
@Aspect
@Order(2)
@Component
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource){
DataSourceType dbType = targetDataSource.value();
if(dbType.equals(DataSourceType.PG1)){
BaseContextHandler.set(EvaConstants.DATA_SOURCE,DataSourceType.PG1.getName());
}else{
BaseContextHandler.set(EvaConstants.DATA_SOURCE,DataSourceType.PG2.getName());
}
}
@After("@annotation(targetDataSource)")
public void clearDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
BaseContextHandler.remove();
}
}
这样在dao层只需要这样
@TargetDataSource(value= DataSourceType.PG2)
int getSeq(@Param("seqName")String seqName);