700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > druid 多数据源_Spring Boot + Mybatis 中 配置Druid多数据源并实现自由切换

druid 多数据源_Spring Boot + Mybatis 中 配置Druid多数据源并实现自由切换

时间:2019-12-03 12:46:52

相关推荐

druid 多数据源_Spring Boot + Mybatis 中 配置Druid多数据源并实现自由切换

概述

前面我们已经介绍过了对MyBatis、Druid的整合,接下来我们在之前的基础上做扩展,实现对Druid多数据源的配置以及动态切换数据源。

问题:多数据源使用场景有哪些呢?

回答:在业务发展中,数据的不断增长,会有读写分离的需求,以及按业务模块分库的需求,这样我们的数据源会越来越多,在项目中就有了在各个数据源之间来回切换的场景。

实践如何配置Druid多数据源并实现动态切换

1. 首先是启动类的改造,在@SpringBootApplication注解后加上exclude = { DataSourceAutoConfiguration.class }

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })

2. yml配置文件,druid配置中,加入两个数据库,分别命名为master、slave

# MyBatis配置

mybatis:

# 搜索指定包别名

typeAliasesPackage: com.zhlab.demo.model

# 配置mapper的扫描,找到所有的mapper.xml映射文件

mapperLocations: classpath:mapper/*Mapper.xml

# 加载全局的配置文件

configLocation: classpath:mybatis/mybatis-config.xml

spring:

## 数据库配置

datasource:

type: com.alibaba.druid.pool.DruidDataSource

driver-class-name: com.mysql.cj.jdbc.Driver

druid:

master:

url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8

username: root

password: root

slave:

enabled: true

url: jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8

username: root

password: root

//...后边省略和之前一样

3. 创建com.zhlab.demo.db包,并创建DataSourceType.java枚举类,存放所有数据源的名称

package com.zhlab.demo.db;

/**

* @ClassName DataSourceType

* @Description //DataSourceType 数据源类型

* @Author singleZhang

* @Email 405780096@

* @Date /11/2 0002 下午 2:41

**/

public enum DataSourceType

{

/**

* master

*/

MASTER,

/**

* slave

*/

SLAVE

}

4. 创建com.zhlab.demo.config.properties包,并创建DruidProperties.java类,用来获取连接池属性,并设置到数据源中

package com.zhlab.demo.config.properties;

import com.alibaba.druid.pool.DruidDataSource;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Configuration;

/**

* @ClassName DruidProperties

* @Description //DruidProperties

* @Author singleZhang

* @Email 405780096@

* @Date /11/2 0002 下午 2:44

**/

@Configuration

public class DruidProperties {

@Value("${spring.datasource.druid.initialSize}")

private int initialSize;

@Value("${spring.datasource.druid.minIdle}")

private int minIdle;

@Value("${spring.datasource.druid.maxActive}")

private int maxActive;

@Value("${spring.datasource.druid.maxWait}")

private int maxWait;

@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")

private int timeBetweenEvictionRunsMillis;

@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")

private int minEvictableIdleTimeMillis;

@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")

private int maxEvictableIdleTimeMillis;

@Value("${spring.datasource.druid.validationQuery}")

private String validationQuery;

@Value("${spring.datasource.druid.testWhileIdle}")

private boolean testWhileIdle;

@Value("${spring.datasource.druid.testOnBorrow}")

private boolean testOnBorrow;

@Value("${spring.datasource.druid.testOnReturn}")

private boolean testOnReturn;

/**

* 连接池属性设置

* */

public DruidDataSource dataSource(DruidDataSource datasource)

{

datasource.setInitialSize(initialSize);

datasource.setMaxActive(maxActive);

datasource.setMinIdle(minIdle);

datasource.setMaxWait(maxWait);

datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);

datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);

datasource.setValidationQuery(validationQuery);

datasource.setTestWhileIdle(testWhileIdle);

datasource.setTestOnBorrow(testOnBorrow);

datasource.setTestOnReturn(testOnReturn);

return datasource;

}

}

5. 接着配置MybatisConfig.java和DruidConfig.java两个配置类

MybatisConfig

package com.zhlab.demo.config;

import org.apache.ibatis.io.VFS;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.annotation.MapperScan;

import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.env.Environment;

import org.springframework.core.io.DefaultResourceLoader;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**

* @ClassName MybatisConfig

* @Description //MybatisConfig配置类

* @Author singleZhang

* @Email 405780096@

* @Date /10/31 0031 上午 9:37

**/

@Configuration

@MapperScan("com.zhlab.demo.mapper") //mapper

public class MybatisConfig {

@Autowired

private Environment env;

@Bean

public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

String mapperLocations = env.getProperty("mybatis.mapperLocations");

String configLocation = env.getProperty("mybatis.configLocation");

VFS.addImplClass(SpringBootVFS.class);

final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

sessionFactory.setDataSource(dataSource);

sessionFactory.setTypeAliasesPackage("com.zhlab.demo.model");

sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));

sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));

return sessionFactory.getObject();

}

}

