龙空技术网

SpringBoot 配置Druid多数据源并实现动态切换

singleZhang 245

前言:

此时兄弟们对“apache配置minetype”可能比较着重,兄弟们都需要学习一些“apache配置minetype”的相关知识。那么小编在网上收集了一些关于“apache配置minetype””的相关内容,希望朋友们能喜欢,小伙伴们快快来学习一下吧!

概述

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

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

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

实践如何配置Druid多数据源并实现动态切换首先是启动类的改造,在@SpringBootApplication注解后加上exclude = { DataSourceAutoConfiguration.class }

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
yml配置文件,druid配置中,加入两个数据库,分别命名为master、slave
# MyBatis配置mybatis:  # 搜索指定包别名  typeAliasesPackage: com.zhlab.demo.model  # 配置mapper的扫描,找到所有的mapper.xml映射文件  mapperLocations: classpath:mapper/*Mapper.xml  # 加载全局的配置文件  configLocation: classpath:mybatis/mybatis-config.xmlspring:  ## 数据库配置  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//...后边省略和之前一样
创建com.zhlab.demo.db包,并创建DataSourceType.java枚举类,存放所有数据源的名称
package com.zhlab.demo.db;/** * @ClassName DataSourceType * @Description //DataSourceType 数据源类型 * @Author singleZhang * @Email 405780096@qq.com * @Date 2020/11/2 0002 下午 2:41 **/public enum DataSourceType{    /**     * master     */    MASTER,    /**     * slave     */    SLAVE}
创建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@qq.com * @Date 2020/11/2 0002 下午 2:44 **/@Configurationpublic 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;    }}
接着配置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@qq.com * @Date 2020/10/31 0031 上午 9:37 **/@Configuration@MapperScan("com.zhlab.demo.mapper") //mapperpublic 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@qq.com * @Date 2020/11/2 0002 下午 2:08 **/@Configurationpublic 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();        }    }}
配置信息完成后,需要加入动态数据源支持,创建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@qq.com * @Date 2020/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();    }
我们还需要创建一个自定义的DynamicDataSourceHelper类,来操作数据源的设置、获取和清除
package com.zhlab.demo.db.datasource;/** * @ClassName DynamicDataSource * @Description //数据源切换处理 * @Author singleZhang * @Email 405780096@qq.com * @Date 2020/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();}}
实现动态切换,通过注解来简化业务层的数据源切换,创建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@Inheritedpublic @interface DataSource{    DataSourceType value() default DataSourceType.MASTER;}
使用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.springframework.stereotype.Component;import java.lang.reflect.Method;/** * @ClassName DynamicDataSourceAspect * @Description //数据源动态切换AOP * @Author singleZhang * @Email 405780096@qq.com * @Date 2020/11/2 0002 下午 3:16 **/@Aspect@Order(1)@Componentpublic 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;        }    }}
完成以上的步骤后,可以在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@qq.com * @Date 2020/10/31 0031 上午 9:45 **/@Servicepublic 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@qq.com * @Date 2020/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

demo

启动项目,打开查看接口

接口

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

demo

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

demo2

实现成功,完成动态切换数据源。

总结

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

项目地址

标签: #apache配置minetype