美文网首页Javaorm
Spring Boot + Mybatis 中 配置Druid多

Spring Boot + Mybatis 中 配置Druid多

作者: singleZhang2010 | 来源:发表于2020-11-03 10:29 被阅读0次

    概述

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

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

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

    1. 首先是启动类的改造,在@SpringBootApplication注解后加上exclude = { DataSourceAutoConfiguration.class }
    @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
    
    1. 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
    //...后边省略和之前一样
    
    1. 创建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
    }
    
    
    1. 创建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
     **/
    @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;
        }
    }
    
    1. 接着配置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") //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@qq.com
     * @Date 2020/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();
            }
        }
    }
    
    1. 配置信息完成后,需要加入动态数据源支持,创建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();
        }
    
    1. 我们还需要创建一个自定义的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();}
    }
    
    1. 实现动态切换,通过注解来简化业务层的数据源切换,创建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;
    }
    
    1. 使用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)
    @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;
            }
        }
    }
    
    1. 完成以上的步骤后,可以在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
     **/
    @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@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
    1. 启动项目,打开http://localhost:8080/swagger-ui.html查看接口
      接口
      demo数据库,查询所有用户:/user/list
      demo
      demo2数据库,查询所有用户:/user/list2
      demo2

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

    总结

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

    项目地址

    https://gitee.com/kaixinshow/springboot-note

    返回【Spring Boot学习】目录

    相关文章

      网友评论

        本文标题:Spring Boot + Mybatis 中 配置Druid多

        本文链接:https://www.haomeiwen.com/subject/dpbmvktx.html