DruidConfig

package com.zhlab.demo.config;

import com.alibaba.druid.pool.DruidDataSource;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

import com.zhlab.demo.config.properties.DruidProperties;

import com.zhlab.demo.db.DataSourceType;

import com.zhlab.demo.db.datasource.DynamicDataSource;

import com.zhlab.demo.utils.SpringUtil;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

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 javax.sql.DataSource;

import java.util.HashMap;

import java.util.Map;

/**

* @ClassName DruidConfig

* @Description //DruidConfig配置类

* @Author singleZhang

* @Email 405780096@

* @Date /11/2 0002 下午 2:08

**/

@Configuration

public class DruidConfig {

/**

* master数据源

* */

@Bean

@ConfigurationProperties("spring.datasource.druid.master")

public DataSource masterDataSource(DruidProperties druidProperties)

{

DruidDataSource dataSource = DruidDataSourceBuilder.create().build();

return druidProperties.dataSource(dataSource);

}

/**

* slave数据源

* */

@Bean

@ConfigurationProperties("spring.datasource.druid.slave")

@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")

public DataSource slaveDataSource(DruidProperties druidProperties)

{

DruidDataSource dataSource = DruidDataSourceBuilder.create().build();

return druidProperties.dataSource(dataSource);

}

@Bean(name = "dynamicDataSource")

@Primary

public DynamicDataSource dataSource(DataSource masterDataSource) {

Map<Object, Object> targetDataSources = new HashMap<>();

targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);

//设置备用

setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");

return new DynamicDataSource(masterDataSource, targetDataSources);

}

/**

* 设置数据源

*

* @param targetDataSources 备选数据源集合

* @param sourceName 数据源名称

* @param beanName bean名称

*/

public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {

try {

DataSource dataSource = SpringUtil.getBean(beanName);

targetDataSources.put(sourceName, dataSource);

}

catch (Exception e) {

e.printStackTrace();

}

}

}

6. 配置信息完成后,需要加入动态数据源支持,创建com.zhlab.demo.db.datasource包,并创建

DynamicDataSource类,继承AbstractRoutingDataSource,这个抽象类有两个成员变量需要我们了解一下

targetDataSources:保存了key和数据库连接的映射关系

defaultTargetDataSource:表示默认的数据库连接

package com.zhlab.demo.db.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;

import java.util.Map;

/**

* @ClassName DynamicDataSource

* @Description //DynamicDataSource

* @Author singleZhang

* @Email 405780096@

* @Date /11/2 0002 下午 2:22

**/

public class DynamicDataSource extends AbstractRoutingDataSource

{

public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)

{

super.setDefaultTargetDataSource(defaultTargetDataSource);

super.setTargetDataSources(targetDataSources);

super.afterPropertiesSet();

}

@Override

protected Object determineCurrentLookupKey()

{

return DynamicDataSourceHelper.getDataSourceType();

}

}

上述类中,重写了determineCurrentLookupKey()函数,我们看一下它在抽象类中是如何被使用的

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

Object lookupKey = this.determineCurrentLookupKey();

DataSource 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 + "]");

} else {

return dataSource;

}

}

所以,我们需要在determineCurrentLookupKey()方法中返回一个数据源的标志即可,即

@Override

protected Object determineCurrentLookupKey()

{

return DynamicDataSourceHelper.getDataSourceType();

}

7. 我们还需要创建一个自定义的DynamicDataSourceHelper类,来操作数据源的设置、获取和清除

package com.zhlab.demo.db.datasource;

/**

* @ClassName DynamicDataSource

* @Description //数据源切换处理

* @Author singleZhang

* @Email 405780096@

* @Date /11/2 0002 下午 2:22

**/

public class DynamicDataSourceHelper

{

private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

/**

* 设置数据源的变量

*/

public static void setDataSourceType(String dsType) {CONTEXT_HOLDER.set(dsType); }

/**

* 获得数据源的变量

*/

public static String getDataSourceType() {return CONTEXT_HOLDER.get(); }

/**

* 清空数据源变量

*/

public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}

}

8. 实现动态切换,通过注解来简化业务层的数据源切换,创建com.zhlab.demo.db.annotation包,并创建注解DataSource.java

package com.zhlab.demo.db.annotation;

import com.zhlab.demo.db.DataSourceType;

import java.lang.annotation.*;

/**

* 自定义多数据源切换注解

* 在这里切换数据源名称

* */

