springboot 多数据源配置和使用

一、背景介绍

       公司做项目,稍微大一点的项目可能会存在数据源来源不同问题,这里说的数据源来源指的是数据库源,比如一个项目既要从mysql数据库中取数据,又要从Oracle中取数据,或者从mysql数据库下的两个数据库中取数据比如test01,test02数据库,此时该如何处理,正常情况我们都只是一个数据源来源,所以配置一个默认的即可,而两个数据来源的话可能就要从数据库的DataSource类入手了,目前我使用的都是springboot项目,所以下面将的都是在springboot环境下作的操作,如果不理解springboot的可以先去学习一下。整篇文章参考:https://www.cnblogs.com/java-zhao/p/5413845.html  但做了很多修改为了能让读者读懂,这里我暂且认为是我的原创文章。

二、核心思想

      多数据源也就是配置多个DataSource类型,比如你有两个数据源来源,可以定义type:test01,test02(一般和数据库名相同),项目在使用数据库的时候需要提前将你要使用哪种类型的DataSource告知给系统(这个系统指的是采用的数据库查询框架),系统获取到类型,选择使用哪个DataSource。这里的问题在于:1、如何告知系统我此时需要这个数据源,而不是其他的数据源。2、假如系统已经知道我要使用哪种数据源了,此时系统需要从一个数据源集合中取出该数据源使用,但系统的这个数据源集合我怎么给系统,让系统知道我就是用这些数据源。这两个问题解决了也就基本完事了,下面就来分析解决一下。

三、功能分析和实现

     1、如何告知系统我此时需要这个数据源,而不是其他的数据源,先来分析一下获取DataSource的源码,这个是AbstractRoutingDataSource类里面的方法,看下面

相信读者已经看明白了此时我们要继承AbstractRoutingDataSource,实现determineCurrentLookupKey这个方法,将DataSource的类型传过去。下面是自定义代码:

package com.more.datasource.more_datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源(需要继承AbstractRoutingDataSource)
 * 在使用查询数据库前需要先设置 dataSourceType;
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static String dataSourceType;

    /**
     * 返回数据源类型,让系统知道我用的是哪个数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceType;
    }

    /**
     * 设置数据源类型,在使用数据库之前自己调用该方法
     * @param type
     */
    public static void setDataSourceType(String type){
        dataSourceType = type;
    }
}

数据源类型我们这里单独定义一个常量类,存储数据源类型:

package com.more.datasource.more_datasource;

/**
 * 此类存储是不同数据库来源对应的type ,例:datashare数据库对应type为DATA_SHARE;
 */
public interface DatasourceType {
    /**
     * 数据库为datashare 的type
     */
    public static String DATA_SHARE = "datashare";

    /**
     * 数据库为test 的type
     */
    public static String TEST = "test";
}

2、系统到此时已经知道我们想要什么数据源了,这时系统要从数据源集合中利用type取出数据源(上面源码已经有标识),现在解决给系统设置数据源集合。看一下源码,还是AbstractRoutingDataSource类的

//设置数据源集合类,参数是map集合,key就是我们定义的数据源类型,value就是数据源 
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }

//这个是设置默认数据源
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }

此时问题已基本解决,下面贴一下代码

2.1 数据源配置文件配置 application.properties:

#the first datasource
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.100.3:3306/datashare?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=admin

#the second datasource
jdbc2.driverClassName=com.mysql.jdbc.Driver
jdbc2.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
jdbc2.username=root
jdbc2.password=root

2.2  自定义config类,让springboot启动加载时提前将数据源等配置加载到系统中

package com.more.datasource.more_datasource;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;


@Configuration // 该注解类似于spring配置文件
public class MyBatisConfig {

    @Autowired
    private Environment env;

