1. 程式人生 > >springboot2.0動態多數據源切換

springboot2.0動態多數據源切換

mapper connect pass generic 1.0 null == runtime posit

摘要: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 如果在項目搭建中遇到問題可以在評論區留言,博主看到第一時間會給予回復,謝謝

springboot2.0動態多數據源切換