@Target({ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited

public @interface DataSource

{

DataSourceType value() default DataSourceType.MASTER;

}

9. 使用AOP,切入DataSource注解,实现数据源切换,创建com.zhlab.demo.db.aspect包,并创建DynamicDataSourceAspect.java

package com.zhlab.demo.db.aspect;

import com.zhlab.demo.db.annotation.DataSource;

import com.zhlab.demo.db.datasource.DynamicDataSourceHelper;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.*;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.core.annotation.Order;

import org.ponent;

import java.lang.reflect.Method;

/**

* @ClassName DynamicDataSourceAspect

* @Description //数据源动态切换AOP

* @Author singleZhang

* @Email 405780096@

* @Date /11/2 0002 下午 3:16

**/

@Aspect

@Order(1)

@Component

public class DynamicDataSourceAspect {

/**

* 选择切入点为DataSource注解

* */

@Pointcut("@annotation(com.zhlab.demo.db.annotation.DataSource)"

+ "|| @within(com.zhlab.demo.db.annotation.DataSource)")

public void dsPointCut() {}

@Around("dsPointCut()")

public Object around(ProceedingJoinPoint point) throws Throwable {

DataSource dataSource = getDataSource(point);

if (dataSource != null) {

DynamicDataSourceHelper.setDataSourceType(dataSource.value().name());

}

try {

return point.proceed();

}

finally {

// 销毁数据源 在执行方法之后

DynamicDataSourceHelper.clearDataSourceType();

}

}

/**

* 获取需要切换的数据源

*/

public DataSource getDataSource(ProceedingJoinPoint point) {

MethodSignature signature = (MethodSignature) point.getSignature();

Class<? extends Object> targetClass = point.getTarget().getClass();

DataSource targetDataSource = targetClass.getAnnotation(DataSource.class);

if (targetDataSource != null) {

return targetDataSource;

} else {

Method method = signature.getMethod();

DataSource dataSource = method.getAnnotation(DataSource.class);

return dataSource;

}

}

}

10. 完成以上的步骤后,可以在DAO层、Service层的方法中切换数据源了

package com.zhlab.demo.mapper;

import com.zhlab.demo.db.DataSourceType;

import com.zhlab.demo.db.annotation.DataSource;

import com.zhlab.demo.model.SysAdminUser;

import java.util.List;

public interface SysAdminUserMapper {

int insert(SysAdminUser record);

/**

* 查询所有用户

* */

List<SysAdminUser> selectAll();

//切换数据源后,查询所有用户

@DataSource(value = DataSourceType.SLAVE)

List<SysAdminUser> selectAll2();

}

SysAdminUserService.java

package com.zhlab.demo.service;

import com.github.pagehelper.PageHelper;

import com.github.pagehelper.PageInfo;

import com.zhlab.demo.db.DataSourceType;

import com.zhlab.demo.db.annotation.DataSource;

import com.zhlab.demo.mapper.SysAdminUserMapper;

import com.zhlab.demo.model.SysAdminUser;

import com.zhlab.demo.utils.PageUtil;

import com.zhlab.demo.utils.page.PageRequest;

import com.zhlab.demo.utils.page.PageResult;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.List;

/**

* @ClassName SysAdminUserService

* @Description //SysAdminUserService

* @Author singleZhang

* @Email 405780096@

* @Date /10/31 0031 上午 9:45

**/

@Service

public class SysAdminUserService {

@Autowired

SysAdminUserMapper sysAdminUserMapper;

/**

* 查询所有用户

* */

public List<SysAdminUser> findAll(){

return sysAdminUserMapper.selectAll();

}

public List<SysAdminUser> findAll2(){

return sysAdminUserMapper.selectAll2();

}

}

UserController.java

package com.zhlab.demo.controller;

import com.github.pagehelper.PageHelper;

import com.github.pagehelper.PageInfo;

import com.zhlab.demo.model.SysAdminUser;

import com.zhlab.demo.service.SysAdminUserService;

import com.zhlab.demo.utils.PageUtil;

import com.zhlab.demo.utils.page.PageRequest;

import com.zhlab.demo.utils.page.PageResult;

import io.swagger.annotations.ApiOperation;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.web.bind.annotation.*;

import java.util.List;

/**

* @ClassName UserController

* @Description //用户接口层

* @Author singleZhang

* @Email 405780096@

* @Date /10/31 0031 上午 9:43

**/

@RestController

@RequestMapping("/user")

public class UserController {

@Autowired

SysAdminUserService sysAdminUserService;

/* 方法注解 */

@ApiOperation(value = "方法名:用户列表", notes = "获取用户列表")

@GetMapping("/list")

public List<SysAdminUser> list(){

List<SysAdminUser> list = sysAdminUserService.findAll();

return list;

}

/* 方法注解 */

@ApiOperation(value = "方法名:用户列表2", notes = "切换数据源获取用户列表")

@GetMapping("/list2")

public List<SysAdminUser> list2(){

List<SysAdminUser> list = sysAdminUserService.findAll2();

return list;

}

}

demo2数据库中的数据需要和demo数据库中的数据不同,形成对比

demo2 数据表内容:

demo 数据表内容:

10. 启动项目,打开http://localhost:8080/swagger-ui.html查看接口

demo数据库,查询所有用户:/user/list

demo2数据库,查询所有用户:/user/list2

实现成功,完成自定义切换数据源。

总结

Druid多数据源以及动态切换的使用场景其实在很多项目中是很常见的,需要大家掌握,以后接触到分布式系统的时候在这基础上会扩展得更多,需要持续深入研究。

项目地址

/kaixinshow/springboot-note

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。