SpringBoot系列博客目录,含1.5.X版本和2.X版本

springboot2.0正式版发布之后,很多的组件集成需要变更了,这次将多数据源的使用踩的坑给大家填一填。当前多数据源的主要为主从库,读写分离,动态切换数据源。使用的技术就是AOP进行dao方法的切面,所以大家的方法名开头都需要按照规范进行编写,如:get***add*** 等等,

起步基础

本次的教程需要有springboot2.0集成mybatis 作为基础:

需要以上的步骤作为基础,运行成功之后可就可以开始配置多数据源了

开始动手

添加依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

修改启动类

修改之前:

@SpringBootApplication
@MapperScan("com.winterchen.dao")
public class SpringBootMybatisMutilDatabaseApplication { public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisMutilDatabaseApplication.class, args);
}
}

修改之后:

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

因为改用多数据源,所以dao接口的扫描我们放在配置类中进行

修改项目配置

首先我们需要在配置文件中配置多数据源,看一下原本项目的配置:

spring:
datasource:
name: mysql_test
#-----------------start-----------------# (1)
type: com.alibaba.druid.pool.DruidDataSource
#-----------------end-----------------#
#druid相关配置
druid:
#监控统计拦截的filters
filters: stat
#-----------------start-----------------# (2)
driver-class-name: com.mysql.jdbc.Driver
#基本属性
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
username: root
password: root
#-----------------end-----------------#
#配置初始化大小/最小/最大
initial-size: 1
min-idle: 1
max-active: 20
#获取连接等待超时时间
max-wait: 60000
#间隔多久进行一次检测,检测需要关闭的空闲连接
time-between-eviction-runs-millis: 60000
#一个连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
validation-query: SELECT 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20

**需要修改的地方: **

  • (1) 需要将 type: com.alibaba.druid.pool.DruidDataSource去除;

  • (2) 将关于数据库的连接信息: driver-class-nameurlusernamepassword 去除;

修改后:

spring:
datasource:
name: mysql_test
#-------------- start ----------------# (1)
master:
#基本属性--注意,这里的为【jdbcurl】-- 默认使用HikariPool作为数据库连接池
jdbcurl: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
slave:
#基本属性--注意,这里为 【url】-- 使用 druid 作为数据库连接池
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
read: get,select,count,list,query,find
write: add,create,update,delete,remove,insert
#-------------- end ----------------#
#druid相关配置
druid:
#监控统计拦截的filters
filters: stat,wall
#配置初始化大小/最小/最大
initial-size: 1
min-idle: 1
max-active: 20
#获取连接等待超时时间
max-wait: 60000
#间隔多久进行一次检测,检测需要关闭的空闲连接
time-between-eviction-runs-millis: 60000
#一个连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
validation-query: SELECT 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20

需要修改地方:

  • (1) 在如上的配置中添加masterslave两个数据源;

注意!!两中数据源中有一处是不一样的,原因是因为master数据源使用 Hikari连接池,slave使用的是druid作为数据库连接池,所以两处的配置分别为:

master:
jdbcurl: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
slave:
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true

数据库的连接不一样的,如果配置成一样的会在启动的时候报错。

注意!!

dao接口方法的方法名规则配置在这里了,当然可以自行更改:

read: get,select,count,list,query,find
write: add,create,update,delete,remove,insert

创建配置包

首先在项目的/src/main/java/com/winterchen/包下创建config

创建数据源类型的枚举DatabaseType

该枚举类主要用来区分读写

package com.winterchen.config;

/**
* 列出数据源类型
* Created by Donghua.Chen on 2018/5/29.
*/
public enum DatabaseType { master("write"), slave("read"); DatabaseType(String name) {
this.name = name;
} private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "DatabaseType{" +
"name='" + name + '\'' +
'}';
}
}

创建线程安全的DatabaseType容器

多数据源必须要保证数据源的线程安全的

package com.winterchen.config;

/**
* 保存一个线程安全的DatabaseType容器
* Created by Donghua.Chen on 2018/5/29.
*/
public class DatabaseContextHolder { //用于存放多线程环境下的成员变量
private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>(); public static void setDatabaseType(DatabaseType type) {
contextHolder.set(type);
} public static DatabaseType getDatabaseType() {
return contextHolder.get();
}
}

创建动态数据源

实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,其实该相当于数据源DataSource的路由中介,可以实现在项目运行时根据相应key值切换到对应的数据源DataSource上,有兴趣的同学可以看看它的源码。

