记录一次spring boot动态数据源配置

maven引入

<!--连接池-->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.1.8</version>
</dependency>
<!--数据库链接自定义依赖-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.16</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>

application.yml配置

具体属性根据项目需要增删

spring:
  datasource:
    druid:
      appid1:
        url: jdbc:mysql://localhost:3306/appid1?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        max-idle: 10
        max-wait: 10000
        max-active: 30
        min-idle: 1
        initial-size: 1
        validation-query: SELECT '1'
        test-on-borrow: false
        test-on-return: false
        test-while-idle: true
        time-between-eviction-runs-millis: 60000 # 注意控制连接数据库的时间,不然会断开连接
        min-evictable-idle-time-millis: 300000
        pool-prepared-statements: true
        filters: stat
      appid2:
        url: jdbc:mysql://localhost:3306/appid2?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        max-idle: 10
        max-wait: 10000
        max-active: 30
        min-idle: 1
        initial-size: 1
        validation-query: SELECT '1'
        test-on-borrow: false
        test-on-return: false
        test-while-idle: true
        time-between-eviction-runs-millis: 60000 # 注意控制连接数据库的时间,不然会断开连接
        min-evictable-idle-time-millis: 300000
        pool-prepared-statements: true
        filters: stat

创建配置文件类 BaseDataSourceProperties

此类中的属性要同步application.yml,否则会注入失败

package cn.yueworld.ray.data.datasource;

import lombok.Data;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 14:30
 */
@Data
public class BaseDataSourceProperties {

    protected String url;

    protected String username;

    protected String password;

    protected String driverClassName;

    protected int initialSize;

    protected int minIdle;

    protected int maxActive;

    protected int maxWait;

    protected int timeBetweenEvictionRunsMillis;

    protected int minEvictableIdleTimeMillis;

    protected String validationQuery;

    protected boolean testWhileIdle;

    protected boolean testOnBorrow;

    protected boolean testOnReturn;

    protected boolean poolPreparedStatements;

    protected String filters;
}

配置通用设置属性的DataSource:BaseMyBatisDataSource

根据配置文件中需要自定义DataSource属性进行设置

package cn.yueworld.ray.data.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.SQLException;

/**
 * //mybaits mapper 搜索路径
 * @Author yanzhiwei
 * @Date 2021/3/14 14:38
 */
@Component
@Slf4j
public class BaseMyBatisDataSource {

    public DataSource druidDataSource(BaseDataSourceProperties config) {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setDriverClassName(config.getDriverClassName());
        datasource.setUrl(config.getUrl());
        datasource.setUsername(config.getUsername());
        datasource.setPassword(config.getPassword());
        datasource.setInitialSize(config.getInitialSize());
        datasource.setMinIdle(config.getMinIdle());
        datasource.setMaxActive(config.getMaxActive());
        datasource.setMaxWait(config.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(config.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(config.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(config.getValidationQuery());
        datasource.setTestWhileIdle(config.isTestWhileIdle());
        datasource.setTestOnBorrow(config.isTestOnBorrow());
        datasource.setTestOnReturn(config.isTestOnReturn());
        datasource.setPoolPreparedStatements(config.isPoolPreparedStatements());
        try {
            datasource.setFilters(config.getFilters());
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        return datasource;
    }
}

使用 ThreadLocal 保存 DataSources 数据源的key

package cn.yueworld.ray.data.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 使用 ThreadLocal 保存 DataSources 数据源的key
 * @Author yanzhiwei
 * @Date 2021/3/14 14:41
 */
public class DataSourceContextHolder {

    public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);

    /**
     * 默认数据源appid1
     */
    public static final String DS_APPID1 = "appid1";
    /**
     * appid2数据源
     */
    public static final String DS_APPID2 = "appid2";

    /**
     * 本地线程
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    static {
        contextHolder.set(DS_APPID1);
    }

    // 设置数据源名
    public static void setDB(String dbType) {
        log.debug("切换到{}数据源", dbType);
        contextHolder.set(dbType);
    }

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

    // 清除数据源名
    public static void clearDB() {
        contextHolder.remove();
    }

    /**
     * 判断当前数据源key是否存在
     * @param key
     * @return
     */
    public static boolean isDataSourceKey (String key) {
        String [] keys = {DS_APPID1, DS_APPID2};
        for (int i = 0 ; i< keys.length;i ++) {
            if (keys[i].equals(key)) {
                return  true;
            }
        }
        return  false;
    }
}

注册多数据源

package cn.yueworld.ray.data.datasource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 注册多数据源
 * @Author yanzhiwei
 * @Date 2021/3/14 14:31
 */
@Configuration
public class DataSourcePropertiesFactory {

    private static final String dataSourceAppid1 = "spring.datasource.druid.appid1";

    private static final String dataSourceAppid2 = "spring.datasource.druid.appid2";

    @Bean("appid1")
    @ConfigurationProperties(prefix = dataSourceAppid1, ignoreUnknownFields = false, ignoreInvalidFields = true)
    public BaseDataSourceProperties getAppid1DataSourceInstance(){
        BaseDataSourceProperties mp = new BaseDataSourceProperties();
        return mp;
    }


    @Bean("appid2")
    @ConfigurationProperties(prefix = dataSourceAppid2, ignoreUnknownFields = false, ignoreInvalidFields = true)
    public BaseDataSourceProperties getAppid2SourceInstance(){
        BaseDataSourceProperties mp = new BaseDataSourceProperties();
        return mp;
    }

}

继承AbstractRoutingDataSource

package cn.yueworld.ray.data.datasource;

/**
 * AbstractRoutingDataSource 是由spring提供的类 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上,
 * 不用每个数据源都去创建一次sqlSessionFactory。
 * sqlSessionFactory 顾名思义 ,就是用来创建session会话的工厂。如果存在多个Sessionfactory 那么Session是不是就乱套了,因此这种架构不可取。
 *
 * determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,
 * 有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
 *
 */

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 14:39
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

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

    @Override
    protected Object determineCurrentLookupKey() {
        log.debug("数据源为{}", DataSourceContextHolder.getDB());

        return DataSourceContextHolder.getDB();
    }
}

由于业务需要,配置通用创建人,创建时间,更新人,更新时间的设置

package cn.yueworld.ray.data.datasource;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * @Author yanzhiwei
 * @Date 2021/3/15 9:07
 */
@Component
public class MybatisObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        LocalDateTime now = LocalDateTime.now();
        setFieldValByName("createdDate", now, metaObject);
        setFieldValByName("createdName", now + "createdName", metaObject);

        setFieldValByName("updatedDate", now, metaObject);
        setFieldValByName("updatedName", now + "updatedName", metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        LocalDateTime now = LocalDateTime.now();
        setFieldValByName("updatedDate", now, metaObject);
        setFieldValByName("updatedName", now + "updatedName", metaObject);
    }
}

配置类 CustomMybatisDataSource

package cn.yueworld.ray.data.datasource;

import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * MapperScan ---> mybaits mapper 搜索路径
 * @Author yanzhiwei
 * @Date 2021/3/14 14:44
 */
@Configuration
@MapperScan(basePackages = {"cn.yueworld.ray.data.dao"}, sqlSessionFactoryRef = "sqlSessionFactory1")
@EnableTransactionManagement
public class CustomMybatisDataSource extends BaseMyBatisDataSource {

