动态数据源配置druid+mybatis

本方案不限数据库数量完全动态配置,支持不同的数据库部署在不同的服务器上。(mybatis-plus没测试,下个版本用oracle配的时候尝试plus)

一 、这次我们使用Mysql,本地现在有两个个数据库用于测试。

如图 

二、下一步我们看一下Druid继承关系

我们可以看到想要配置DataSource其实非常简单,继承DruidDataSource就可以调用

getConnection方法了

三、下面直接开始上配置(简单的一个小例子其他的自己扩展吧)


#连接池  使用阿里的druid
spring:
  application:
    name: mall_user
  datasource:
    url: jdbc:mysql://${IP}:3306/${NAME}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8&useSSL=false
    username:
    password:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver

四、开始上代码

1、数据源配置管理类(DataSourceConfig.java)

package com.taipingyang.config.datasource2;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 *  数据源配置管理。
 *  @author zz
 *  @date 2021/1/10 20:46
 */
@MapperScan(basePackages="com.taipingyang.Mapper", value="sqlSessionFactory")
@Configuration
public class DataSourceConfig {
    /**
     * 根据配置参数创建数据源。使用派生的子类。
     *
     * @return 数据源
     */
    @Bean(name="dataSource")
    @ConfigurationProperties(prefix="spring.datasource")
    public DruidDataSource getDataSource() {DataSourceBuilder.create().type(DynamicDataSource.class).build();
        return build;
    }


    /**
     * 创建会话工厂。
     * @param dataSource 数据源
     * @return 会话工厂
     */
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        try {
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


}

 

2、定义动态数据源

(1)首先增加一个数据库标识类,用于区分不同的数据库(DBIdentifier.java)

由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。而微服务支持多线程并发的,采用线程变量。

package com.taipingyang.config.datasource2;


/**
 * 数据库标识管理类。用于区分数据源连接的不同数据库。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DBIdentifier {
    /**
     * 用不同的工程编码来区分数据库
     */

    private static ThreadLocal<String> projectCode = new ThreadLocal<String>();


    public static String getProjectCode() {
        return projectCode.get();
    }


    public static void setProjectCode(String code) {
        projectCode.set(code);
    }

}

(2)从DataSource派生一个DynamicDataSource,在其中实现数据库连接的动态切换(DynamicDataSource.java)

package com.taipingyang.config.datasource2;

import java.lang.reflect.Field;


import java.sql.SQLException;


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.pool.PoolableWrapper;
import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

/**
 * 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DynamicDataSource extends DruidDataSource {
    private static Logger log = LogManager.getLogger(DynamicDataSource.class);


    /**
     * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。 DruidDataSource getConnection
     */
    @Override
    public DruidPooledConnection getConnection() {
        String projectCode = DBIdentifier.getProjectCode()==null?"project_001": DBIdentifier.getProjectCode();
        //1、获取数据源
        DruidDataSource dds = DDSHolder.instance().getDDS(projectCode);

        DruidDataSource ddsddn = null;
        //2、如果数据源不存在则创建
        if (dds == null) {
            try {
                DruidDataSource newDDS = initDDS(projectCode);
                DDSHolder.instance().addDDS(projectCode, newDDS);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                log.error("Init data source fail. projectCode:" + projectCode);
                return null;
            }
        }

        dds = DDSHolder.instance().getDDS(projectCode);
        try {
            return dds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 以当前数据对象作为模板复制一份。
     *
     * @return dds
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */

    private DruidDataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {
        DruidDataSource dds = new DruidDataSource();
        String urlFormat = this.getUrl();

        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        String url  =urlFormat.replace("${IP}", ProjectDBMgr.instance().getDBIP(projectCode)).replace("${NAME}",ProjectDBMgr.instance().getDBName(projectCode));
        dataSource.setUrl(url);
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;

    }

}

(3)通过DDSTimer控制数据连接释放(DDSTimer.java)

package com.taipingyang.config.datasource2;


import com.alibaba.druid.pool.DruidDataSource;



/**
 * 动态数据源定时器管理。长时间无访问的数据库连接关闭。
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DDSTimer {
    /**
     * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。
     */

    private static long idlePeriodTime = 10 * 60 * 1000;


    /**
     * 动态数据源
     */

    private DruidDataSource dds;


    /**
     * 上一次访问的时间
     */

    private long lastUseTime;


    public DDSTimer(DruidDataSource dds) {

        this.dds = dds;

        this.lastUseTime = System.currentTimeMillis();

    }


    /**
     * 更新最近访问时间
     */

    public void refreshTime() {

        lastUseTime = System.currentTimeMillis();

    }


    /**
     * 检测数据连接是否超时关闭。
     *
     * @return true-已超时关闭; false-未超时
     */

    public boolean checkAndClose() {
        if (System.currentTimeMillis() - lastUseTime > idlePeriodTime) {
            dds.close();
            return true;
        }
        return false;
    }

    public DruidDataSource getDds() {
        return dds;
    }

}