public class DynamicDataSource extends AbstractRoutingDataSource {

    static final Map<DatabaseType, List<String>> METHOD_TYPE_MAP = new HashMap<>();

    @Nullable
@Override
protected Object determineCurrentLookupKey() {
DatabaseType type = DatabaseContextHolder.getDatabaseType();
logger.info("====================dataSource ==========" + type);
return type;
} void setMethodType(DatabaseType type, String content) {
List<String> list = Arrays.asList(content.split(","));
METHOD_TYPE_MAP.put(type, list);
} }

创建数据源配置类DataSourceConfig

@Configuration
@MapperScan("com.winterchen.dao")
@EnableTransactionManagement
public class DataSourceConfig { private static Logger logger = LoggerFactory.getLogger(DataSourceConfig.class); @Autowired
private Environment env; // (1) @Autowired
private DataSourceProperties properties; // (2) @Value("${spring.datasource.druid.filters}") // (3)
private String filters; @Value("${spring.datasource.druid.initial-size}")
private Integer initialSize; @Value("${spring.datasource.druid.min-idle}")
private Integer minIdle; @Value("${spring.datasource.druid.max-active}")
private Integer maxActive; @Value("${spring.datasource.druid.max-wait}")
private Integer maxWait; @Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private Long timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private Long minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validation-query}")
private String validationQuery; @Value("${spring.datasource.druid.test-while-idle}")
private Boolean testWhileIdle; @Value("${spring.datasource.druid.test-on-borrow}")
private boolean testOnBorrow; @Value("${spring.datasource.druid.test-on-return}")
private boolean testOnReturn; @Value("${spring.datasource.druid.pool-prepared-statements}")
private boolean poolPreparedStatements; @Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
private Integer maxPoolPreparedStatementPerConnectionSize; /**
* 通过Spring JDBC 快速创建 DataSource
* @return
*/
@Bean(name = "masterDataSource")
@Qualifier("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master") // (4)
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
} /**
* 手动创建DruidDataSource,通过DataSourceProperties 读取配置
* @return
* @throws SQLException
*/
@Bean(name = "slaveDataSource")
@Qualifier("slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setFilters(filters);
dataSource.setUrl(properties.getUrl());
dataSource.setDriverClassName(properties.getDriverClassName());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
dataSource.setInitialSize(initialSize);
dataSource.setMinIdle(minIdle);
dataSource.setMaxActive(maxActive);
dataSource.setMaxWait(maxWait);
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setTestOnReturn(testOnReturn);
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
return dataSource;
} /**
* 构造多数据源连接池
* Master 数据源连接池采用 HikariDataSource
* Slave 数据源连接池采用 DruidDataSource
* @param master
* @param slave
* @return
*/
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseType.master, master);
targetDataSources.put(DatabaseType.slave, slave); DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
dataSource.setDefaultTargetDataSource(slave);// 默认的datasource设置为myTestDbDataSource String read = env.getProperty("spring.datasource.read");
dataSource.setMethodType(DatabaseType.slave, read); String write = env.getProperty("spring.datasource.write");
dataSource.setMethodType(DatabaseType.master, write); return dataSource;
} @Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource myTestDbDataSource,
@Qualifier("slaveDataSource") DataSource myTestDb2DataSource) throws Exception {
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(this.dataSource(myTestDbDataSource, myTestDb2DataSource));
fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapper-locations")));
return fb.getObject();
} @Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}

以上的代码中:

  • (1) 注入类 Environment 可以很方便的获取配置文件中的参数

  • (2) DataSourceProperties和(4)中的 @ConfigurationProperties(prefix = "spring.datasource.master")配合使用,将配置文件中的配置数据自动封装到实体类DataSourceProperties

  • (3) @Value注解同样是指定获取配置文件中的配置;

更详细的配置大家可以参考官方文档。

配置AOP

本章的开头已经说过,多数据源动态切换的原理是利用AOP切面进行动态的切换的,当调用dao接口方法时,根据接口方法的方法名开头进行区分读写。