    /**
     * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
     */
    @Bean
    public DataSource myTestDbDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("jdbc.driverClassName"));
        props.put("url", env.getProperty("jdbc.url"));
        props.put("username", env.getProperty("jdbc.username"));
        props.put("password", env.getProperty("jdbc.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    @Bean
    public DataSource myTestDb2DataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("jdbc2.driverClassName"));
        props.put("url", env.getProperty("jdbc2.url"));
        props.put("username", env.getProperty("jdbc2.username"));
        props.put("password", env.getProperty("jdbc2.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("myTestDbDataSource") DataSource myTestDbDataSource,
                                        @Qualifier("myTestDb2DataSource") DataSource myTestDb2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatasourceType.DATA_SHARE, myTestDbDataSource);
        targetDataSources.put(DatasourceType.TEST, myTestDb2DataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(myTestDbDataSource);// 默认的datasource设置为myTestDbDataSource

        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("myTestDbDataSource") DataSource myTestDbDataSource,
                                               @Qualifier("myTestDb2DataSource") DataSource myTestDb2DataSource) throws Exception{
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(this.dataSource(myTestDbDataSource, myTestDb2DataSource));
        // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
//        fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));
//        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
        return fb.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

}

三、测试

实体类的创建shop实体

package com.more.datasource.more_datasource.model;

import java.io.Serializable;

/**
 * @author WYH
 */
public class Shop implements Serializable {

    private String id;

    private String shopName;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getShopName() {
        return shopName;
    }

    public void setShopName(String shopName) {
        this.shopName = shopName;
    }

    @Override
    public String toString() {
        return "Shop{" +
                "id='" + id + '\'' +
                ", shopName='" + shopName + '\'' +
                '}';
    }
}

实体类的创建DxTest实体

package com.more.datasource.more_datasource.model;

import java.io.Serializable;

/**
 * @author WYH
 */
public class DxTest implements Serializable {

    private String id;

    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
mapper接口类创建,这里采用的是注解方式查询数据库,没有使用xml的方式
package com.more.datasource.more_datasource.mapper;

import com.more.datasource.more_datasource.model.DxTest;
import com.more.datasource.more_datasource.model.Shop;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

@Mapper
@Repository("mapper")
public interface ShopMapper {

    @Select("SELECT * FROM student WHERE id = #{id}")
    @Results(value = { @Result(id = true, column = "id", property = "id"),
            @Result(column = "name", property = "shopName") })
    public Shop getShop(@Param("id") int id);


    @Select("SELECT * FROM t_data_field WHERE id = #{id}")
    @Results(value = { @Result(id = true, column = "id", property = "id"),
            @Result(column = "tablename", property = "name") })
    public DxTest getDxTest(@Param("id") int id);

}

controller类的创建,这里将service,dao两层给去掉,简化了一下步骤,方便阅读

package com.more.datasource.more_datasource.controller;

import com.more.datasource.more_datasource.DatasourceType;
import com.more.datasource.more_datasource.DynamicDataSource;
import com.more.datasource.more_datasource.mapper.ShopMapper;
import com.more.datasource.more_datasource.model.DxTest;
import com.more.datasource.more_datasource.model.Shop;
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.RestController;

@RestController
@RequestMapping("/shop")
public class ShopController {

    @Autowired
    private ShopMapper mapper;

    @RequestMapping(value = "/getShop", method = RequestMethod.GET)
    public Shop getShop() {
        //查询数据前设置数据源类型
        DynamicDataSource.setDataSourceType(DatasourceType.TEST);
        return mapper.getShop(1);
    }

    @RequestMapping(value = "/getDxTest", method = RequestMethod.GET)
    public DxTest getDxTest(){
        //查询数据前设置数据源类型
        DynamicDataSource.setDataSourceType(DatasourceType.DATA_SHARE);
        return mapper.getDxTest(3238);
    }

}

到此,多数据源已经实现完毕,此案例也留着自己以后快速设计多数据源结构做准备。

猜你喜欢

转载自blog.csdn.net/qq_34297563/article/details/89633277