SpringBoot动态配置多数据源-从数据库读取连接信息

一. 背景

        公司要求开发一个接口平台,作为我司各系统之间,或我司系统与第三方系统的对接工具。使用微服务架构,通过该接口平台可动态定义并生成restful接口。

二. 思考

       系统之间的对接,无外乎就是把己方系统的数据发送给第三方或接收第三方系统发送过来的数据(加密签名等机制本文不谈)。作为一个支持动态定义接口的平台,应该有自己的数据库设计,它不应该和业务系统的耦合度太高。因此,最终决定方案如下:

  1. 本平台独立运行且有自己独立的数据库,即不能把本平台的表建立在业务系统的库里面;
  2. 可登陆平台创建数据源信息(数据库连接信息),并给每个数据源分配一个唯一的code,支持Oracle、Mysql、SQLServer三种数据库类型;
  3. 数据源创建成功后,可以针对该数据源定义增删改查接口,分别对应restful的post、delete、put、get请求;
  4. 接口定义完成后,自动生成接口地址,平台接收到调用者的请求后,从请求URL(接口地址)解析出要操作的数据源code,然后基于Durid创建对应的数据库连接池(由于创建数据库连接池是比较费时的操作,因此仅当该数据源第一次被使用时才创建它的连接池,后期不会重复创建),连接池创建完成后再继续后续的操作。

三. 实现

        为了方便区分,我们把平台自身的数据源称为“主数据源”,动态创建的数据源称为“客数据源”。

        第1步:在application.properties配置主数据源信息

       spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
       spring.datasource.username=adi
       spring.datasource.password=adipassword
       spring.datasource.driverClassName = oracle.jdbc.OracleDriver

       spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
       spring.datasource.initialSize=20
       spring.datasource.minIdle=10
       spring.datasource.maxActive=50
       spring.datasource.maxWait=60000

      第2步:创建一个数据源实体类DataSource(类名可以改)

      该类的关键字段:

      url:数据库地址

      userName:数据库用户名

      passWord:数据库密码

      code:数据源编码,保证唯一

      databasetype:数据库类型,支持oracle、mysql、sqlserver2000、sqlserver

       第3步:创建动态数据源类DynamicDataSource(类名可以改)

      注意:这里的动态数据源不是“客数据源”,动态数据源类有一个“袋子”,用来装具体的数据源对象,也就是说可以装主数据源对象和各个客数据源对象。

     该类必须继承AbstractRoutingDataSource,其中的奥妙请参考

http://blog.csdn.net/rj042/article/details/21654627    (感谢这位博主!向您学习了不少!)

代码:

package com.bitservice.adi.datasource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.bitservice.adi.exception.ADIException;
import com.bitservice.adi.security.Base64;
import com.bitservice.adi.security.SecurityTools;
import com.bitservice.adi.util.ADIPropUtil;
import com.bitservice.adi.util.DBUtil;
import com.bitservice.adi.util.NullUtil;

public class DynamicDataSource extends AbstractRoutingDataSource {
    private boolean debug = false;
    private final Logger log = Logger.getLogger(getClass());
    private Map<Object, Object> dynamicTargetDataSources;
    private Object dynamicDefaultTargetDataSource;
    @Override
    protected Object determineCurrentLookupKey() {
        String datasource = DBContextHolder.getDataSource();
        if (debug) {
            if (NullUtil.IsAllNotNullOfString(datasource)) {
                Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
                if (dynamicTargetDataSources2.containsKey(datasource)) {
                    log.info("---当前数据源:" + datasource + "---");
                } else {
                    throw new ADIException("不存在的数据源:"+datasource,500);
                }
            } else {
                log.info("---当前数据源:默认数据源---");
            }
        }
        return datasource;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        this.dynamicTargetDataSources = targetDataSources;
    }
    // 创建数据源
    public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
        try {
            try { // 排除连接不上的错误
                Class.forName(driveClass);
                DriverManager.getConnection(url, username, password);// 相当于连接数据库
            } catch (Exception e) {
                return false;
            }
            @SuppressWarnings("resource")
            DruidDataSource druidDataSource = new DruidDataSource();
            
            druidDataSource.setName(key);
            druidDataSource.setDriverClassName(driveClass);
            druidDataSource.setUrl(url);
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
            druidDataSource.setMaxActive(200); //最大连接池数量
            druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
            druidDataSource.setMinIdle(40); //最小连接池数量
            String validationQuery = "select 1 from dual";
            if("mysql".equalsIgnoreCase(databasetype)) {
                driveClass = DBUtil.mysqldriver;
                validationQuery = "select 1";
            } else if("oracle".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.oracledriver;
                druidDataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
                druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
                int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut();
                druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
            } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.sql2000driver;
                validationQuery = "select 1";
            } else if("sqlserver".equalsIgnoreCase(databasetype)){
                driveClass = DBUtil.sql2005driver;
                validationQuery = "select 1";
            }
            
            druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
            druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
            druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
            druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
            druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
            
            druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
            druidDataSource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
            
            DataSource createDataSource = (DataSource) druidDataSource;
            druidDataSource.init();
            Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            dynamicTargetDataSources2.put(key, createDataSource);// 加入map
            setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
            super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
            log.info(key+"数据源初始化成功");
            //log.info(key+"数据源的概况:"+druidDataSource.dump());
            return true;
        } catch (Exception e) {
            log.error(e + "");
            return false;
        }
    }
    // 删除数据源
    public boolean delDatasources(String datasourceid) {
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceid)) {
            Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
            for (DruidDataSource l : druidDataSourceInstances) {
                if (datasourceid.equals(l.getName())) {
                    dynamicTargetDataSources2.remove(datasourceid);
                    DruidDataSourceStatManager.removeDataSource(l);
                    setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                    super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    // 测试数据源连接是否有效
    public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
        try {
            Class.forName(driveClass);
            DriverManager.getConnection(url, username, password);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Specify the default target DataSource, if any.
     * <p>
     * The mapped value can either be a corresponding
     * {@link javax.sql.DataSource} instance or a data source name String (to be
     * resolved via a {@link #setDataSourceLookup DataSourceLookup}).
     * <p>
     * This DataSource will be used as target if none of the keyed
     * {@link #setTargetDataSources targetDataSources} match the
     * {@link #determineCurrentLookupKey()} current lookup key.
     */
    @Override
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
    }

    /**
     * @param debug
     *            the debug to set
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * @return the debug
     */
    public boolean isDebug() {
        return debug;
    }

    /**
     * @return the dynamicTargetDataSources
     */
    public Map<Object, Object> getDynamicTargetDataSources() {
        return dynamicTargetDataSources;
    }

    /**
     * @param dynamicTargetDataSources
     *            the dynamicTargetDataSources to set
     */
    public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {
        this.dynamicTargetDataSources = dynamicTargetDataSources;
    }

    /**
     * @return the dynamicDefaultTargetDataSource
     */
    public Object getDynamicDefaultTargetDataSource() {
        return dynamicDefaultTargetDataSource;
    }

    /**
     * @param dynamicDefaultTargetDataSource
     *            the dynamicDefaultTargetDataSource to set
     */
    public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
        this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
    }

    public void createDataSourceWithCheck(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
        String datasourceId = dataSource.getDatasourceId();
        log.info("准备创建数据源"+datasourceId);
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceId)) {
            log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常...");
            //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
            DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
            boolean rightFlag = true;
            Connection connection = null;
            try {
//                log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount());
//                long activeCount = druidDataSource.getActiveCount();
//                log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount);
//                if(activeCount > 0) {
//                    log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace());
//                }
                log.info("准备获取数据库连接...");
                connection = druidDataSource.getConnection();
                log.info("数据源"+datasourceId+"正常");
            } catch (Exception e) {
                log.error(e.getMessage(),e); //把异常信息打印到日志文件
                rightFlag = false;
                log.info("缓存数据源"+datasourceId+"已失效,准备删除...");
                if(delDatasources(datasourceId)) {
                    log.info("缓存数据源删除成功");
                } else {
                    log.info("缓存数据源删除失败");
                }
            } finally {
                if(null != connection) {
                    connection.close();
                }
            }
            if(rightFlag) {
                log.info("不需要重新创建数据源");
                return;
            } else {
                log.info("准备重新创建数据源...");
                createDataSource(dataSource);
                log.info("重新创建数据源完成");
            }
        } else {
            createDataSource(dataSource);
        }
        
    }
    
    private  void createDataSource(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
        String datasourceId = dataSource.getDatasourceId();
        log.info("准备创建数据源"+datasourceId);
        String databasetype = dataSource.getDatabasetype();
        String username = dataSource.getUserName();
        String password = dataSource.getPassWord();
        password = new String(SecurityTools.decrypt(Base64.decode(password)));
        String url = dataSource.getUrl();
        String driveClass = "";
        if("mysql".equalsIgnoreCase(databasetype)) {
            driveClass = DBUtil.mysqldriver;
        } else if("oracle".equalsIgnoreCase(databasetype)){
            driveClass = DBUtil.oracledriver;
        }  else if("sqlserver2000".equalsIgnoreCase(databasetype)){
            driveClass = DBUtil.sql2000driver;
        } else if("sqlserver".equalsIgnoreCase(databasetype)){
            driveClass = DBUtil.sql2005driver;
        }
        if(testDatasource(datasourceId,driveClass,url,username,password)) {
            boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
            if(!result) {
                throw new ADIException("数据源"+datasourceId+"配置正确,但是创建失败",500);
            }
        } else {
            throw new ADIException("数据源配置有错误",500);
        }
    }

}

       第4步:创建数据源配置类DruidDBConfig(类名可以改)

       该类在springboot启动时就会实例化,主要功能是创建主数据源对象和第3步的动态数据源对象。动态数据源对象手上有一个“袋子”,用来装具体的数据源对象,通过代码可以看到,我把主数据源对象也放到了这个“袋子”里面。

代码:

package com.bitservice.adi.config;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.alibaba.druid.pool.DruidDataSource;
import com.bitservice.adi.datasource.DynamicDataSource;

/**
 * DruidDBConfig类被@Configuration标注,用作配置信息; DataSource对象被@Bean声明,为Spring容器所管理,
 *
 * @Primary表示这里定义的DataSource将覆盖其他来源的DataSource。
 *
 * @author aiyo92
 */
@Configuration
@EnableTransactionManagement
public class DruidDBConfig {

    private final Logger log = Logger.getLogger(getClass());
    
    // adi数据库连接信息
    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;

    // 连接池连接信息
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;

    @Bean // 声明其为Bean实例
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    @Qualifier("adiDataSource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource datasource = new DruidDataSource();
        // 基础连接信息
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        // 连接池连接信息
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        
        datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
        datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
        datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        String validationQuery = "select 1 from dual";
        datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
        datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
        datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
        datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
        
        datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
        datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
        datasource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
        return datasource;
    }

    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    public DataSource dynamicDataSource() throws SQLException {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDebug(false);
        dynamicDataSource.setDefaultTargetDataSource(dataSource());
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put("adiDataSource", dataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    @Bean(name = "dynamicJdbcTemplate")
    @Qualifier("dynamicJdbcTemplate")
    public NamedParameterJdbcTemplate dynamicJdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }
    
    @Bean(name = "adiJdbcTemplate")
    @Qualifier("adiJdbcTemplate")
    @Primary
    public NamedParameterJdbcTemplate adiJdbcTemplate(@Qualifier("adiDataSource") DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }
    
    @Bean(name = "entityManagerFactory")
    @Qualifier("entityManagerFactory")
    @Primary
    public LocalContainerEntityManagerFactoryBean entityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
        LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dataSource()).packages("com.bitservice.adi.entity").build();
        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
        jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        jpaProperties.put("hibernate.connection.charSet", "utf-8");
        jpaProperties.put("hibernate.show_sql", "false");
        entityManagerFactory.setJpaProperties(jpaProperties);
        return entityManagerFactory;
    }

    @Bean(name = "entityManager")
    @Qualifier("entityManager")
    @Primary
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) throws SQLException{
        return entityManageFactory(builder).getObject().createEntityManager();
    }
    
    
    
    @Bean(name = "dynamicEntityManageFactory")
    @Qualifier("dynamicEntityManageFactory")
    public LocalContainerEntityManagerFactoryBean dynamicEntityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
        LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dynamicDataSource()).packages("com.bitservice.dynamic.entity").build();
        Properties jpaProperties = new Properties();
        //jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        jpaProperties.put("hibernate.connection.charSet", "utf-8");
        jpaProperties.put("hibernate.show_sql", "false");
        entityManagerFactory.setJpaProperties(jpaProperties);
        return entityManagerFactory;
    }

    @Bean(name = "dynamicEntityManage")
    @Qualifier("dynamicEntityManage")
    public EntityManager dynamicEntityManage(EntityManagerFactoryBuilder builder) throws SQLException{
        return entityManageFactory(builder).getObject().createEntityManager();
    }
}

