• springboot2.0动态多数据源切换


    摘要:springboot1.x到springboot2.0配置变化有一点变化,网上关于springboot2.0配置多数据源的资料也比较少,为了让大家配置多数据源从springboot1.x升级到springboot2.0少踩写坑,博主在此介绍用springboot2.0来进行动态数据源切换。(在博客的最后会给大家提供源码的下载地址)

    一、引入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.heshouyou</groupId>
        <artifactId>dynamic-datasource</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>dynamic-datasource</name>
        <description>dynamic-datasource project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.0.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
    
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>

    二、编辑application.yml

    server.port=8080
    #数据源配置(默认)
    #useSSL=false MySQL在高版本需要指明是否进行SSL连接(不加第一次连接数据库会有警告信息)
    spring.datasource.driver=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false
    spring.datasource.username=root
    spring.datasource.password=123456
    #ds1,ds2 其他两个数据源
    slave.datasource.names=ds1,ds2
    #ds1
    slave.datasource.ds1.driver=com.mysql.jdbc.Driver
    slave.datasource.ds1.url=jdbc:mysql://localhost:3306/test1?useSSL=false
    slave.datasource.ds1.username=root
    slave.datasource.ds1.password=123456
    #ds2
    slave.datasource.ds2.driver=com.mysql.jdbc.Driver
    slave.datasource.ds2.url=jdbc:mysql://localhost:3306/test2?useSSL=false
    slave.datasource.ds2.username=root
    slave.datasource.ds2.password=123456
    #mapper.xml文件
    mybatis.mapper-locations=classpath:mapper/*.xml
    #实体类包
    mybatis.type-aliases-package=datasource.springboot_datasource.entity

    三、DataSource工具类

    动态切换类

    package com.chaoqi.springboot_datasource.config;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * @Author caiChaoqi
     * @Date 2018-06-23
     * @Description 动态数据源
     * AbstractRoutingDataSource(每执行一次数据库,动态获取DataSource)
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    }
    动态数据源上下文管理
    package com.chaoqi.springboot_datasource.config;
    
    import org.apache.log4j.Logger;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author caiChaoqi
     * @Date 2018-06-23
     * @Description 动态数据源上下文管理
     */
    public class DynamicDataSourceContextHolder {
    
        private Logger logger = Logger.getLogger(DynamicDataSourceContextHolder.class);
        //存放当前线程使用的数据源类型信息
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        //存放数据源id
        public static List<String> dataSourceIds = new ArrayList<String>();
    
        //设置数据源
        public static void setDataSourceType(String dataSourceType) {
            contextHolder.set(dataSourceType);
        }
    
        //获取数据源
        public static String getDataSourceType() {
            return contextHolder.get();
        }
    
        //清除数据源
        public static void clearDataSourceType() {
            contextHolder.remove();
        }
    
        //判断当前数据源是否存在
        public static boolean isContainsDataSource(String dataSourceId) {
            return dataSourceIds.contains(dataSourceId);
        }
    }
    初始化数据源和提供了执行动态切换数据源的工具类
    package com.chaoqi.springboot_datasource.config;
    
    import org.apache.log4j.Logger;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author caiChaoqi
     * @Date 2018-06-23
     * @Description 注册动态数据源
     * 初始化数据源和提供了执行动态切换数据源的工具类
     * EnvironmentAware(获取配置文件配置的属性值)
     */
    
    public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    
        private Logger logger = Logger.getLogger(DynamicDataSourceRegister.class);
    
        //指定默认数据源(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置)
        private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
        //默认数据源
        private DataSource defaultDataSource;
        //用户自定义数据源
        private Map<String, DataSource> slaveDataSources = new HashMap<>();
    
        @Override
        public void setEnvironment(Environment environment) {
            initDefaultDataSource(environment);
            initslaveDataSources(environment);
        }
    
        private void initDefaultDataSource(Environment env) {
            // 读取主数据源
            Map<String, Object> dsMap = new HashMap<>();
            dsMap.put("driver", env.getProperty("spring.datasource.driver"));
            dsMap.put("url", env.getProperty("spring.datasource.url"));
            dsMap.put("username", env.getProperty("spring.datasource.username"));
            dsMap.put("password", env.getProperty("spring.datasource.password"));
            defaultDataSource = buildDataSource(dsMap);
        }
    
    
        private void initslaveDataSources(Environment env) {
            // 读取配置文件获取更多数据源
            String dsPrefixs = env.getProperty("slave.datasource.names");
            for (String dsPrefix : dsPrefixs.split(",")) {
                // 多个数据源
                Map<String, Object> dsMap = new HashMap<>();
                dsMap.put("driver", env.getProperty("slave.datasource." + dsPrefix + ".driver"));
                dsMap.put("url", env.getProperty("slave.datasource." + dsPrefix + ".url"));
                dsMap.put("username", env.getProperty("slave.datasource." + dsPrefix + ".username"));
                dsMap.put("password", env.getProperty("slave.datasource." + dsPrefix + ".password"));
                DataSource ds = buildDataSource(dsMap);
                slaveDataSources.put(dsPrefix, ds);
            }
        }
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
            //添加默认数据源
            targetDataSources.put("dataSource", this.defaultDataSource);
            DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
            //添加其他数据源
            targetDataSources.putAll(slaveDataSources);
            for (String key : slaveDataSources.keySet()) {
                DynamicDataSourceContextHolder.dataSourceIds.add(key);
            }
    
            //创建DynamicDataSource
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DynamicDataSource.class);
            beanDefinition.setSynthetic(true);
            MutablePropertyValues mpv = beanDefinition.getPropertyValues();
            mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
            mpv.addPropertyValue("targetDataSources", targetDataSources);
            //注册 - BeanDefinitionRegistry
            beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
    
            logger.info("Dynamic DataSource Registry");
        }
    
        public DataSource buildDataSource(Map<String, Object> dataSourceMap) {
            try {
                Object type = dataSourceMap.get("type");
                if (type == null) {
                    type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
                }
                Class<? extends DataSource> dataSourceType;
                dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
                String driverClassName = dataSourceMap.get("driver").toString();
                String url = dataSourceMap.get("url").toString();
                String username = dataSourceMap.get("username").toString();
                String password = dataSourceMap.get("password").toString();
                // 自定义DataSource配置
                DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                        .username(username).password(password).type(dataSourceType);
                return factory.build();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    自定义注解

    package com.chaoqi.springboot_datasource.config;
    
    import java.lang.annotation.*;
    
    /**
     * @Author caiChaoqi
     * @Date 2018-06-23
     * @Description 作用于类、接口或者方法上
     */
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
    
        String name();
    }
    动态数据源通知
    package com.chaoqi.springboot_datasource.config;
    
    import org.apache.log4j.Logger;
    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.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author caiChaoqi
     * @Description 动态数据源通知
     * @Date 2018-06-23
     */
    @Aspect
    @Order(-1)//保证在@Transactional之前执行
    @Component
    public class DynamicDattaSourceAspect {
    
        private Logger logger = Logger.getLogger(DynamicDattaSourceAspect.class);
    
        //改变数据源
        @Before("@annotation(targetDataSource)")
        public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
            String dbid = targetDataSource.name();
    
            if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) {
                //joinPoint.getSignature() :获取连接点的方法签名对象
                logger.error("数据源 " + dbid + " 不存在使用默认的数据源 -> " + joinPoint.getSignature());
            } else {
                logger.debug("使用数据源:" + dbid);
                DynamicDataSourceContextHolder.setDataSourceType(dbid);
            }
        }
    
        @After("@annotation(targetDataSource)")
        public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
            logger.debug("清除数据源 " + targetDataSource.name() + " !");
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    核心代码以全部展示,源码下载地址:https://github.com/caicahoqi/ChaoqiIsPrivateLibrary 如果在项目搭建中遇到问题可以在评论区留言,博主看到第一时间会给予回复,谢谢

  • 相关阅读:
    使用PHP类库PHPqrCode生成二维码
    40+个对初学者非常有用的PHP技巧
    (高级篇)jQuery学习之jQuery Ajax用法详解
    lerna管理前端模块实践
    Node.js:深入浅出 http 与 stream
    从koa-session源码解读session本质
    Elastichsearch实践
    Linux代理搭建TinyProxy
    linux常用命令
    node前后端同构的踩坑经历
  • 原文地址:https://www.cnblogs.com/caichaoqi/p/9216865.html
Copyright © 2020-2023  润新知