# 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. LeetCode算法题-Implement Stack Using Queues

    这是悦乐书的第193次更新,第198篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第54题(顺位题号是225).使用队列实现栈的以下操作: push(x) - 将元素x推 ...

  2. 【算法】LeetCode算法题-Implement strStr

    这是悦乐书的第151次更新,第153篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第10题(顺位题号是28).给定两个任意字符串haystack.needle,返回hay ...

  3. Glyphicons 字体图标

  4. C#基础知识之Partial class

    C# 2.0 可以将类.结构或接口的定义拆分到两个或多个源文件中,在类声明前添加partial关键字即可. 例如:下面的PartialTest类 class PartialTest { string ...

  5. 冒泡排序 最好O(n)平均O(n^2) 选择排序O(n2) 插入排序O(n2)

    LOWB 三人组 分清有序区跟无序区 冒泡排序 思路: 首先,列表每两个相邻的数,如果前边的比后边的大,那么交换和两个数.... 冒泡排序优化 如果一趟没有发生任何交换 那么证明列表已经是有序的了 i ...

  6. 因PHP漏洞,超过4.5万个中国网站被攻击

    因PHP漏洞,超过4.5万个中国网站被攻击 日前据外媒ZDNet了解,超过4.5万个中国网站受到了试图进入网络服务器的不法分子攻击. 此次黑客利用中国网络安全公司VulnSpy在ExploitDB上发 ...

  7. BeautifulSoup类

    from bs4 import BeautifulSoup soup1 = BeautifulSoup("<html>data</html>"," ...

  8. 自然语言处理之jieba分词

    在处理英文文本时,由于英文文本天生自带分词效果,可以直接通过词之间的空格来分词(但是有些人名.地名等需要考虑作为一个整体,比如New York).而对于中文还有其他类似形式的语言,我们需要根据来特殊处 ...

  9. Codechef CNTL Counting is life 生成函数

    传送门--Vjudge 第一问很氵,如果\(K,N\)同奇偶就是\(2^K-1\),否则就是\(2^K-2\) 第二问似乎是可重排列,考虑指数型生成函数. 如何限制某些数必须要出现奇数/偶数次?考虑\ ...

  10. IDEA+Maven+Tomcat构建项目流程

    0.准备 本文主要解决在IDEA上开发Maven-webapp项目关联Tomcat的问题. 首先,确保本地计算机下载解压了Tomcat压缩包,以及配置好了Java环境. 1.新建Mavne项目 2.I ...