第5步:创建数据源切换类DBContextHolder(类名可以改)

代码:

package com.bitservice.adi.datasource;

/**
 * 数据源切换
 *
 * @author aiyo92
 *
 */
public class DBContextHolder {
    // 对当前线程的操作-线程安全的
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    // 调用此方法,切换数据源
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    // 获取数据源
    public static String getDataSource() {
        return contextHolder.get();
    }

    // 删除数据源
    public static void clearDataSource() {
        contextHolder.remove();
    }
}

核心代码写完了,接下来就是怎么用了!

创建一个DAO类SqlRepository,专门用来操作各个客数据源。

以实现对客数据源的查询请求为例,假如平台创建了多个数据源,并为每个数据源定义了SQL语句,由于平台底层执行SQL的方法是共用的(都在SqlRepository类里面),那么如何在执行查询方法之前动态切换要查询的客数据源呢?上代码!

package com.bitservice.adi.dao;

import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;

import com.bitservice.adi.datasource.DBContextHolder;
import com.bitservice.adi.datasource.DynamicDataSource;
import com.bitservice.adi.entity.DataSource;

@Service
@Transactional
public class SqlRepository {
    private final Logger logger = Logger.getLogger(getClass());

    @Autowired
    @Qualifier("dynamicJdbcTemplate")
    private NamedParameterJdbcTemplate jdbcTemplate;
    
    @Autowired
    @Qualifier("dynamicDataSource")
    private DynamicDataSource dynamicDataSource;
    
    @PersistenceContext(unitName = "dynamicEntityManageFactory")
    private EntityManager entityManager;
    
    private static boolean dynamicFlag = true;
    
    public List<Map<String, Object>> doSelect(DataSource dataSource, String sql, Map<String, Object> params) throws Exception {
        if(dynamicFlag) {
            dynamicDataSource.createDataSourceWithCheck(dataSource);
            DBContextHolder.setDataSource(dataSource.getDatasourceId());
        }
        //logger.info("执行sql查询doSelect-sql:" + sql);
        logger.info("sql_params:" + params);
        List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql, params);
        logger.info("查询数据库结果doSelect-result:" + resultList.toString());
        return resultList;
    }
}

我这里是手动切换的,大家可以使用AOP自动切换。

最后温馨提示:以上我给出的只是核心代码,大家直接粘贴使用的话肯定会报错,报错的地方大家一看就知道怎么改,所以你们只需要把报错的代码根据实际情况换成自己的代码即可。如果有任何疑问,咱们可以随时交流!

    

猜你喜欢

转载自blog.csdn.net/aiyo92/article/details/86518217
今日推荐