    @Autowired
    @Qualifier("appid1")
    private BaseDataSourceProperties appid1;

    @Autowired
    @Qualifier("appid2")
    private BaseDataSourceProperties appid2;

    /**
     * 动态数据源配置
     * @return
     */
    @Bean
    public DataSource getDataSource() {
        DataSource appid1DataSource = super.druidDataSource(appid1);
        DataSource appid2DataSource = super.druidDataSource(appid2);

        // 此类继承了spring的 AbstractRoutingDataSource 类 代替原有的
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(appid1DataSource);

        // 配置多数据源
        Map<Object, Object> dsMap = new ConcurrentHashMap<>(5);
        dsMap.put(DataSourceContextHolder.DS_APPID1, appid1DataSource);
        dsMap.put(DataSourceContextHolder.DS_APPID2, appid2DataSource);

        dynamicDataSource.setTargetDataSources(dsMap);

        return dynamicDataSource;

    }


    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(getDataSource());
    }

    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory1() throws Exception {
        // 使用MybatisSqlSessionFactoryBean
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        // 使用数据源, 连接库
        factoryBean.setDataSource(getDataSource());
        // 下面一步很重要,配置mapper。xml的扫描位置
        factoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().
                        getResources("classpath*:mybatis/mappers/*.xml"));
        // 配置扫描包的位置
        factoryBean.setTypeAliasesPackage("cn.yueworld.ray.data.dao");

        // mybatis-plus注解实现:@TableField(fill = FieldFill.INSERT)
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setMetaObjectHandler(new MybatisObjectHandler());
        factoryBean.setGlobalConfig(globalConfig);

        // 重新注入分页插件
        factoryBean.setPlugins(new Interceptor[]{paginationInterceptor()});
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
        // 使用上面配置的Factory
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1());
        return template;
    }

}

AOP切面进行动态切换数据源

package cn.yueworld.ray.data.datasource;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 14:52
 */
@Aspect
@Component
@Slf4j
public class DynamicDataSourceAop {

    @Pointcut("execution(* cn.yueworld.ray.data.dao.*.*(..))")
    public void aPointcut() {
    }

