zoukankan      html  css  js  c++  java
  • springboot动态多数据源

     参考文章:https://www.cnblogs.com/hehehaha/p/6147096.html

    前言

    目标是springboot工程支持多个MySQL数据源,在代码层面上,同一个SQL(Mapper)可以在多个数据源灵活使用,也就是所说的动态。

    这种动态是通过LocalThread实现的,即一个web请求对应一个线程,在线程中指定一个数据源。

    1、maven pom

    pom.xml里有springboot的starter和数据库驱动,我这里用的是druid

    <?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.ivan.studio</groupId>
        <artifactId>fantacy</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <!-- 文件拷贝时的编码 -->
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <!-- 编译时的编码 -->
            <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
            <jdk.version>1.7</jdk.version>
            <hive.version>1.2.1</hive.version>
            <hadoop.version>2.7.2</hadoop.version>
            <shiro.version>1.4.0</shiro.version>
        </properties>
        <!-- 继承父包 -->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.4.3.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>1.4.3.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
                <version>1.4.3.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>1.4.3.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <version>1.4.3.RELEASE</version>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
                <version>1.4.3.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <version>1.4.3.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.1.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
                <version>1.4.3.RELEASE</version>
            </dependency>
            <!--<dependency>-->
                <!--<groupId>org.apache.tomcat</groupId>-->
                <!--<artifactId>tomcat-jdbc</artifactId>-->
            <!--</dependency>-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.20</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>4.3.8.RELEASE</version>
            </dependency>
        
            </dependency>
            <!-- druid阿里巴巴数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.15</version>
            </dependency>
        </dependencies>
        
    
    </project>

    2、配置文件

    application.properties配置文件里要有主数据源,多数据源,线程池等配置

    # 主数据源
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ivan?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username=root
    spring.datasource.password=123456
    
    
    # 更多数据源
    custom.datasource.names=ds1,ds2
    
    custom.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
    custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
    custom.datasource.ds1.url=jdbc:mysql://127.0.0.1:3306/vicky?useUnicode=true&characterEncoding=utf-8
    custom.datasource.ds1.username=root
    custom.datasource.ds1.password=123456
    
    custom.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource
    custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
    custom.datasource.ds2.url=jdbc:mysql://127.0.0.1:3306/ivan?useUnicode=true&characterEncoding=utf-8
    custom.datasource.ds2.username=root
    custom.datasource.ds2.password=123456
    
    #连接池的配置信息
    spring.datasource.initialSize=10
    spring.datasource.minIdle=10
    spring.datasource.maxActive=100
    spring.datasource.maxWait=60000
    spring.datasource.timeBetweenEvictionRunsMillis=60000
    spring.datasource.minEvictableIdleTimeMillis=300000
    spring.datasource.validationQuery=SELECT 1
    spring.datasource.testWhileIdle=true
    spring.datasource.testOnBorrow=false
    spring.datasource.testOnReturn=false
    spring.datasource.poolPreparedStatements=true
    spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
    spring.datasource.filters=stat,wall,log4j
    spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

    3、动态数据源路由类

           动态数据源能进行自动切换的核心就是spring底层提供了AbstractRoutingDataSource类进行数据源的路由的,我们主要继承这个类,实现里面的方法即可实现我们想要的,这里主要是实现方法:determineCurrentLookupKey(),而此方法只需要返回一个数据库的名称即可,所以我们核心的是有一个类来管理数据源的线程池,这个类才是动态数据源的核心处理类。还有另外就是我们使用aop技术在执行事务方法前进行数据源的切换。所以这里有几个需要编码的类,具体如下:

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author: ivan
     * @Description:
     * @Date: Created in 15:23 18/5/24
     * @Modified By:
     */
    
    public class DynamicDataSourceContextHolder {
    
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    
        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();
        }
    
        /**
         * 判断指定DataSrouce当前是否存在
         *
         * @param dataSourceId
         * @return
         * @author ivan
         * @create  2016年1月24日
         */
        public static boolean containsDataSource(String dataSourceId){
            return dataSourceIds.contains(dataSourceId);
        }
    }

    数据源路由类:

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 动态多数据源
     * @Author: ivan
     * @Description:
     * @Date: Created in 15:22 18/5/24
     * @Modified By:
     */
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    
    }

    指定数据源注解类>com.kfit.config.datasource.dynamic.TargetDataSource:

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @Author: ivan
     * @Description:
     * @Date: Created in 15:29 18/5/24
     * @Modified By:
     */
    
    
    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
        String name();
    }

    切换数据源Advice: 

    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.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author: ivan
     * @Description:
     * @Date: Created in 15:25 18/5/24
     * @Modified By:
     */
    
    
    /**
     * 切换数据源Advice
     *
     * @author ivan
     */
    @Aspect
    @Order(-10)// 保证该AOP在@Transactional之前执行
    @Component
    public class DynamicDataSourceAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    
        @Before("@annotation(ds)")
        public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {
            String dsId = ds.name();
            if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
                logger.error("ds=[{}]not exist,use main DataSource > {}", ds.name(), point.getSignature());
            } else {
                logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
                DynamicDataSourceContextHolder.setDataSourceType(ds.name());
            }
        }
    
        @After("@annotation(ds)")
        public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
            logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    
    }

    4、注册多数据源

    以上都是动态数据源在注入的时候使用的代码,其实很重要的一部分代码就是注册我们在application.properties配置的多数据源,这才是重点,这里我们使用

    ImportBeanDefinitionRegistrar进行注册,具体的代码如下:

    package com.ivan.studio.fantacy.common;
    
    /**
     * @Author: ivan
     * @Description:
     * @Date: Created in 15:27 18/5/24
     * @Modified By:
     */
    import com.alibaba.druid.pool.DruidDataSource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.PropertyValues;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
    import org.springframework.boot.bind.RelaxedDataBinder;
    import org.springframework.boot.bind.RelaxedPropertyResolver;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.convert.ConversionService;
    import org.springframework.core.convert.support.DefaultConversionService;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 动态数据源注册<br/>
     * 启动动态数据源请在启动类中(如SpringBootSampleApplication)
     * 添加 @Import(DynamicDataSourceRegister.class)
     *
     * @author ivan
     */
    public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
    
        private ConversionService conversionService = new DefaultConversionService();
        private PropertyValues dataSourcePropertyValues;
    
        // 如配置文件中未指定数据源类型,使用该默认值
        private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
        // private static final Object DATASOURCE_TYPE_DEFAULT =
        // "com.zaxxer.hikari.HikariDataSource";
    
        // 数据源
        private DataSource defaultDataSource;
        private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
            // 将主数据源添加到更多数据源中
            targetDataSources.put("dataSource", defaultDataSource);
            DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
            // 添加更多数据源
            targetDataSources.putAll(customDataSources);
            for (String key : customDataSources.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);
            registry.registerBeanDefinition("dataSource", beanDefinition);
    
            logger.info("Dynamic DataSource Registry");
        }
    
        /**
         * 创建DataSource
         * @return
         * @author ivan
         * @create 2016年1月24日
         */
        @SuppressWarnings("unchecked")
        public DataSource buildDataSource(Map<String, Object> dsMap) {
            try {
                Object type = dsMap.get("type");
                if (type == null) {
                    type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
                }
    
                Class<? extends DataSource> dataSourceType;
                dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
    
                String driverClassName = dsMap.get("driver-class-name").toString();
                String url = dsMap.get("url").toString();
                String username = dsMap.get("username").toString();
                String password = dsMap.get("password").toString();
    
                DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                        .username(username).password(password).type(dataSourceType);
                return factory.build();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public DataSource getDataSource(Map<String, Object> dsMap) {
    
            DruidDataSource datasource = new DruidDataSource();
            datasource.setUrl(dsMap.get("url").toString());
            datasource.setUsername(dsMap.get("username").toString());
            datasource.setPassword(dsMap.get("password").toString());
            datasource.setDriverClassName(dsMap.get("driver-class-name").toString());
    
            return datasource;
    
        }
    
        /**
         * 加载多数据源配置
         */
        @Override
        public void setEnvironment(Environment env) {
            initDefaultDataSource(env);
            initCustomDataSources(env);
        }
    
        /**
         * 初始化主数据源
         *
         * @author ivan
         */
        private void initDefaultDataSource(Environment env) {
            // 读取主数据源
            RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
            Map<String, Object> dsMap = new HashMap<String, Object>();
            dsMap.put("type", propertyResolver.getProperty("type"));
            dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
            dsMap.put("url", propertyResolver.getProperty("url"));
            dsMap.put("username", propertyResolver.getProperty("username"));
            dsMap.put("password", propertyResolver.getProperty("password"));
    
            defaultDataSource = getDataSource(dsMap);
    
            dataBinder(defaultDataSource, env);
        }
    
        /**
         * 为DataSource绑定更多数据
         *
         * @param dataSource
         * @param env
         * @author ivan
         */
        private void dataBinder(DataSource dataSource, Environment env){
            RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
            //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
            dataBinder.setConversionService(conversionService);
            dataBinder.setIgnoreNestedProperties(false);//false
            dataBinder.setIgnoreInvalidFields(false);//false
            dataBinder.setIgnoreUnknownFields(true);//true
            if(dataSourcePropertyValues == null){
                Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
                Map<String, Object> values = new HashMap<String, Object>(rpr);
                // 排除已经设置的属性
                values.remove("type");
                values.remove("driver-class-name");
                values.remove("url");
                values.remove("username");
                values.remove("password");
                dataSourcePropertyValues = new MutablePropertyValues(values);
            }
            dataBinder.bind(dataSourcePropertyValues);
        }
    
        /**
         * 初始化更多数据源
         *
         * @author ivan
         * @create 2016年1月24日
         */
        private void initCustomDataSources(Environment env) {
            // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
            RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
            String dsPrefixs = propertyResolver.getProperty("names");
            for (String dsPrefix : dsPrefixs.split(",")) {
                Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
                DataSource ds = getDataSource(dsMap);
                customDataSources.put(dsPrefix, ds);
                dataBinder(ds, env);
            }
        }
    
    }

    这里还有一个步骤很重要,由于我们是使用的ImportBeanDefinitionRegistrar的方式进行注册的,所以我们需要在App.java类中使用@Import进行注册,具体改造之后的App.java代码如下:

    import com.ivan.studio.fantacy.common.DynamicDataSourceRegister;
    import com.ivan.studio.fantacy.service.AutowiredService;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Import;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import static org.springframework.boot.SpringApplication.run;
    
    /**
     * @author ivan
     */
    @SpringBootApplication
    @Import({DynamicDataSourceRegister.class})
    public class WebApplication {
    
        private static volatile boolean IS_REGISTRY = false;
    
        public static void main(String[] args) {
           SpringApplication.run(App.class, args);
        }
    }
  • 相关阅读:
    流量分析 (WireShark)
    WEB小技俩
    PHP伪协议
    php弱类型基础
    宽字节注入
    布尔盲注
    时间盲注
    Odoo13教程-Odoo快捷键使用_江苏欧度软件
    Odoo,快速上手Odoo,来了解Odoo几个标准模块
    开源Odoo13更新的模块功能信息(译文)
  • 原文地址:https://www.cnblogs.com/kangoroo/p/10609055.html
Copyright © 2011-2022 走看看