(4)通过DDSHolder来管理不同的数据源,提供数据源的添加、查询功能(DDSHolder.java)

package com.taipingyang.config.datasource2;

import com.alibaba.druid.pool.DruidDataSource;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;


import java.util.Map.Entry;



/**
 * 动态数据源管理器。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DDSHolder {

    /**
     * 管理动态数据源列表。<工程编码,数据源>

     */
    private Map<String, DDSTimer> ddsMap =new HashMap<String, DDSTimer>();

    /**
     * 通过定时任务周期性清除不使用的数据源
     */

    private static Timer clearIdleTask = new Timer();

    static {
        clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000);
    };


    private DDSHolder() {

    }



    /*
     * 获取单例对象
     */
    public static DDSHolder instance() {
        return DDSHolderBuilder.instance;
    }


    /**
     * 添加动态数据源。
     * @param projectCode 项目编码
     * @param dds         dds
     */
    public synchronized void addDDS(String projectCode, DruidDataSource dds) {
        DDSTimer ddst = new DDSTimer(dds);
        ddsMap.put(projectCode, ddst);

    }


    /**
     * 查询动态数据源
     *
     * @param projectCode 项目编码
     * @return dds
     */
    public synchronized DruidDataSource getDDS(String projectCode) {

        if (ddsMap.containsKey(projectCode)) {

            DDSTimer ddst = ddsMap.get(projectCode);

            ddst.refreshTime();

            return ddst.getDds();

        }


        return null;

    }


    /**
     * 清除超时无人使用的数据源。
     */

    public synchronized void clearIdleDDS() {
        Iterator<Map.Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator();

        while(iter.hasNext()){
            Entry<String, DDSTimer> entry = iter.next();
            if (entry.getValue().checkAndClose()) {
                iter.remove();
            }
        }
    }


    /**
     * 单例构件类
     *
     * @author elon
     * @version 2018年2月26日
     */

    private static class DDSHolderBuilder {
        private static DDSHolder instance = new DDSHolder();
    }
}

(5)定时器任务ClearIdleTimerTask用于定时清除空闲的数据源(ClearIdleTimerTask.java)

package com.taipingyang.config.datasource2;

import java.util.TimerTask;


/**
 * 清除空闲连接任务。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class ClearIdleTimerTask extends TimerTask {


    @Override
    public void run() {
        DDSHolder.instance().clearIdleDDS();
    }
}

(6)管理项目编码与数据库IP和名称的映射关系(ProjectDBMgr.java)

package com.taipingyang.config.datasource2;

import java.util.HashMap;

import java.util.Map;


/**
 * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class ProjectDBMgr {
    /**
     * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;
     * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。

     */

    private Map<String, String> dbNameMap =new HashMap<String, String>();


    /**
     * 保存项目编码与数据库IP的映射关系。

     */

    private Map<String, String> dbIPMap =new HashMap<String, String>();


    private ProjectDBMgr() {
        dbNameMap.put("project_001", "annoyingly");
        dbNameMap.put("project_002", "tpy");

        dbIPMap.put("project_001", "127.0.0.1");
        dbIPMap.put("project_002", "127.0.0.1");
    }


    public static ProjectDBMgr instance() {
        return ProjectDBMgrBuilder.instance;
    }


    // 实际开发中改为从缓存获取

    public String getDBName(String projectCode) {
        if (dbNameMap.containsKey(projectCode)) {
            return dbNameMap.get(projectCode);
        }
        return "";
    }


    //实际开发中改为从缓存中获取

    public String getDBIP(String projectCode) {
        if (dbIPMap.containsKey(projectCode)) {
            return dbIPMap.get(projectCode);
        }
        return "";
    }

    private static class ProjectDBMgrBuilder {
        private static ProjectDBMgr instance = new ProjectDBMgr();
    }
}

以上就是Druid+mybatis动态数据源全部配置

下面看一下我写的测试类=》

package com.taipingyang.Mapper;

import com.taipingyang.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {

    @Select(" select * from tpy_user ")
    List<User> testuser();

}

直接上controller 


@RestController
public class userController {



    @Autowired
    private UserService userService;

    @RequestMapping("user/testuser")
    public List<User> testuser(String projectCode) {
        DBIdentifier.setProjectCode(projectCode);
        return userService.testuser();
    }

}

四、postman开始测试

查询数据库全部正常

 

 

其实非常简单,只需要在继承DruidDataSource重新getConnection建立连接就好了,至于搞并发场景下还是需要凭借其他框架的。通过这次搭建希望大家能够对DataSource有全新的认识。

 

 

 

 

代码千万行,注释第一行。格式不规范,报错两行泪!!!

 

 

  • 12
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值