    @Around("aPointcut()")
    public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {

        Map<String, Object> map = new HashMap();
//        map = getFieldsName(joinPoint);
        //参数名称
        String[] names = ((CodeSignature) joinPoint.getSignature()).getParameterNames();

        Object[] args = joinPoint.getArgs();

        String appid = DataSourceContextHolder.DS_APPID1;

        assert names != null;
        for (int i = 0; i < names.length; i++) {
            if (StringUtils.equals(names[i], "entity")) {
                Class<?> type = args[i].getClass();
                Field field = null;
                Object res = null;

                field = type.getField("appId");
                res = field.get(args[i]);
                if (res != null) {
                    appid = (String) res;
                }

            } else {
                if (StringUtils.equals(names[i], "appId") && StringUtils.isNotBlank((String) args[i])) {
                    appid = (String) args[i];
                }
            }

        }

        log.info("切换数据源至={}", appid);
        if (DataSourceContextHolder.isDataSourceKey(appid)) {
            // 切换数据源
            DataSourceContextHolder.setDB(appid);
        }else {
            throw new RuntimeException("数据源key书写错误");
        }
        return joinPoint.proceed(args);
    }

    @AfterReturning(returning = "object", pointcut = "aPointcut()")
    public void doAfterReturning(Object object) {
        log.info("切换数据源至={}",DataSourceContextHolder.DS_APPID1);
        DataSourceContextHolder.setDB(DataSourceContextHolder.DS_APPID1);

    }


    private String attNameHandle(String method, String attName) {
        StringBuilder res = new StringBuilder(method);
        // 属性只有一个字母
        if (attName.length() == 1) {
            res.append(attName.toUpperCase());
        } else {
            // 属性包含两个字母及以上
            char[] charArray = attName.toCharArray();
            if (Character.isLowerCase(charArray[0]) && Character.isLowerCase(charArray[1])) {
                res.append(Character.toUpperCase(charArray[0]));
                res.append(attName.substring(1));
            } else {
                res.append(attName);
            }
        }

        return res.toString();
    }

}

测试类

实体类

package cn.yueworld.ray.data.client.entity;

import cn.yueworld.pd.tools.mybatis.vo.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 20:40
 */
@Data
public class CustomBaseEntity {

    @TableField(exist = false)
    public String appId;
}

package cn.yueworld.ray.data.client.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 16:41
 */
@Data
@TableName("test1")
public class Test1 extends CustomBaseEntity {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdDate;

    /**
     * 创建人
     */
    @TableField(fill = FieldFill.INSERT)
    private String createdName;

    /**
     * 最后修改时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedDate;

    /**
     * 最后修改人
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updatedName;

}

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.yueworld.ray.data.dao.Test1Mapper">

    <resultMap id="BaseResultMap" type="cn.yueworld.ray.data.client.entity.Test1">
		<id column="id" jdbcType="BIGINT" property="id" />
		<result column="name" jdbcType="VARCHAR" property="name" />
		<result column="created_date" jdbcType="DATE" property="createdDate" />
		<result column="created_name" jdbcType="VARCHAR" property="createdName" />
		<result column="updated_date" jdbcType="DATE" property="updatedDate" />
		<result column="updated_name" jdbcType="VARCHAR" property="updatedName" />
	</resultMap>

    <sql id="Base_Column_List">
		 id, `name`, created_date, created_name, updated_date, updated_name
	</sql>

</mapper>

mapper接口

package cn.yueworld.ray.data.dao;

import cn.yueworld.ray.data.client.entity.Test1;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 16:41
 */
public interface Test1Mapper extends BaseMapper<Test1> {
}

service接口

package cn.yueworld.ray.data.service;

import cn.yueworld.ray.data.client.entity.Test1;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 16:45
 */
public interface Test1Service {


    int save(Test1 test1);

    int update(Test1 test1);
}

service实现类

package cn.yueworld.ray.data.service.impl;

import cn.yueworld.ray.data.client.entity.Test1;
import cn.yueworld.ray.data.dao.Test1Mapper;
import cn.yueworld.ray.data.service.Test1Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author yanzhiwei
 * @Date 2021/3/14 16:46
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Test1ServiceImpl implements Test1Service {

    private final Test1Mapper test1Mapper;

    @Override
    public int save(Test1 test1) {

        return test1Mapper.insert(test1);
    }

    @Override
    public int update(Test1 test1) {
        return test1Mapper.updateById(test1);
    }
}

接口入口controller

package cn.yueworld.ray.data.api;

import cn.yueworld.ray.data.client.entity.Test1;
import cn.yueworld.ray.data.service.Test1Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * 应用表
 * @author Wang TianWen
 * @email wangtianwen90@163.com
 * @date 2021-02-24 10:23:45
 */
@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Test1Api {

	private final Test1Service test1Service;


	@RequestMapping(value = "save", method = RequestMethod.GET)
	public boolean save(Test1 test1) {
		int count = test1Service.save(test1);
	    return count > 0;
    }



	@RequestMapping(value = "update", method = RequestMethod.GET)
	public boolean update(Test1 test1) {
		int count = test1Service.update(test1);
	    return count > 0;
    }

}

以上内容也可以通过自动生成代码框架去生成,具体方法请自行根据需要添加

参考博客:https://blog.csdn.net/HELLO_WROLD_/article/details/90713897

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值