/**
*
* 动态处理数据源,根据命名区分
* Created by Donghua.Chen on 2018/5/29.
*/
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect { private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); @Pointcut("execution(* com.winterchen.dao.*.*(..))")//切点
public void aspect() { } @Before("aspect()")
public void before(JoinPoint point) { //在指定切点的方法之前执行
String className = point.getTarget().getClass().getName();
String method = point.getSignature().getName();
String args = StringUtils.join(point.getArgs(), ",");
logger.info("className:{}, method:{}, args:{} ", className, method, args);
try {
for (DatabaseType type : DatabaseType.values()) {
List<String> values = DynamicDataSource.METHOD_TYPE_MAP.get(type);
for (String key : values) {
if (method.startsWith(key)) {
logger.info(">>{} 方法使用的数据源为:{}<<", method, key);
DatabaseContextHolder.setDatabaseType(type);
DatabaseType types = DatabaseContextHolder.getDatabaseType();
logger.info(">>{}方法使用的数据源为:{}<<", method, types);
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}

如上可以看到,切点切在dao的接口方法中,根据接口方法的方法名进行匹配数据源,然后将数据源set到用于存放数据源线程安全的容器中;

完整的项目结构了解一下:

项目启动

启动成功:

2018-05-30 17:27:16.492  INFO 35406 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'masterDataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=masterDataSource,type=HikariDataSource]
2018-05-30 17:27:16.496 INFO 35406 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'slaveDataSource': registering with JMX server as MBean [com.alibaba.druid.pool:name=slaveDataSource,type=DruidDataSource]
2018-05-30 17:27:16.498 INFO 35406 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'statFilter': registering with JMX server as MBean [com.alibaba.druid.filter.stat:name=statFilter,type=StatFilter]
2018-05-30 17:27:16.590 INFO 35406 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-05-30 17:27:16.598 INFO 35406 --- [ main] pringBootMybatisMutilDatabaseApplication : Started SpringBootMybatisMutilDatabaseApplication in 11.523 seconds (JVM running for 13.406)

添加用户(write):

日志:

2018-05-30 17:29:07.347  INFO 35406 --- [nio-8080-exec-1] com.winterchen.config.DataSourceAspect   : className:com.sun.proxy.$Proxy73, method:insert, args:com.winterchen.model.UserDomain@4b5b52dc
2018-05-30 17:29:07.350 INFO 35406 --- [nio-8080-exec-1] com.winterchen.config.DataSourceAspect : >>insert 方法使用的数据源为:insert<<
2018-05-30 17:29:07.351 INFO 35406 --- [nio-8080-exec-1] com.winterchen.config.DataSourceAspect : >>insert方法使用的数据源为:DatabaseType{name='write'}<<
2018-05-30 17:29:07.461 INFO 35406 --- [nio-8080-exec-1] com.winterchen.config.DynamicDataSource : ====================dataSource ==========DatabaseType{name='write'}
2018-05-30 17:29:07.462 INFO 35406 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2018-05-30 17:29:07.952 INFO 35406 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.

可以看出使用的就是write数据源,并且该数据源是使用HikariPool作为数据库连接池的

查询用户(read):

日志:

2018-05-30 17:29:41.616  INFO 35406 --- [nio-8080-exec-2] com.winterchen.config.DataSourceAspect   : className:com.sun.proxy.$Proxy73, method:selectUsers, args:
2018-05-30 17:29:41.618 INFO 35406 --- [nio-8080-exec-2] com.winterchen.config.DataSourceAspect : >>selectUsers 方法使用的数据源为:select<<
2018-05-30 17:29:41.618 INFO 35406 --- [nio-8080-exec-2] com.winterchen.config.DataSourceAspect : >>selectUsers方法使用的数据源为:DatabaseType{name='read'}<<
2018-05-30 17:29:41.693 INFO 35406 --- [nio-8080-exec-2] com.winterchen.config.DynamicDataSource : ====================dataSource ==========DatabaseType{name='read'}
2018-05-30 17:29:41.982 INFO 35406 --- [nio-8080-exec-2] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited

可以看出使用的是read数据源。

源码地址:戳这里

springboot mybatis 使用多数据源的更多相关文章

  1. SpringBoot+MyBatis配置多数据源

    SpringBoot 可以支持多数据源,这是一个非常值得学习的功能,但是从现在主流的微服务的架构模式中,每个应用都具有唯一且准确的功能,多数据源的需求很难用到,考虑到实际情况远远比理论复杂的多,这里还 ...

  2. springboot+mybatis集成多数据源MySQL/Oracle/SqlServer

    日常开发中可能时常会遇到一些这样的需求,业务数据库和第三方数据库,两个或多个数据库属于不同数据库厂商,这时候就需要通过配置来实现对数据库实现多源处理.大致说一下我的业务场景,框架本身是配置的sprin ...

  3. 记录一下自己搭建springboot+mybatis+druid 多数据源的过程

    前言  上次的一个项目(springboot+mybatis+vue),做到后面的时间发现需要用到多数据源.当时没有思路..后来直接用了jdbc来实现.这几天不是很忙,所以决定自己再搭建一次.不多说, ...

  4. springboot + mybatis配置多数据源示例

    转:http://www.jb51.net/article/107223.htm 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)Datab ...

  5. 基于注解的springboot+mybatis的多数据源组件的实现

    通常业务开发中,我们会使用到多个数据源,比如,部分数据存在mysql实例中,部分数据是在oracle数据库中,那这时候,项目基于springboot和mybatis,其实只需要配置两个数据源即可,只需 ...

  6. SpringBoot+mybatis实现多数据源支持

    什么是多数据源支持? 简单的说,就是一个项目里,同时可以访问多个不同的数据库. 实现原理 单个数据源在配置时会绑定一套mybatis配置,多个数据源时,不同的数据源绑定不同的mybatis配置就可以了 ...

  7. SpringBoot+Mybatis 实现动态数据源切换方案

    背景 最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据.考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态数据源的解决方案.在此分享给大家. 实现方案 数据库配置文 ...

  8. springboot mybatis plus多数据源轻松搞定 (上)

    在开发中经常会遇到一个程序需要调用多个数据库的情况,总得来说分为下面的几种情况: 一个程序会调用不同结构的两个数据库. 读写分离,两个数据结构可能一样高,但是不同的操作针对不同的数据库. 混合情况,既 ...

  9. SpringBoot MyBatis 配置多数据源 (静态多个)

    转载地址:https://www.jianshu.com/p/118ca1d5ecf9?utm_campaign=haruki&utm_content=note&utm_medium= ...

随机推荐

  1. Django自带表User认证详解

    认证登陆(附方法实现代码,百度网盘拉取即可下载,激活码:gqt1) 在进行用户登陆验证的时候,如果是自己写代码,就必须要先查询数据库,看用户输入的用户名是否存在于数据库中: 如果用户存在于数据库中,然 ...

  2. bzoj1801中国象棋

    题目链接 很裸的$dp+$组合计数 注意 注意 注意 $BZOJ$不要用玄学优化 $CE$不管$qwq$ /********************************************** ...

  3. 三,Smarty模板技术/引擎——变量操作(2)

    1, 变量的分类 ① 从PHP中分配的变量,比如a.php跳转到b.php时候,可以在a.php中分配变量,b.tpl中直接调用.a.php中代码,$smarty->assign(‘str’,’ ...

  4. Logstash 收集 IIS 日志

    日志样例 查看 IIS 日志配置,选择格式为 W3C(默认字段设置)保存生效. 2016-02-25 01:27:04 112.74.74.124 GET /goods/list/0/1.html - ...

  5. myeclipse关于svn更新报错:OPTIONS of '/svn/Xxx': 403 Forbidden

    这个问题出现原因是其他人修改了我原本写作的代码位置,把两个类转移到了别的文件夹,我更新之后只显示除了他增加的文件夹而没有里面的类,同时爆出错误: 问题原因:svn版本号不匹配,即跳版本. 解决如下:r ...

  6. macdown快速上手

    1.断句 在结尾处输入两个空格并使用回车. 2.标题分级 使用#来进行分级,#越多级数越低 3.链接 可以使用<>里面直接加上地址 或者使用[}里面加上链接名字然后后面接上()里面就是地址 ...

  7. Flink学习笔记:Flink Runtime

    本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKhaz ...

  8. 2018年10月19 手记 - 身为开发者的我de窘境

    从10月1国庆过完节回来,那已经是7号了,之后便开始紧锣密鼓的筹划着接下来11月份的公司组织的对外活动,这边新来的产品对产品或者说对任务很是负责,并且策划了很多的方案,并且乐意站在我们开发的角度上去考 ...

  9. restful api上传文件(基础)-springboot

    基于restful api格式的文件上传(只是上传到本地): package com.nxz.controller; import com.nxz.entity.FileInfo; import or ...

  10. 关于DES加密强制更新版(4.22)

    数据加密算法(Data Encryption Algorithm,DEA)是一种对称加密算法,很可能是使用最广泛的密钥系统,特别是在保护金融数据的安全中,最初开发的DEA是嵌入硬件中的.通常,自动取款 ...