精华内容
下载资源
问答
  • 之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不且固定的情况。...

    之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力。

    下面讲的方案能支持数据库动态增删,数量不限。

    数据库环境准备

    下面以Mysql为例,先在本地建3个数据库用于测试。需要说明的是本方案不限数据库数量,支持不同的数据库部署在不同的服务器上。如图所示db_project_001、db_project_002、db_project_003。

    53477f348c241d9b9e9e2fbeb344e7aa.png

    搭建Java后台微服务项目

    创建一个Spring Boot的maven项目:

    55cadacd8116636d443faab8065e0399.png

    config:数据源配置。

    datasource:自己实现的动态数据源相关类。

    dbmgr:管理项目编码与数据库IP、名称的映射关系(实际项目中这部分数据保存在redis缓存中,可动态增删)。

    mapper:mybatis的数据库访问接口。

    model:映射模型。

    rest:微服务对外发布的restful接口,这里用来测试。

    application.yml:配置数据库JDBC参数。

    详细的代码实现

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

    package com.elon.dds.config;

    import javax.sql.DataSource;

    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.autoconfigure.jdbc.DataSourceBuilder;

    import org.springframework.boot.context.properties.ConfigurationProperties;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import com.elon.dds.datasource.DynamicDataSource;

    /**

    * 数据源配置管理。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @Configuration

    @MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory")

    public class DataSourceConfig {

    /**

    * 根据配置参数创建数据源。使用派生的子类。

    *

    * @return 数据源

    */

    @Bean(name="dataSource")

    @ConfigurationProperties(prefix="spring.datasource")

    public DataSource getDataSource() {

    DataSourceBuilder builder = DataSourceBuilder.create();

    builder.type(DynamicDataSource.class);

    return builder.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.elon.dds.datasource;

    /**

    * 数据库标识管理类。用于区分数据源连接的不同数据库。

    *

    * @author elon

    * @version 2018-02-25

    */

    public class DBIdentifier {

    /**

    * 用不同的工程编码来区分数据库

    */

    private static ThreadLocal projectCode = new ThreadLocal();

    public static String getProjectCode() {

    return projectCode.get();

    }

    public static void setProjectCode(String code) {

    projectCode.set(code);

    }

    }

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

    package com.elon.dds.datasource;

    import java.lang.reflect.Field;

    import java.sql.Connection;

    import java.sql.SQLException;

    import org.apache.logging.log4j.LogManager;

    import org.apache.logging.log4j.Logger;

    import org.apache.tomcat.jdbc.pool.DataSource;

    import org.apache.tomcat.jdbc.pool.PoolProperties;

    import com.elon.dds.dbmgr.ProjectDBMgr;

    /**

    * 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。

    *

    * @author elon

    * @version 2018-02-25

    */

    public class DynamicDataSource extends DataSource {

    private static Logger log = LogManager.getLogger(DynamicDataSource.class);

    /**

    * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。

    */

    @Override

    public Connection getConnection(){

    String projectCode = DBIdentifier.getProjectCode();

    //1、获取数据源

    DataSource dds = DDSHolder.instance().getDDS(projectCode);

    //2、如果数据源不存在则创建

    if (dds == null) {

    try {

    DataSource 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 DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {

    DataSource dds = new DataSource();

    // 2、复制PoolConfiguration的属性

    PoolProperties property = new PoolProperties();

    Field[] pfields = PoolProperties.class.getDeclaredFields();

    for (Field f : pfields) {

    f.setAccessible(true);

    Object value = f.get(this.getPoolProperties());

    try

    {

    f.set(property, value);

    }

    catch (Exception e)

    {

    //有一些static final的属性不能修改。忽略。

    log.info("Set value fail. attr name:" + f.getName());

    continue;

    }

    }

    dds.setPoolProperties(property);

    // 3、设置数据库名称和IP(一般来说,端口和用户名、密码都是统一固定的)

    String urlFormat = this.getUrl();

    String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),

    ProjectDBMgr.instance().getDBName(projectCode));

    dds.setUrl(url);

    return dds;

    }

    }

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

    package com.elon.dds.datasource;

    import org.apache.tomcat.jdbc.pool.DataSource;

    /**

    * 动态数据源定时器管理。长时间无访问的数据库连接关闭。

    *

    * @author elon

    * @version 2018年2月25日

    */

    public class DDSTimer {

    /**

    * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。

    */

    private static long idlePeriodTime = 10 * 60 * 1000;

    /**

    * 动态数据源

    */

    private DataSource dds;

    /**

    * 上一次访问的时间

    */

    private long lastUseTime;

    public DDSTimer(DataSource 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 DataSource getDds() {

    return dds;

    }

    }

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

    package com.elon.dds.datasource;

    import java.util.HashMap;

    import java.util.Iterator;

    import java.util.Map;

    import java.util.Map.Entry;

    import java.util.Timer;

    import org.apache.tomcat.jdbc.pool.DataSource;

    /**

    * 动态数据源管理器。

    *

    * @author elon

    * @version 2018年2月25日

    */

    public class DDSHolder {

    /**

    * 管理动态数据源列表。

    */

    private Map ddsMap = new HashMap();

    /**

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

    */

    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, DataSource dds) {

    DDSTimer ddst = new DDSTimer(dds);

    ddsMap.put(projectCode, ddst);

    }

    /**

    * 查询动态数据源

    *

    * @param projectCode 项目编码

    * @return dds

    */

    public synchronized DataSource getDDS(String projectCode) {

    if (ddsMap.containsKey(projectCode)) {

    DDSTimer ddst = ddsMap.get(projectCode);

    ddst.refreshTime();

    return ddst.getDds();

    }

    return null;

    }

    /**

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

    */

    public synchronized void clearIdleDDS() {

    Iterator> iter = ddsMap.entrySet().iterator();

    for (; iter.hasNext(); ) {

    Entry 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.elon.dds.datasource;

    import java.util.TimerTask;

    /**

    * 清除空闲连接任务。

    *

    * @author elon

    * @version 2018年2月26日

    */

    public class ClearIdleTimerTask extends TimerTask {

    @Override

    public void run() {

    DDSHolder.instance().clearIdleDDS();

    }

    }

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

    package com.elon.dds.dbmgr;

    import java.util.HashMap;

    import java.util.Map;

    /**

    * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。

    * @author elon

    * @version 2018年2月25日

    */

    public class ProjectDBMgr {

    /**

    * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;

    * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。

    */

    private Map dbNameMap = new HashMap();

    /**

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

    */

    private Map dbIPMap = new HashMap();

    private ProjectDBMgr() {

    dbNameMap.put("project_001", "db_project_001");

    dbNameMap.put("project_002", "db_project_002");

    dbNameMap.put("project_003", "db_project_003");

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

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

    dbIPMap.put("project_003", "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();

    }

    }

    4.  编写数据库访问的mapper(UserMapper.java)

    package com.elon.dds.mapper;

    import java.util.List;

    import org.apache.ibatis.annotations.Mapper;

    import org.apache.ibatis.annotations.Result;

    import org.apache.ibatis.annotations.Results;

    import org.apache.ibatis.annotations.Select;

    import com.elon.dds.model.User;

    /**

    * Mybatis映射接口定义。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @Mapper

    public interface UserMapper

    {

    /**

    * 查询所有用户数据

    * @return 用户数据列表

    */

    @Results(value= {

    @Result(property="userId", column="id"),

    @Result(property="name", column="name"),

    @Result(property="age", column="age")

    })

    @Select("select id, name, age from tbl_user")

    List getUsers();

    }

    5. 定义查询对象模型(User.java)

    package com.elon.dds.model;

    public class User

    {

    private int userId = -1;

    private String name = "";

    private int age = -1;

    @Override

    public String toString()

    {

    return "name:" + name + "|age:" + age;

    }

    public int getUserId()

    {

    return userId;

    }

    public void setUserId(int userId)

    {

    this.userId = userId;

    }

    public String getName()

    {

    return name;

    }

    public void setName(String name)

    {

    this.name = name;

    }

    public int getAge()

    {

    return age;

    }

    public void setAge(int age)

    {

    this.age = age;

    }

    }

    6.  定义查询数据的restful接口(WSUser.java)

    package com.elon.dds.rest;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.web.bind.annotation.RequestMapping;

    import org.springframework.web.bind.annotation.RequestMethod;

    import org.springframework.web.bind.annotation.RequestParam;

    import org.springframework.web.bind.annotation.RestController;

    import com.elon.dds.datasource.DBIdentifier;

    import com.elon.dds.mapper.UserMapper;

    import com.elon.dds.model.User;

    /**

    * 用户数据访问接口。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @RestController

    @RequestMapping(value="/user")

    public class WSUser {

    @Autowired

    private UserMapper userMapper;

    /**

    * 查询项目中所有用户信息

    *

    * @param projectCode 项目编码

    * @return 用户列表

    */

    @RequestMapping(value="/v1/users", method=RequestMethod.GET)

    public List queryUser(@RequestParam(value="projectCode", required=true) String projectCode)

    {

    DBIdentifier.setProjectCode(projectCode);

    return userMapper.getUsers();

    }

    }

    要求每次查询都要带上projectCode参数。

    7.   编写Spring Boot App的启动代码(App.java)

    package com.elon.dds;

    import org.springframework.boot.SpringApplication;

    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**

    * Hello world!

    *

    */

    @SpringBootApplication

    public class App

    {

    public static void main( String[] args )

    {

    System.out.println( "Hello World!" );

    SpringApplication.run(App.class, args);

    }

    }

    8.  在application.yml中配置数据源

    其中的数据库IP和数据库名称使用%s。在执行数据操作时动态切换。

    spring:

    datasource:

    url: jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf-8

    username: root

    password:

    driver-class-name: com.mysql.jdbc.Driver

    logging:

    config: classpath:log4j2.xml

    测试方案

    1.   查询project_001的数据,正常返回

    785f0d490254ada56cfe0f67e94b8663.png

    2.  查询project_002的数据,正常返回

    025e52b63cd88afe344d7a84c3345346.png

    Spring Boot配置多数据源并实现Druid自动切换

    原文:https://blog.csdn.net/acquaintanceship/article/details/75350653 Spring Boot配置多数据源配置yml文件主数据源配置从数据 ...

    spring boot 配置双数据源mysql、sqlServer

    背景:原来一直都是使用mysql数据库,在application.properties 中配置数据库信息 spring.datasource.url=jdbc:mysql://xxxx/test sp ...

    spring boot 配置多数据源

    https://www.jianshu.com/p/b2e53a2521fc

    spring boot 配置虚拟静态资源文件

    我们实现的目的是:通过spring boot 配置静态资源访问的虚拟路径,可实现在服务器,或者在本地通过:http://ip地址:端口/资源路径/文件名  ,可直接访问文件 比如:我们本地电脑的:E: ...

    Spring配置动态数据源-读写分离和多数据源

    在现在互联网系统中,随着用户量的增长,单数据源通常无法满足系统的负载要求.因此为了解决用户量增长带来的压力,在数据库层面会采用读写分离技术和数据库拆分等技术.读写分离就是就是一个Master数据库,多 ...

    Spring Boot2.x 动态数据源配置

    原文链接: Spring Boot2.x 动态数据源配置 基于 Spring Boot 2.x.Spring Data JPA.druid.mysql 的动态数据源配置Demo,适合用于数据库的读写分 ...

    Spring boot配置多个Redis数据源操作实例

    原文:https://www.jianshu.com/p/c79b65b253fa Spring boot配置多个Redis数据源操作实例 在SpringBoot是项目中整合了两个Redis的操作实例 ...

    spring boot 配置访问其他模块包中的mapper和xml

    maven项目结构如下,这里只是简单测试demo,使用的springboot版本为2.1.3.RELEASE 1.comm模块主要是一些mybatis的mapper接口和对应的xml文件,以及数据库表 ...

    spring boot 开静态资源访问,配置视图解析器

    配置视图解析器spring.mvc.view.prefix=/pages/spring.mvc.view.suffiix= spring boot 开静态资源访问application.proerti ...

    随机推荐

    【python】点分十进制ip与数字互转

    来源:http://www.cnblogs.com/vovlie/archive/2012/10/17/2727029.html 利用lambda表达式,非常简洁 int_to_ip = lambda ...

    Ubuntu/linux 有关权限修改的命令

    chmod更改文件权限命令 最常用的基础命令chmod chmod 777  目录名(路径名) 777 三位数字分别代表 user.group.others 的权限,可读(r).可写(w).可执行(x ...

    NGUI 图集生成 图片Sprite 有撕裂边的问题

    修改 Dimensions 的 X 和 Y值进行调整. 在生成图集时 选择Padding 设置1以上 应该不会出现这个问题.

    Best Cow Line (POJ 3217)

    给定长度为N的字符串S,要构造一个长度为N的字符串T,起初,T是一个空串,随后反复进行下列任意操作. *从S的头部删除一个字符,加到T的尾部 *从S的尾部删除一个字符,加到T的尾部 目标是要构造字典序 ...

    Trie图和Fail树

    Trie图和AC自动机的区别 Trie图是AC自动机的确定化形式,即把每个结点不存在字符的next指针都补全了.这样做的好处是使得构造fail指针时不需要next指针为空而需要不断回溯. 比如构造ne ...

    vSphere HA状况:未知配置错误解决的方法

    问题:vSphere HA配置出现未知错误,导致打不开主机上的虚拟机电源,vmware client连接vcenter后,主机显示警报信息,例如以下: 解决:例如以下图,选中有问题的物理主机,然后又一 ...

    Akari谜题(关灯问题)的开灯解法

    提高解时预处理的速度 本方法的结果是得到满足所有黑色有数字方块的一个带有未照亮的块的可能解集. 解集大小为 4~(3号块数量+1号块数量)+6~(2号块数量)-灯互相照射到的解的集合.集合中的灯为黄色 ...

    压缩JS的eclipse插件

    主页:http://jscompressor.oncereply.me/ Update site: http://jscompressor.oncereply.me/update/

    os.rename 和os.replace

    f1 = open("hello.txt","w") f1.write("hello,my name is bobo.") f1.close ...

    F800上的CPU有多少个core?

    本来想看看F800的一个节点上,每个core的cpu的使用情况.使用下面的命令,可以进行查看: top –P 从命令输出来看,好像是有32个core嘛. 使用下面的命令可以看到具体的CPU的信息. d ...

    展开全文
  • 之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不且固定的情况。...

    之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力。

    下面讲的方案能支持数据库动态增删,数量不限。

    数据库环境准备

    下面一Mysql为例,先在本地建3个数据库用于测试。需要说明的是本方案不限数据库数量,支持不同的数据库部署在不同的服务器上。如图所示db_project_001、db_project_002、db_project_003。

    471a2422016011635b24b3864774ad76.png

    搭建Java后台微服务项目

    创建一个Spring Boot的maven项目:

    94d3385f74618d36cff0a77ba18ee7cc.png

    config:数据源配置管理类。

    datasource:自己实现的数据源管理逻辑。

    dbmgr:管理了项目编码与数据库IP、名称的映射关系(实际项目中这部分数据保存在redis缓存中,可动态增删)。

    mapper:数据库访问接口。

    model:映射模型。

    rest:微服务对外发布的restful接口,这里用来测试。

    application.yml:配置了数据库的JDBC参数。

    详细的代码实现

    1. 添加数据源配置

    package com.elon.dds.config;

    import javax.sql.DataSource;

    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.autoconfigure.jdbc.DataSourceBuilder;

    import org.springframework.boot.context.properties.ConfigurationProperties;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import com.elon.dds.datasource.DynamicDataSource;

    /**

    * 数据源配置管理。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @Configuration

    @MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory")

    public class DataSourceConfig {

    /**

    * 根据配置参数创建数据源。使用派生的子类。

    *

    * @return 数据源

    */

    @Bean(name="dataSource")

    @ConfigurationProperties(prefix="spring.datasource")

    public DataSource getDataSource() {

    DataSourceBuilder builder = DataSourceBuilder.create();

    builder.type(DynamicDataSource.class);

    return builder.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)  首先增加一个数据库标识类,用于区分不同的数据库访问。

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

    package com.elon.dds.datasource;

    /**

    * 数据库标识管理类。用于区分数据源连接的不同数据库。

    *

    * @author elon

    * @version 2018-02-25

    */

    public class DBIdentifier {

    /**

    * 用不同的工程编码来区分数据库

    */

    private static ThreadLocal projectCode = new ThreadLocal();

    public static String getProjectCode() {

    return projectCode.get();

    }

    public static void setProjectCode(String code) {

    projectCode.set(code);

    }

    }

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

    import java.lang.reflect.Field;

    import java.sql.Connection;

    import java.sql.SQLException;

    import org.apache.logging.log4j.LogManager;

    import org.apache.logging.log4j.Logger;

    import org.apache.tomcat.jdbc.pool.DataSource;

    import org.apache.tomcat.jdbc.pool.PoolProperties;

    import com.elon.dds.dbmgr.ProjectDBMgr;

    /**

    * 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。

    *

    * @author elon

    * @version 2018-02-25

    */

    public class DynamicDataSource extends DataSource {

    private static Logger log = LogManager.getLogger(DynamicDataSource.class);

    /**

    * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。

    */

    @Override

    public Connection getConnection(){

    String projectCode = DBIdentifier.getProjectCode();

    //1、获取数据源

    DataSource dds = DDSHolder.instance().getDDS(projectCode);

    //2、如果数据源不存在则创建

    if (dds == null) {

    try {

    DataSource 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 DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {

    DataSource dds = new DataSource();

    // 2、复制PoolConfiguration的属性

    PoolProperties property = new PoolProperties();

    Field[] pfields = PoolProperties.class.getDeclaredFields();

    for (Field f : pfields) {

    f.setAccessible(true);

    Object value = f.get(this.getPoolProperties());

    try

    {

    f.set(property, value);

    }

    catch (Exception e)

    {

    log.info("Set value fail. attr name:" + f.getName());

    continue;

    }

    }

    dds.setPoolProperties(property);

    // 3、设置数据库名称和IP(一般来说,端口和用户名、密码都是统一固定的)

    String urlFormat = this.getUrl();

    String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),

    ProjectDBMgr.instance().getDBName(projectCode));

    dds.setUrl(url);

    return dds;

    }

    }

    3)  通过DDSTimer控制数据连接释放(超过指定时间未使用的数据源释放)

    package com.elon.dds.datasource;

    import org.apache.tomcat.jdbc.pool.DataSource;

    /**

    * 动态数据源定时器管理。长时间无访问的数据库连接关闭。

    *

    * @author elon

    * @version 2018年2月25日

    */

    public class DDSTimer {

    /**

    * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。

    */

    private static long idlePeriodTime = 10 * 60 * 1000;

    /**

    * 动态数据源

    */

    private DataSource dds;

    /**

    * 上一次访问的时间

    */

    private long lastUseTime;

    public DDSTimer(DataSource 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 DataSource getDds() {

    return dds;

    }

    }

    4)      增加DDSHolder来管理不同的数据源,提供数据源的添加、查询功能

    package com.elon.dds.datasource;

    import java.util.HashMap;

    import java.util.Iterator;

    import java.util.Map;

    import java.util.Map.Entry;

    import java.util.Timer;

    import org.apache.tomcat.jdbc.pool.DataSource;

    /**

    * 动态数据源管理器。

    *

    * @author elon

    * @version 2018年2月25日

    */

    public class DDSHolder {

    /**

    * 管理动态数据源列表。

    */

    private Map ddsMap = new HashMap();

    /**

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

    */

    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, DataSource dds) {

    DDSTimer ddst = new DDSTimer(dds);

    ddsMap.put(projectCode, ddst);

    }

    /**

    * 查询动态数据源

    *

    * @param projectCode 项目编码

    * @return dds

    */

    public synchronized DataSource getDDS(String projectCode) {

    if (ddsMap.containsKey(projectCode)) {

    DDSTimer ddst = ddsMap.get(projectCode);

    ddst.refreshTime();

    return ddst.getDds();

    }

    return null;

    }

    /**

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

    */

    public synchronized void clearIdleDDS() {

    Iterator> iter = ddsMap.entrySet().iterator();

    for (; iter.hasNext(); ) {

    Entry 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用于定时清除空闲的数据源

    package com.elon.dds.datasource;

    import java.util.TimerTask;

    /**

    * 清除空闲连接任务。

    *

    * @author elon

    * @version 2018年2月26日

    */

    public class ClearIdleTimerTask extends TimerTask {

    @Override

    public void run() {

    DDSHolder.instance().clearIdleDDS();

    }

    }

    3.       管理项目编码与数据库IP和名称的映射关系

    package com.elon.dds.dbmgr;

    import java.util.HashMap;

    import java.util.Map;

    /**

    * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。

    * @author elon

    * @version 2018年2月25日

    */

    public class ProjectDBMgr {

    /**

    * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;

    * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。

    */

    private Map dbNameMap = new HashMap();

    /**

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

    */

    private Map dbIPMap = new HashMap();

    private ProjectDBMgr() {

    dbNameMap.put("project_001", "db_project_001");

    dbNameMap.put("project_002", "db_project_002");

    dbNameMap.put("project_003", "db_project_003");

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

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

    dbIPMap.put("project_003", "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();

    }

    }

    4.       定义数据库访问的mapper

    package com.elon.dds.mapper;

    import java.util.List;

    import org.apache.ibatis.annotations.Mapper;

    import org.apache.ibatis.annotations.Result;

    import org.apache.ibatis.annotations.Results;

    import org.apache.ibatis.annotations.Select;

    import com.elon.dds.model.User;

    /**

    * Mybatis映射接口定义。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @Mapper

    public interface UserMapper

    {

    /**

    * 查询所有用户数据

    * @return 用户数据列表

    */

    @Results(value= {

    @Result(property="userId", column="id"),

    @Result(property="name", column="name"),

    @Result(property="age", column="age")

    })

    @Select("select id, name, age from tbl_user")

    List getUsers();

    }

    5.       定义查询对象模型

    package com.elon.dds.model;

    public class User

    {

    private int userId = -1;

    private String name = "";

    private int age = -1;

    @Override

    public String toString()

    {

    return "name:" + name + "|age:" + age;

    }

    public int getUserId()

    {

    return userId;

    }

    public void setUserId(int userId)

    {

    this.userId = userId;

    }

    public String getName()

    {

    return name;

    }

    public void setName(String name)

    {

    this.name = name;

    }

    public int getAge()

    {

    return age;

    }

    public void setAge(int age)

    {

    this.age = age;

    }

    }

    6.       定义查询用户数据的restful接口

    package com.elon.dds.rest;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.web.bind.annotation.RequestMapping;

    import org.springframework.web.bind.annotation.RequestMethod;

    import org.springframework.web.bind.annotation.RequestParam;

    import org.springframework.web.bind.annotation.RestController;

    import com.elon.dds.datasource.DBIdentifier;

    import com.elon.dds.mapper.UserMapper;

    import com.elon.dds.model.User;

    /**

    * 用户数据访问接口。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @RestController

    @RequestMapping(value="/user")

    public class WSUser {

    @Autowired

    private UserMapper userMapper;

    /**

    * 查询项目中所有用户信息

    *

    * @param projectCode 项目编码

    * @return 用户列表

    */

    @RequestMapping(value="/v1/users", method=RequestMethod.GET)

    public List queryUser(@RequestParam(value="projectCode", required=true) String projectCode)

    {

    DBIdentifier.setProjectCode(projectCode);

    return userMapper.getUsers();

    }

    }

    要求每次查询都要带上projectCode参数。

    7.       编写Spring Boot App的启动代码

    package com.elon.dds;

    import org.springframework.boot.SpringApplication;

    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**

    * Hello world!

    *

    */

    @SpringBootApplication

    public class App

    {

    public static void main( String[] args )

    {

    System.out.println( "Hello World!" );

    SpringApplication.run(App.class, args);

    }

    }

    8.       在application.yml中配置数据源

    其中的数据库IP和数据库名称使用%s。在查询用户数据中动态切换。

    spring:

    datasource:

    url: jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf-8

    username: root

    password:

    driver-class-name: com.mysql.jdbc.Driver

    logging:

    config: classpath:log4j2.xml

    测试方案

    1.       查询project_001的数据,正常返回

    3393b556794e5eb7e588474c3cb9de8f.png

    2.       查询project_002的数据,正常返回

    529d10427f70b920543d877a3d30a644.png

    总结

    以上所述是小编给大家介绍的通过Spring Boot配置动态数据源访问多个数据库的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

    展开全文
  • springboot实现动态数据源访问多数据库1. 简介和实现类介绍2. 项目创建实现类2.1添加数据源配置 DateSourceConfig2.2.定义动态数据源2.2.1 数据库标识管理类 DBIdentifier2.2.2.定义动态数据源派生类 ...

    1. 简介和实现类介绍

    昨天进行开发的时候,遇到一个问题,在不同的数据指标下,访问的数据库是不一样的,但是又在同一个数据库链接里面,以前用到的都是单数据源,数据库不会变,第一次遇到这个问题,网上查了一下资料,网上很多都是多数据源的实现方式,无非就是写死的动态源,然后来回切换,但是在项目中我们不知道有几个动态源是不是就没办法写,难道还需要来一个数据源我们就要加一套数据源的配置,比较麻烦,所以只能做成动态的,实现最小化的改动来实现多数据源,所以我们把数据源存在了,yml文件里面最终用map读,实现动态多数据源的问题。

    实现类名 实现类的用途
    ClearIdleTimerTask 清除空闲连接任务
    DataSourceByMapNameConfig 多数据源的存储
    DataSourceHolder 动态数据源管理器
    DataSourceTimer 动态数据源定时器管理。长时间无访问的数据库连接关闭
    DateSourceConfig 数据源配置管理
    DynamicDataSource 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现
    DBIdentifier 数据库标识管理类。用于区分数据源连接的不同数据库。

    大致就是这个样子的几个类来实现的,下面进行代码详解
    哦!对了,还要依赖一个jar包,maven地址在这

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <version>8.5.21</version>
        </dependency>
    

    2. 项目创建实现类

    2.1添加数据源配置 DateSourceConfig

    package com.casic.collect.config;
    
    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;
    
    import javax.sql.DataSource;
    
    /**
     * 数据源配置管理。
     *
     * @author jiangs
     * @version 2020年12月17日
     */
    @Configuration
    @MapperScan(basePackages="com.test.collect.mapper", value="sqlSessionFactory")
    public class DateSourceConfig {
        /**
         * 根据配置参数创建数据源。使用派生的子类。
         * @return 数据源
         */
        @Bean(name="dataSource")
        @ConfigurationProperties(prefix="spring.datasource")
        public DataSource getDataSource() {
            DataSourceBuilder builder = DataSourceBuilder.create();
            builder.type(DynamicDataSource.class);
            return builder.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.2.定义动态数据源

    2.2.1 数据库标识管理类 DBIdentifier

    这里我是直接通过yml获取的map所以,请看后面的yml获取键值对。

    首先增加一个数据库标识类,用于区分不同的数据库访问。
    由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。而微服务支持多线程并发的,采用线程变量。

    /**
     * 数据库标识管理类。用于区分数据源连接的不同数据库。
     *
     * @author jiangs
     * @version 2020-12-17
     */
    public class DBIdentifier {
        /**
         * 用不同的工程编码来区分数据库
         */
        private static ThreadLocal<String> projectCode = new ThreadLocal<String>();
        private static Map<String,String> dbName = Maps.newHashMap();
    
        public static Map<String, String> getMap() {
            return dbName;
        }
    
        public static String getProjectCode() {
            return projectCode.get();
        }
        public static void setProjectCode(String code,Map map) {
            projectCode.set(code);
            dbName = map;
        }
    }
    

    2.2.2.定义动态数据源派生类 DynamicDataSource

    从DataSource派生了一个DynamicDataSource,在其中实现数据库连接的动态切换

    /**
     * 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。
     *
     * @author jiangs
     * @version 2020-12-17
     */
    public class DynamicDataSource extends DataSource {
        private static Logger log = LogManager.getLogger(DynamicDataSource.class);
    
        /**
         * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。
         */
        @Override
        public Connection getConnection(){
            String projectCode = DBIdentifier.getProjectCode();
            //1、获取数据源
            DataSource dataSource = DataSourceHolder.instance().getDataSource(projectCode);
            //2、如果数据源不存在则创建
            if (dataSource == null) {
                try {
                    DataSource initDataSource = initDataSource(projectCode);
                    DataSourceHolder.instance().addDataSource(projectCode, initDataSource);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    log.error("Init data source fail. projectCode:" + projectCode);
                    return null;
                }
            }
            dataSource = DataSourceHolder.instance().getDataSource(projectCode);
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 以当前数据对象作为模板复制一份。
         *
         * @return DataSource
         * @throws IllegalAccessException
         * @throws IllegalArgumentException
         */
        private DataSource initDataSource(String projectCode) throws IllegalArgumentException, IllegalAccessException {
            DataSource dataSource = new DataSource();
            // 2、复制PoolConfiguration的属性
            PoolProperties property = new PoolProperties();
            Field[] fields = PoolProperties.class.getDeclaredFields();
            for (Field f : fields) {
                f.setAccessible(true);
                Object value = f.get(this.getPoolProperties());
                try{
                    f.set(property, value);
                }
                catch (Exception e){
                    log.info("Set value fail. attr name:" + f.getName());
                    continue;
                }
            }
            dataSource.setPoolProperties(property);
            // 3、设置数据库名称和IP(一般来说,端口和用户名、密码都是统一固定的)
            String urlFormat = this.getUrl();
            String url = String.format(urlFormat, DBIdentifier.getMap().get(projectCode));
            dataSource.setUrl(url);
            return dataSource;
        }
    }
    

    2.2.3.动态数据源定时器管理 DataSourceTimer

    动态数据源定时器管理。长时间无访问的数据库连接关闭。

    /**
     * 动态数据源定时器管理。长时间无访问的数据库连接关闭。
     *
     * @author jiangs
     * @version 2020年12月17日
     */
    public class DataSourceTimer {
        /**
         * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。
         */
        private static long idlePeriodTime = 10 * 60 * 1000;
        /**
         * 动态数据源
         */
        private DataSource dataSource;
        /**
         * 上一次访问的时间
         */
        private long lastUseTime;
        public DataSourceTimer(DataSource dataSource) {
            this.dataSource = dataSource;
            this.lastUseTime = System.currentTimeMillis();
        }
        /**
         * 更新最近访问时间
         */
        public void refreshTime() {
            lastUseTime = System.currentTimeMillis();
        }
        /**
         * 检测数据连接是否超时关闭。
         *
         * @return true-已超时关闭; false-未超时
         */
        public boolean checkAndClose() {
            if (System.currentTimeMillis() - lastUseTime > idlePeriodTime)
            {
                dataSource.close();
                return true;
            }
            return false;
        }
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    

    2.2.4.动态数据源管理器 DataSourceHolder

    DataSourceHolder来管理不同的数据源,提供数据源的添加、查询功能。
    里面添加了读写锁,并创建了ConcurrentHashMap来保证线程安全。

    /**
     * 动态数据源管理器。
     *
     * @author jiangs
     * @version 2020年12月17日
     */
    public class DataSourceHolder {
        /**
         * 管理动态数据源列表。<工程编码,数据源>
         */
        private Map<String, DataSourceTimer> dataSourceTimerMap = new ConcurrentHashMap<>();
        /**
         * 通过定时任务周期性清除不使用的数据源
         */
        private static Timer clearIdleTask = new Timer();
        static {
            clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000);
        };
        private DataSourceHolder() {
        }
        /**
         * 单例构件类
         * @author jiangs
         * @version 2020年12月17日
         */
        private static class DataSourceHolderBuilder {
            private static DataSourceHolder instance = new DataSourceHolder();
        }
        /*
         * 获取单例对象
         */
        public static DataSourceHolder instance() {
            return DataSourceHolderBuilder.instance;
        }
        /**
         * 添加动态数据源。
         *
         * @param projectCode 项目编码
         * @param dataSource 数据源
         */
        public void addDataSource(String projectCode, DataSource dataSource) {
            synchronized(dataSourceTimerMap) {
                DataSourceTimer dataSourceTimer = new DataSourceTimer(dataSource);
                dataSourceTimerMap.put(projectCode, dataSourceTimer);
            };
        }
        /**
         * 查询动态数据源
         *
         * @param projectCode 项目编码
         * @return dds
         */
        public DataSource getDataSource(String projectCode) {
            synchronized(dataSourceTimerMap){
                if (dataSourceTimerMap.containsKey(projectCode)) {
                    DataSourceTimer dataSourceTimer = dataSourceTimerMap.get(projectCode);
                    dataSourceTimer.refreshTime();
                    return dataSourceTimer.getDataSource();
                }
            };
            return null;
        }
        /**
         * 清除超时无人使用的数据源。
         * 存在正在运行数据源会被清理掉风险!!!
         */
        public synchronized void clearIdleDataSource() {
            Iterator<Map.Entry<String, DataSourceTimer>> iter = dataSourceTimerMap.entrySet().iterator();
            for (; iter.hasNext(); ) {
                Map.Entry<String, DataSourceTimer> entry = iter.next();
                if (entry.getValue().checkAndClose())
                {
                    iter.remove();
                }
            }
        }
    }
    

    2.2.5.定时器任务,用于定时清除空闲的数据源 ClearIdleTimerTask

    /**
     * 清除空闲连接任务。
     *
     * @author jiangs
     * @version 2020年12月17日
     */
    public class ClearIdleTimerTask extends TimerTask {
        @Override
        public void run() {
            DataSourceHolder.instance().clearIdleDataSource();
        }
    }
    

    2.3.管理项目编码名称的映射关系DataSourceByMapNameConfig

    先上代码,这个放在第三节来解释

    @Component
    @ConfigurationProperties(prefix = "data")
    @Data
    public class DataSourceByMapNameConfig {
        private Map<String,String> map = Maps.newHashMap();
        private String name;
        public Map getMap() {
            return map;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    

    这是yml的数据源配置,需要动态的地方用通配符%s进行替换
    在这里插入图片描述
    然后在DynamicDataSource 这个类的
    在这里插入图片描述
    进行通配符替换,有几个通配符里面就写几个参数,实在不懂的可以留言告诉我,秒回复的哦~

    3. spring boot 读取yml中的map即键值对

    这章节对于这个yml获取来进行详解,首先,在yml文件里面写上自己的动态数据

    在这里插入图片描述
    data是定义名字,在config的注解里面会进行匹配,map是集合的key值,下面是集合的value值,因为我的项目需要所以我得value值是一个map,也可以根据自己的需求来添加。
    注意:
    key和value值中间隔得是空格而不是Tab,就因为这个今天折磨我了两个小时。
    上面代码里面的注解@Data是lombok的一个插件,可以自行下载这里不详解,不需要可不加。
    @ConfigurationProperties这个注解里面的参数对应的是yml的data。

    我的调用是在controller里面直接调用传值进动态数据源的
    在这里插入图片描述

    在这里插入图片描述

    4. 总结

    这个鬼东西吧,说难也难,说简单也简单,就是看个人对底层的理解,反正我是觉得挺难得,由于时间的关系,我写的很仓促,又看不懂的可以私下问我,留言什么的都可以,主要是秒回!!!,谢谢大家的观看,每天做好笔记是对自己提升的重要阶段,写这个也是为了以后查看方便和对自己的理解加深。😁

    参考文档:https://www.jb51.net/article/135688.htm

    展开全文
  • MyBatis多数据源配置 环境 springboot 2.3.1 Mybatis 2.1.3 MariaDB 10.4.13 IDEA 2020.1 jdk 1.8 方法 MyBatis配置多数据源可以用分包的方法,将不同的.xml文件放在不同的包中,关闭主启动类自动扫描,配置多个数据...

    MyBatis多数据源配置

    环境

    springboot 2.3.1

    Mybatis 2.1.3

    MariaDB 10.4.13

    IDEA 2020.1

    方法

    MyBatis配置多数据源可以用分包的方法,将不同的.xml文件放在不同的包中,关闭主启动类自动扫描,配置多个数据源配置文件分别扫描对应的包,使不同的数据源使用对应的方法。

    ###项目结构

    mapper文件夹中分包对应两个测试数据源,mapper.xml文件放在了资源目录下。mapper.xml也可以放在对应的包路径下,需要指定资源路径。

    pom.xml

    需要添加数据库依赖和Mybatis依赖。Maven 在运行时会忽略包下的 xml文件,将xml文件放在包目录下时需要指定资源文件位置

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.hozuo</groupId>
        <artifactId>test</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>testMybatisDatasource</name>
        <description>测试mybatis多数据源配置</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- 数据库驱动依赖 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.19</version>
            </dependency>
    
            <!-- MyBatis依赖 -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
            <!-- 重新指明资源文件位置,资源文件放在包目录下需要 -->
    <!--        <resources>-->
    <!--            <resource>-->
    <!--                <directory>src/main/java</directory>-->
    <!--                <includes>-->
    <!--                    <include>**/*.xml</include>-->
    <!--                </includes>-->
    <!--            </resource>-->
    <!--            <resource>-->
    <!--                <directory>src/main/resources</directory>-->
    <!--            </resource>-->
    <!--        </resources>-->
        </build>
    </project>
    

    application.yml

    数据源配置test1和test2,对应两个测试数据源

    server:
      port: 2020
    
    spring:
      datasource:
        test1:
          jdbc-url: jdbc:mysql://localhost:3306/testD1?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8
          driverClassName: com.mysql.cj.jdbc.Driver
          username: root
          password: root
        test2:
          jdbc-url: jdbc:mysql://localhost:3306/testD2?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8
          driverClassName: com.mysql.cj.jdbc.Driver
          username: root
          password: root
    
    mybatis:
      mapper-locations: classpath*:/mapper/**.xml
    

    数据库地址要使用jdbc-url,使用url会报如下错误

    ### Error querying database.  Cause: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
    ### The error may exist in com/hozuo/testMybatisDatasource/mapper/testD1/TestD1Mapper.xml
    ### The error may involve com.hozuo.testMybatisDatasource.mapper.testD1.TestD1Mapper.select
    ### The error occurred while executing a query
    ### Cause: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.] with root cause
    

    DataSourceConfig1.java

    注意设置mybatis的.xml所在位置

    package com.hozuo.testMybatisDatasource.config;
    
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    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;
    import org.springframework.context.annotation.Primary;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    
    /**
     * DataSourceConfig1
     * <p>2020/6/23
     * <p>主数据源配置
     * @author hozuo
     * @version 1.0
     */
    @Configuration
    @MapperScan(value  = "com.hozuo.testMybatisDatasource.mapper.testD1", sqlSessionFactoryRef = "test1SqlSessionFactory")
    public class DataSourceConfig1 {
    
        // 表示这个数据源是默认数据源
        @Primary
        @Bean(name = "test1DataSource")
        // 读取application.properties中的配置参数映射成为一个对象
        // prefix表示yml配置文件中参数的前缀
        @ConfigurationProperties(prefix = "spring.datasource.test1")
        public DataSource getDateSource1() {
            return DataSourceBuilder.create().build();
        }
    
        @Primary
        @Bean(name = "test1SqlSessionFactory")
        // @Qualifier表示查找Spring容器中名字为test1DataSource的对象
        public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource)
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(datasource);
            bean.setMapperLocations(
                    // 设置mybatis的.xml所在位置
                    new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/testD1/*.xml"));
            return bean.getObject();
        }
    
        @Primary
        @Bean("test1SqlSessionTemplate")
        public SqlSessionTemplate test1SqlSessionTemplate(
                @Qualifier("test1SqlSessionFactory") SqlSessionFactory sessionFactory) {
            return new SqlSessionTemplate(sessionFactory);
        }
    }
    

    DataSourceConfig2.java

    package com.hozuo.testMybatisDatasource.config;
    
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    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;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    
    /**
     * DataSourceConfig2
     * <p>2020/6/23
     * <p>从数据源配置
     * @author hozuo
     * @version 1.0
     */
    @Configuration
    @MapperScan(value  = "com.hozuo.testMybatisDatasource.mapper.testD2", sqlSessionFactoryRef = "test2SqlSessionFactory")
    public class DataSourceConfig2 {
    
        @Bean(name = "test2DataSource")
        @ConfigurationProperties(prefix = "spring.datasource.test2")
        public DataSource getDateSource2() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "test2SqlSessionFactory")
        public SqlSessionFactory test2SqlSessionFactory(@Qualifier("test2DataSource") DataSource datasource)
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(datasource);
            bean.setMapperLocations(
                    new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/testD2/*.xml"));
            return bean.getObject();
        }
    
        @Bean("test2SqlSessionTemplate")
        public SqlSessionTemplate test2SqlSessionTemplate(
                @Qualifier("test2SqlSessionFactory") SqlSessionFactory sessionFactory) {
            return new SqlSessionTemplate(sessionFactory);
        }
    }
    

    User.java

    package com.hozuo.testMybatisDatasource.pojo;
    
    import java.io.Serializable;
    import java.util.Objects;
    
    /**
     * User
     * <p>2020/6/23
     * <p>测试实体,模拟一个用户
     * @author hozuo
     * @version 1.0
     */
    public class User implements Serializable {
        private Integer id;
        private String username;
        private String password;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            User user = (User) o;
            return Objects.equals(id, user.id) &&
                    Objects.equals(username, user.username) &&
                    Objects.equals(password, user.password);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(id, username, password);
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    

    testD1Mapper.xml

    命名空间要对应

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.hozuo.testMybatisDatasource.mapper.testD1.TestD1Mapper">
    
        <select id="select" resultType="com.hozuo.testMybatisDatasource.pojo.User">
            SELECT * FROM user
        </select>
    </mapper>
    

    testD2Mapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.hozuo.testMybatisDatasource.mapper.testD2.TestD2Mapper">
    
        <select id="select" resultType="com.hozuo.testMybatisDatasource.pojo.User">
            SELECT * FROM user
        </select>
    </mapper>
    

    TestD1Mapper.java

    package com.hozuo.testMybatisDatasource.mapper.testD1;
    
    import com.hozuo.testMybatisDatasource.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    @Mapper
    public interface TestD1Mapper {
        List<User> select ();
    }
    

    TestD2Mapper.java

    package com.hozuo.testMybatisDatasource.mapper.testD2;
    
    import com.hozuo.testMybatisDatasource.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    @Mapper
    public interface TestD2Mapper {
        List<User> select ();
    }
    

    TestD1Service.java

    package com.hozuo.testMybatisDatasource.service;
    
    import com.hozuo.testMybatisDatasource.mapper.testD1.TestD1Mapper;
    import com.hozuo.testMybatisDatasource.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.List;
    
    /**
     * TestD1Service
     * <p>2020/6/23
     * @author hozuo
     * @version 1.0
     */
    public interface TestD1Service {
        List<User> select ();
    }
    

    TestD2Service.java

    package com.hozuo.testMybatisDatasource.service;
    
    import com.hozuo.testMybatisDatasource.pojo.User;
    
    import java.util.List;
    
    /**
     * TestD2Service
     * <p>2020/6/23
     * @author hozuo
     * @version 1.0
     */
    public interface TestD2Service {
        List<User> select ();
    }
    

    TestD1ServiceImpl.java

    package com.hozuo.testMybatisDatasource.service.impl;
    
    import com.hozuo.testMybatisDatasource.mapper.testD1.TestD1Mapper;
    import com.hozuo.testMybatisDatasource.pojo.User;
    import com.hozuo.testMybatisDatasource.service.TestD1Service;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * TestD1ServiceImpl
     * 2020/6/23
     *
     * @author hozuo
     * @version 1.0
     */
    @Service
    public class TestD1ServiceImpl implements TestD1Service {
    
        private TestD1Mapper testD1Mapper;
    
        @Autowired
        public void setTestD1Mapper(TestD1Mapper testD1Mapper) {
            this.testD1Mapper = testD1Mapper;
        }
    
        @Override
        public List<User> select() {
            return testD1Mapper.select();
        }
    }
    

    TestD2ServiceImpl.java

    package com.hozuo.testMybatisDatasource.service.impl;
    
    import com.hozuo.testMybatisDatasource.mapper.testD2.TestD2Mapper;
    import com.hozuo.testMybatisDatasource.pojo.User;
    import com.hozuo.testMybatisDatasource.service.TestD2Service;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * TestD1ServiceImpl
     * 2020/6/23
     *
     * @author hozuo
     * @version 1.0
     */
    @Service
    public class TestD2ServiceImpl implements TestD2Service {
    
        private TestD2Mapper testD2Mapper;
    
        @Autowired
        public void setTestD2Mapper(TestD2Mapper testD2Mapper) {
            this.testD2Mapper = testD2Mapper;
        }
    
        @Override
        public List<User> select() {
            return testD2Mapper.select();
        }
    }
    

    TestD2ServiceImpl.java

    package com.hozuo.testMybatisDatasource.service.impl;
    
    import com.hozuo.testMybatisDatasource.mapper.testD2.TestD2Mapper;
    import com.hozuo.testMybatisDatasource.pojo.User;
    import com.hozuo.testMybatisDatasource.service.TestD2Service;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * TestD2ServiceImpl
     * 2020/6/23
     *
     * @author hozuo
     * @version 1.0
     */
    @Service
    public class TestD2ServiceImpl implements TestD2Service {
    
        private TestD2Mapper testD2Mapper;
    
        @Autowired
        public void setTestD2Mapper(TestD2Mapper testD2Mapper) {
            this.testD2Mapper = testD2Mapper;
        }
    
        @Override
        public List<User> select() {
            return testD2Mapper.select();
        }
    }
    

    TestController.java

    package com.hozuo.testMybatisDatasource.controller;
    
    import com.hozuo.testMybatisDatasource.pojo.User;
    import com.hozuo.testMybatisDatasource.service.TestD1Service;
    import com.hozuo.testMybatisDatasource.service.TestD2Service;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    /**
     * TestController
     * <p>2020/6/23
     * <p> 测试控制器
     * @author hozuo
     * @version 1.0
     */
    @RestController
    public class TestController {
    
        private TestD1Service testD1Service;
    
        private TestD2Service testD2Service;
    
        @Autowired
        public void setTestD1Service(TestD1Service testD1Service) {
            this.testD1Service = testD1Service;
        }
    
        @Autowired
        public void setTestD2Service(TestD2Service testD2Service) {
            this.testD2Service = testD2Service;
        }
    
        @RequestMapping("/d1")
        public List<User> testD1() {
            return testD1Service.select();
        }
    
        @RequestMapping("/d2")
        public List<User> testD2() {
            return testD2Service.select();
        }
    }
    

    Application.java 主启动类

    package com.hozuo.testMybatisDatasource;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    
    /**
     * TestMybatisDatasourceApplication
     * <p>2020/6/23
     * <p>关闭mapper自动扫描
     * @author hozuo
     * @version 1.0
     */
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    public class TestMybatisDatasourceApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(TestMybatisDatasourceApplication.class, args);
       }
    
    }
    

    数据库

    image-20200623141345479

    测试结果

    image-20200623141437048

    image-20200623141453224

    源码地址

    https://github.com/hozuo/TestMybatisDatasource

    参考资料

    https://blog.csdn.net/tuesdayma/article/details/81081666

    https://www.hangge.com/blog/cache/detail_2539.html

    展开全文
  • 如何通过Spring Boot配置动态数据源访问多数据库

    万次阅读 多人点赞 2018-03-18 14:59:52
    之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不且固定的情况。...
  • springboot项目已经创建好了,并且正常访问数据库。 项目架构:springboot+mybatis ...2、在数据源配置类中DataSource的bean中设置两个属性:filters、connectionProperties。 public DataSource getDevDataSource...
  • 官方说明:Spring Boot 2.X 版本不再支持配置继承,多数据源的话每个数据源...下面以一个项目中连接两个不同数据库实例中来演示MyBatis 配置多数据源。 11.1 POM文件配置 添加POM依赖。 <?xml version="1.0"?>...
  • String url = "jdbc:postgresql://数据库地址/库"; String name = "org.postgresql.Driver"; String user = "postgres"; String password = "Aa123456"; Connection conn = null; try { Class.forName(name);...
  • 主要介绍了通过Spring Boot配置动态数据源访问多数据库的实现代码,需要的朋友可以参考下
  • 数据源访问数据库时出现的错误

    千次阅读 2017-06-08 11:18:56
    使用数据源访问数据库时常出现的一I些错误: 1.不能加载类驱动 解决方案:通过数据源访问数据库数据源由tomcat创建,应该把JDBC驱动程序的JAR 文件复制到tomcat的lib目录下。 2.由于连接路径为null,不能创建...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,947
精华内容 778
关键字:

多数据源数据库访问