# springboot-double-data
springboot-double-data

应用场景

项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库。

多数据源

首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。在@SpringBootApplication注解中添加exclude属性即可:

//一般你启动springboot项目,都会写一个有@SpringBootApplication注解的类
//你在这个注解中添加exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class}
//即可无数据库运行
//@SpringBootApplication//(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableSwagger2
@EnableDiscoveryClient
@ServletComponentScan
@ComponentScan("app")
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

然后在application.properties中配置多数据源连接信息:

#mysql1
spring.datasource.db1.url=jdbc:mysql://10.96.140.136:3306/activiti?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.db1.username=ipdata
spring.datasource.db1.password=open2013
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db1.max-idle=10
spring.datasource.db1.max-wait=10000
spring.datasource.db1.min-idle=5
spring.datasource.db1.initial-size=5

#mysql2
spring.datasource.db2.url=jdbc:mysql://10.96.140.136:3306/cmdb_resource_module?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.db2.username=ipdata
spring.datasource.db2.password=open2013
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db2.max-idle=10
spring.datasource.db2.max-wait=10000
spring.datasource.db2.min-idle=5
spring.datasource.db2.initial-size=5

由于我们禁掉了自动数据源配置,因些下一步就需要手动将这些数据源创建出来:
package app.configuration;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

/**
* 多数据源配置类
* Created by pure on 2018-05-06.
*/
@Configuration
public class DataSourceConfig {

//由于我们禁掉了自动数据源配置,因些下一步就需要手动将这些数据源创建出来:
//数据源1
@Bean(name = "datasource1")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.db1") // application.properteis中对应属性的前缀
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}

//数据源2
@Bean(name = "datasource2")
@ConfigurationProperties(prefix = "spring.datasource.db2") // application.properteis中对应属性的前缀
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}

}

接下来需要配置两个mybatis的SqlSessionFactory分别使用不同的数据源:
package app.configuration;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
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;

@Configuration
@MapperScan(basePackages = {"app.mapper.a"}, sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDbAConfig {
@Autowired
@Qualifier("datasource1")
private DataSource ds1;

@Bean
public SqlSessionFactory sqlSessionFactory1() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds1); // 使用ds1数据源, 连接ds1库
return factoryBean.getObject();
}

@Bean
public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); // 使用上面配置的Factory
return template;
}

}

经过上面的配置后,app.mapper.b下的Mapper接口,都会使用titan数据源。同理可配第二个SqlSessionFactory:
package app.configuration;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
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;

@Configuration
@MapperScan(basePackages = {"app.mapper.b"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDbBConfig {
@Autowired
@Qualifier("datasource2")
private DataSource ds2;

@Bean
public SqlSessionFactory sqlSessionFactory2() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds2); // 使用ds2数据源, 连接ds2库
return factoryBean.getObject();
}

@Bean
public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2()); // 使用上面配置的Factory
return template;
}

}

完成这些配置后,假设有2个Mapper app.mapper.a和app.mapper.b,使用前者时会自动连接ds1库,后者连接ds2库。

动态数据源

使用动态数据源的初衷,是能在应用层做到读写分离,即在程序代码中控制不同的查询方法去连接不同的库。除了这种方法以外,数据库中间件也是个不错的选择,它的优点是数据库集群对应用来说只暴露为单库,不需要切换数据源的代码逻辑。

我们通过自定义注解 + AOP的方式实现数据源动态切换。

首先定义一个DataSourceContextHolder, 用于保存当前线程使用的数据源名:
package app.configuration;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class DataSourceContextHolder {

private static Logger log = LogManager.getLogger(DataSourceContextHolder.class);
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "db1";

private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
System.out.println("切换到{}数据源"+ dbType);
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}

}

然后自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource即可:
package app.configuration;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource{

private static final Logger log = LogManager.getLogger(DynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
System.out.println("数据源为{}"+DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}

}

创建动态数据源:
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Bean(name = "datasource1")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap<>(2);
dsMap.put("datasource1", dataSource1());
dsMap.put("datasource2", dataSource2());

dynamicDataSource.setTargetDataSources(dsMap);

return dynamicDataSource;
}

自定义注释@DS用于在编码时指定方法使用哪个数据源:
package app.configuration;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义注释@DS用于在编码时指定方法使用哪个数据源:
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
String value() default "datasource1";
}

编写AOP切面,实现切换逻辑:
package app.configuration;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 自定义注解 + AOP的方式实现数据源动态切换。
* Created by pure on 2018-05-06.
*/
@Aspect
@Component
public class DynamicDataSourceAspect {

@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}

@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}

完成上述配置后,在先前SqlSessionFactory配置中指定使用DynamicDataSource就可以在Service中愉快的切换数据源了:
@DS("datasource2")
public List<PieEcharts> findVmPie() {
List<PieEcharts> findVmPie = echartsMapper.findVmPie();
return findVmPie;
}

