SpringBoot实现多数据源(启动时不加载数据库)

1.前言

本篇文章主要是完成springboot整合多数据源,一般都用来解决那些比较复杂需要连接不同的数据库来支持业务,可以做到不同的接口连接不同的数据库来请求数据,在配置方面不同于其他人的文章在application.yml文件里面配置两三个数据库连接,当然这种只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力,当你有很多数据库需要连接时,这篇文章就为你提供了解决方法。

2.项目结构

在这里插入图片描述
这里主要的功能实现是在config文件里面

3.application.yml

yml配置文件针对数据库连接的配置贴出来。
这里就不复制pom.xml 因为不需要另类的jar。
%s是占位符
第一个%s代替ip地址 第二个%s代替数据库名字,密码如实填写

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    druid: 
      url: jdbc:mysql://%s:3306/%s?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
      username: root
      #password: ********
      password: ********

4.详细的代码实现

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

package com.example.demo.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;


/**
 * Created by Administrator on 2019/8/5.
 */
@Configuration
//"com.example.demo.mapper"  这里写mapper的地址
@MapperScan(basePackages="com.example.demo.mapper", value="sqlSessionFactory")
public class DataSourceConfig {
    /**
     * 根据配置参数创建数据源
     *
     * @return 数据源
     */
    @Bean(name="dataSource")
    //prefix  这里写yml文件里面的spring.datasource.druid
    @ConfigurationProperties(prefix="spring.datasource.druid")
    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、定义动态数据源**

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

package com.example.demo.config;

/**
 * Created by Administrator on 2019/8/5.
 * 首先增加一个数据库标识类,用于区分不同的数据库访问。
 *由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。
 * 而微服务支持多线程并发的,采用线程变量。
 */
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);
    }
}

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

package com.example.demo.config;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;


/**
 * Created by Administrator on 2019/8/5.
 */

public class DynamicDataSource extends DataSource {
    //private static Logger log = LogManager.getLogger(DynamicDataSource.class);

    /**
     * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。
     */
    @Override
    public Connection getConnection() {
        String projectCode = DBIdentifier.getProjectCode();
        System.out.println(projectCode+"projectCode");
        //1、获取数据源
        DataSource dds = DDSHolder.instance().getDDS(projectCode);
        System.out.println(dds+"dds");
        //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 username=this.getUsername();
        String password= this.getPassword();
        System.out.println("urlFormat"+urlFormat);
        System.out.println("getUsername"+username);
        System.out.println("getPassword"+password);
        String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),
                ProjectDBMgr.instance().getDBName(projectCode));
        System.out.println("url"+url);
        dds.setUrl(url);
        dds.setUsername(ProjectDBMgr.instance().getUsername(projectCode));
        dds.setPassword(ProjectDBMgr.instance().getPassword(projectCode));
        return dds;
    }
}

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