springboot 双数据源+aop动态切换的更多相关文章

  1. 基于springboot通过注解AOP动态切换druid多数据源--mybatis

    控制于接口之上: 开始:demo地址  在lsr-core-base中 自定义注解: /** * @Description: 数据源切换注解 * @Package: lsr-microservice ...

  2. Springboot+Mybatis+Pagehelper+Aop动态配置Oracle、Mysql数据源

      本文链接:https://blog.csdn.net/wjy511295494/article/details/78825890 Springboot+Mybatis+Pagehelper+Aop ...

  3. Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离

    一.基础介绍 多数据源字面意思,比如说二个数据库,甚至不同类型的数据库.在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源. ...

  4. 【原】通过AOP实现MyBatis多数据源的动态切换

    [环境参数]1.开发框架:Spring + SpringMVC + MyBatis 2.数据库A的URL:jdbc.url=jdbc:mysql://172.16.17.164:3306/ test? ...

  5. Spring多数据源的动态切换

    Spring多数据源的动态切换 目前很多项目中只能配置单个数据源,那么如果有多个数据源肿么办?Spring提供了一个抽象类AbstractRoutingDataSource,为我们很方便的解决了这个问 ...

  6. Spring简单实现数据源的动态切换

    Spring简单实现数据源的动态切换: 1. 创建一个数据源切换类: 2. 继承AbstractRoutingDataSource,创建多数据源路由类,并注入到spring的配置文件中: 3. AOP ...

  7. Spring AOP动态切换数据源

    现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以 ...

  8. SpringBoot多数据源:动态数据源

    目录 1. 引言 2. 动态数据源流程说明 3. 实现动态数据源 3.1 说明及数据源配置 3.1.1 包结构说明 3.1.2 数据库连接信息配置 3.1.3 数据源配置 3.2 动态数据源设置 3. ...

  9. Spring配置多个数据源,并实现数据源的动态切换转载)

    1.首先在config.properties文件中配置两个数据库连接的基本数据.这个省略了 2.在spring配置文件中配置这两个数据源: 数据源1 <!-- initialSize初始化时建立 ...

随机推荐

  1. c/c++ 标准库 set 自定义关键字类型与比较函数

    标准库 set 自定义关键字类型与比较函数 问题:哪些类型可以作为标准库set的关键字类型呢??? 答案: 1,任意类型,但是需要额外提供能够比较这种类型的比较函数. 2,这种类型实现了 < 操 ...

  2. 编译.py为.pyc

    将test.py编译为.pyc文件,然后直接使用.pyc即可,防止源码外泄 import py_compile py_compile.compile(r'c:/test.py')compileall. ...

  3. Linux中FTP的一点理解

    FTP(File Transfer Protocol)是一个非常古老并且应用十分广泛的文件传输协议,FTP协议是现今使用最为广泛的网络文件共享协议之一,我们现在也一直有在用着FTP协议来进行各种文件的 ...

  4. 本学期c#总结

     本学期我学习了C#编程认识到了什么是方法什么是关键字,和代码的作用.认识到了编程不仅仅是对一个程序的编程,也是对数据的编程.程序是由数据组成的.c#本身就是一种语言,C#适用于生成面向.net fr ...

  5. VS2013 创建ASP.NET MVC 4.0 未指定的错误(异常来自HRESULT: 0x80004005(e_fail))

    这个错误是真的头疼,尝试各种办法都没用,最后解决用的方法是: 找到 vs_ultimate.exe 修复文件,我的文件位置在 C:\ProgramData\Package Cache\{4d78654 ...

  6. linux学习笔记整理(三)

    第四章 文件的基本管理和XFS文件系统备份恢复本节所讲内容:4.1 Linux系统目录结构和相对/绝对路径.4.2 创建/复制/删除文件,rm -rf / 意外事故4.3 查看文件内容的命令4.4 实 ...

  7. postgresql + mybatis insert主键自增方法

    postgresql + mybatis插入记录时设置自增主键方法: 一.数据库设置主键自增 1.数据库中id字段选择serial4类型后,会在默认值中生成 nextval('app_id_seq': ...

  8. 搭建golang学习环境,并用chrome headless获取网页内容

    想用go练练手(我是win7系统,已从https://studygolang.com/dl 下载了go安装包并安装,比较简单,不详述. 但作为边民,没法go get ,又不敢用梯子,幸亏有爱心大牛们的 ...

  9. BZOJ3110:[ZJOI2013]K大数查询(整体二分)

    Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位 ...

  10. Leetcode:0027

    Leetcode:0027 题目:给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度.不要使用额外的数组空间,你必须在原地修改输入数组并在使用 ...