package com.example.demo.config;
import org.apache.tomcat.jdbc.pool.DataSource;
/**
 * Created by Administrator on 2019/8/5.
 */
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.example.demo.config;
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;
/**
 * Created by Administrator on 2019/8/5.
 */
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, 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<Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator();
        for (; 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.example.demo.config;
import java.util.TimerTask;
/**
 * 清除空闲连接任务。
 *
 * @author wu
 * @version 2019年8月5日
 */
public class ClearIdleTimerTask extends TimerTask {
    @Override
    public void run() {
        DDSHolder.instance().clearIdleDDS();
    }
}

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

package com.example.demo.config;    
import com.example.demo.util.DBData;    
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;    
/**
 *  * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。
 *  * @author wu
 *  * @version 2 2019/8/5.
 *  格式化  ctrl+ALT+;
 */
public class ProjectDBMgr {
    /**
     *  * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;
     *  * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。
     *  
     */
    private Map<String, DBData> depDBMap = new HashMap<>();
    //private Map<String, String> dbNameMap = new HashMap<String, String>();
    /**
     *  * 保存项目编码与数据库IP的映射关系。
     *  
     */
    //private Map<String, String> dbIPMap = new HashMap<String, String>();

    private ProjectDBMgr() {
    //注意你的项目用到的数据库不同,如果表结果也不同的话 那你得写不同的bean  
        depDBMap.put("5642",new DBData("5642","青和居西区","10.10.10.10","3306","数据库名字","账号","密码");
        depDBMap.put("5455",new DBData("5455","八大家花西园","10.10.10.10","3306","数据库名字","账号","密码"));
        depDBMap.put("5643",new DBData("5643","青和居西区","10.10.10.10","3306","数据库名字","账号","密码"));
        depDBMap.put("5454",new DBData("5454","八大家花东园","10.10.10.10","3306","数据库名字","账号","密码"));
        .
        .
        .
        
    }

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

    // 实际开发中改为从缓存获取
    public String getDBName(String projectCode) {
        if (depDBMap.containsKey(projectCode)) {
            return depDBMap.get(projectCode).getDbName();
        }
        return "";
    }

    //实际开发中改为从缓存中获取
    public String getDBIP(String projectCode) {
        if (depDBMap.containsKey(projectCode)) {
            return depDBMap.get(projectCode).getIp();
        }
        return "";
    }
    //动态获取密码
    public String getPassword(String projectCode) {
        if (depDBMap.containsKey(projectCode)) {
            return depDBMap.get(projectCode).getPassword();
        }
        return "";
    }
    //动态获取username
    public String getUsername(String projectCode) {
        if (depDBMap.containsKey(projectCode)) {
            return depDBMap.get(projectCode).getUser();
        }
        return "";
    }

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

7.编写数据库访问的mapper(PersonSnapshotHistoryMapper .java)

package com.example.demo.mapper;    
import com.example.demo.bean.PersonSnapshotHistory;
import com.example.demo.bean.PersonSnapshotHistoryExample;
import java.util.List;   
import org.apache.ibatis.annotations.*;
import org.springframework.data.jpa.repository.Query; 
@Mapper
public interface PersonSnapshotHistoryMapper {
    @Select(value = "SELECT uuid,cardid,cardno FROM PERSON_SNAPSHOT_HISTORY ORDER BY createdAt DESC LIMIT 0,3")
    List<PersonSnapshotHistory> selectThreeSnapshot();      
}

8.定义查询对象模型(PersonSnapshotHistory .java)

package com.example.demo.bean;

public class PersonSnapshotHistory {
private String uuid;
private Integer cardid;
private String cardno;
…get set省略
}

9.定义查询数据的restful接口(PersonSnapshotHistoryController .java)

package com.example.demo.controller;

import com.example.demo.bean.PersonSnapshotHistory;
import com.example.demo.config.DBIdentifier;
import com.example.demo.mapper.PersonSnapshotHistoryMapper;
import com.example.demo.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * Created by Administrator on 2019/8/5.
 */

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*", maxAge = 3600)
public class PersonSnapshotHistoryController {
    @Autowired
    PersonSnapshotHistoryMapper  personSnapshotHistoryMapper;
    /**
     * 显示前三条数据
     * @return
     */
    @RequestMapping("threeSnapshot")
    public R selectThreeSnapshot(){
    //不同的接口需要请求哪个数据库数据  直接填定义好的数据库编号
        DBIdentifier.setProjectCode("5642");
        List<PersonSnapshotHistory> list=personSnapshotHistoryMapper.selectThreeSnapshot();
        return R.ok("").put("list",list);
    }
    @RequestMapping("threeSnapshot1")
    public R selectThreeSnapshot1(){
        DBIdentifier.setProjectCode("5455");
        List<PersonSnapshotHistory> list=personSnapshotHistoryMapper.selectThreeSnapshot();
        DBIdentifier.setProjectCode("5642");
        List<PersonSnapshotHistory> list1=personSnapshotHistoryMapper.selectThreeSnapshot();
        list.addAll(list1);
        return R.ok("").put("list",list);
    }
}

5.贴出整个项目

https://download.csdn.net/download/weixin_42694286/11688084

6. 启动类配置重点

这样启动springboot项目的时候就不会去连接数据库 ,不然会启动报错

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class,JpaRepositoriesAutoConfiguration.class})
@MapperScan(basePackages = {"com.example.demo.*","com.example.demo.mapper"})
public class SmartCommunityBootApplication {

	public static void main(String[] args) {
		//DBIdentifier.setProjectCode("5642");
		SpringApplication.run(SmartCommunityBootApplication.class, args);    
	}    
}
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test713?serverTimezone=GMT username: XFour password: xuqingyuan753 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalStateException: Unable to create XADataSource instance from 'com.mysql.jdbc.jdbc2.optional.MysqlXADataSource' at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1128) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1022) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:512) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:296) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:754) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866) ~[spring-context-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) ~[spring-context-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:371) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1186) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1175) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE] at com.xfour.AppRun.main(AppRun.java:16) [main/:na] Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalStateException: Unable to create XADataSource instance from 'com.mysql.jdbc.jdbc2.optional.MysqlXADataSource' at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] ... 20 common frames omitted Caused by: java.lang.IllegalStateException: Unable to create XADataSource instance from 'com.mysql.jdbc.jdbc2.optional.MysqlXADataSource' at org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration.createXaDataSourceInstance(XADataSourceAutoConfiguration.java:103) ~[spring-boot-autoconfigure-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration.createXaDataSource(XADataSourceAutoConfiguration.java:90) ~[spring-boot-autoconfigure-1.4.2.RELEASE.jar:1.4.2.RELEASE] at org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration.dataSource(XADataSourceAutoConfiguration.java:72) ~[spring-boot-autoconfigure-1.4.2.RELEASE.jar:1.4.2.RELEASE] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_112] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_112] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_112] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162) ~[spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE] ... 21 common frames omitted
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页