spring boot 动态数据源切换+通用mapper配置

最近因为项目可能要用到多数据源,所以最近研究了下动态切换数据源,在spring2.0以后增加了AbstractRoutingDataSource 来实现对数据源的操作。

对数据源进行切换 继承扩展AbstractRoutingDataSource这个抽象类 ,根据提供的键值切换对应的数据源,下面我们来看下这个类


public abstract class AbstractRoutingDataSourceextends AbstractDataSourceimplements InitializingBean {

@Nullable

private Map targetDataSources; //存放所有的数据源的map 根据key 值可获取对应的数据源

    @Nullable

private Object defaultTargetDataSource;  //默认的数据源

    private boolean lenientFallback =true;

    private DataSourceLookup dataSourceLookup =new JndiDataSourceLookup();

    @Nullable

private Map resolvedDataSources;

    @Nullable

private DataSource resolvedDefaultDataSource;

    public AbstractRoutingDataSource() {

}

public void setTargetDataSources(Map targetDataSources) {

this.targetDataSources = targetDataSources;

    }

public void setDefaultTargetDataSource(Object defaultTargetDataSource) {

this.defaultTargetDataSource = defaultTargetDataSource;

    }

public void setLenientFallback(boolean lenientFallback) {

this.lenientFallback = lenientFallback;

    }

public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {

this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup !=null?dataSourceLookup:new JndiDataSourceLookup());

    }

// 初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。方法体是将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中

public void afterPropertiesSet() {

if(this.targetDataSources ==null) {

throw new IllegalArgumentException("Property 'targetDataSources' is required");

        }else {

this.resolvedDataSources =new HashMap(this.targetDataSources.size());

            this.targetDataSources.forEach((key, value) -> {

Object lookupKey =this.resolveSpecifiedLookupKey(key);

                DataSource dataSource =this.resolveSpecifiedDataSource(value);

                this.resolvedDataSources.put(lookupKey, dataSource);

            });

            if(this.defaultTargetDataSource !=null) {

this.resolvedDefaultDataSource =this.resolveSpecifiedDataSource(this.defaultTargetDataSource);

            }

}

}

protected Object resolveSpecifiedLookupKey(Object lookupKey) {

return lookupKey;

    }

protected DataSource resolveSpecifiedDataSource(Object dataSource)throws IllegalArgumentException {

if(dataSourceinstanceof DataSource) {

return (DataSource)dataSource;

        }else if(dataSourceinstanceof String) {

return this.dataSourceLookup.getDataSource((String)dataSource);

        }else {

throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);

        }

}

// 先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。

public Connection getConnection()throws SQLException {

return this.determineTargetDataSource().getConnection();

    }

public Connection getConnection(String username, String password)throws SQLException {

return this.determineTargetDataSource().getConnection(username, password);

    }

public T unwrap(Class iface)throws SQLException {

return iface.isInstance(this)?this:this.determineTargetDataSource().unwrap(iface);

    }

public boolean isWrapperFor(Class iface)throws SQLException {

return iface.isInstance(this) ||this.determineTargetDataSource().isWrapperFor(iface);

    }

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

** Object lookupKey =this.determineCurrentLookupKey();//这里就是根据 determineCurrentLookupKey获取相应的key 下面再根据key值进行获取相应的数据源.**

        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);

        if(dataSource ==null && (this.lenientFallback || lookupKey ==null)) {

dataSource =this.resolvedDefaultDataSource;

        }

if(dataSource ==null) {

throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey +"]");

        }else {

return dataSource;

        }

}

//重写此方法就可以进行

@Nullable

protected abstract Object determineCurrentLookupKey();

}

定义DynamicDataSource 重写determineCurrentLookupKey方法

image

数据源配置:


spring:
 #数据源配置
  datasource:
    common:
      dbconfig:
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # ? 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        spring.datasource.filters: stat,wall,log4j
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    names: base,bloodsugar

base:

      type: com.alibaba.druid.pool.DruidDataSource

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

initialize: true#指定初始化数据源,是否用data.sql来初始化,默认: true

      name: cmmi

url: jdbc:mysql://192.168.2.13:3306/api?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull

username: root

password: eeesys

minIdle: 5

maxActive: 20

# 配置获取连接等待超时的时间

      maxWait: 60000

# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

      timeBetweenEvictionRunsMillis: 60000

# 配置一个连接在池中最小生存的时间,单位是毫秒

      minEvictableIdleTimeMillis: 300000

validationQuery: SELECT 1 FROM DUAL

testWhileIdle: true

testOnBorrow: false

testOnReturn: false

# 打开PSCache,并且指定每个连接上PSCache的大小

      poolPreparedStatements: true

maxPoolPreparedStatementPerConnectionSize: 20

# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙

      spring.datasource.filters: stat,wall,log4j

# 通过connectProperties属性来打开mergeSql功能;慢SQL记录

      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000


下面创建注册类,实现数据源的注册和初始化


package com.txby.common.mybatis.DataSource;

import java.sql.SQLException;

import java.util.HashMap;

import java.util.Map;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValues;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.beans.factory.support.GenericBeanDefinition;

import org.springframework.boot.bind.RelaxedDataBinder;

import org.springframework.boot.bind.RelaxedPropertyResolver;

import org.springframework.context.EnvironmentAware;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotationMetadata;

/**
 * 动态数据源注册
 * @author liqun
 * @data 2018-05-16
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
    private static final String prefix = "spring.datasource";
    // 数据源配置信息
    private PropertyValues dataSourcePropertyValues;
    // 默认数据源
    private DataSource defaultDataSource;
    // 动态数据源
    private Map<String, DataSource> dynamicDataSources = new HashMap<>();



    /**
     *  加载多数据源配置
     * @param env
     */
    @Override
    public void setEnvironment(Environment env) {
        //获取数据源配置 的节点
        Binder binder = Binder.get(env);
        Map parentMap = binder.bind(prefix, Map.class).get();
        Map common = (Map<String, Object>)((Map<String, Object>) parentMap.get("common")).get("dbconfig");
        //数据源存放的属性
        String dsPrefixs = parentMap.get("names").toString();
        //遍历生成相应的数据源并存储
        for (String dsPrefix : dsPrefixs.split(",")) {
            Map<String,Object> map = (Map<String, Object>) parentMap.get(dsPrefix);
            DataSource ds = null;
            if(map.get("minIdle") == null ){
                ds = initDataSource(map,common );
            }else {
                ds = initDataSource(map,null);
            }
            // 设置默认数据源
            if ("base".equals(dsPrefix)) {
                defaultDataSource = ds;
            } else {
                dynamicDataSources.put(dsPrefix, ds);
            }
            //初始化数据源
            dataBinder(ds, env,dsPrefix);
        }
    }

    /**
     * 初始化数据源
     * @param map
     * @return
     */
    public DataSource initDataSource(Map<String, Object> map ,Map<String, Object> commonMap ) {
        String driverClassName = map.get("driver-class-name").toString();
        String url = map.get("url").toString();
        String username = map.get("username").toString();
        String password = map.get("password").toString();
        Integer minIdle = 0 ;
        Integer maxActive =0;
        Long maxWait;
        Long minEvictableIdleTimeMillis;
        if(map.get("minIdle") == null ){
            minIdle = Integer.parseInt(commonMap.get("minIdle").toString());
            maxActive = Integer.parseInt(commonMap.get("maxActive").toString());
            maxWait = Long.parseLong(commonMap.get("maxWait").toString());
            minEvictableIdleTimeMillis =  Long.parseLong(commonMap.get("minEvictableIdleTimeMillis").toString());
        }else{
            minIdle = Integer.parseInt(map.get("minIdle").toString());
            maxActive = Integer.parseInt(map.get("maxActive").toString());
            maxWait = Long.parseLong(map.get("maxWait").toString());
            minEvictableIdleTimeMillis =  Long.parseLong(map.get("minEvictableIdleTimeMillis").toString());
        }

        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(url);
        datasource.setDriverClassName(driverClassName);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setMinIdle(minIdle);
        datasource.setMaxWait(maxWait);
        datasource.setMaxActive(maxActive);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        try {
            //开启Druid的监控统计功能 stat表示sql合并,wall表示防御SQL注入攻击
            datasource.setFilters("stat,wall");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return datasource;
    }

    /**
     * 加载数据源配置信息
     * @param dataSource
     * @param env
     */
    private void dataBinder(DataSource dataSource, Environment env,String defix) {
        Binder binder = Binder.get(env);
        binder.bind( defix,Bindable.ofInstance(dataSource));
    }

    /**
     * 注册数据源been
     * @param importingClassMetadata
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put("dataSource", defaultDataSource);
        // 添加更多数据源
        targetDataSources.putAll(dynamicDataSources);
        // 创建动态数据源DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);

    }

}

启动类中加 @Import({DynamicDataSourceRegister.class}) 把注册类注入spring 容器中

为了方便使用我们运用注解的形式


/**

* 指定使用哪个数据源的注解

* Created by liqun on 2018/5/16.

*/

@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface TargetDataSource{

Stringname();

}

拦截注解,设置相应的数据源,使用完成后清楚设置的数据源


/**

* 利用aop原理实现切换数据源

* Created by liqun on 2018/5/16.

*/

@Aspect

@Component

public class TargetDataSourceAspect {

/**

* 根据@TargetDataSource的name值设置不同的DataSource

    * @param joinPoint

    * @param targetDataSource

    */

    @Before("@annotation(targetDataSource)")

public void changeDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){

DynamicDataSource.setDataSourceKey(targetDataSource.name());

    }

/**

* 方法执行完之后清楚当前数据源,让其使用默认数据源

    * @param joinPoint

    * @param targetDataSource

    */

    @After("@annotation(targetDataSource)")

public void restoreDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){

DynamicDataSource.clearDataSourceKey();

    }

}

到此就可以在Service中引用了


@TargetDataSource(name ="test")

public UsergetUser(int id) {

return userDao.selectByPrimaryKey(id);

}

下面我们配置下通用mapper

pom.xml


  tk.mybatis

  mapper

  3.3.7

配置通用的mapper


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import tk.mybatis.spring.mapper.MapperScannerConfigurer;

import java.util.Properties;

@Configuration

public class MyBatisMapperScannerConfig {

@Bean

    public MapperScannerConfigurermapperScannerConfigurer() {

MapperScannerConfigurer mapperScannerConfigurer =new MapperScannerConfigurer();

        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");

        mapperScannerConfigurer.setBasePackage("com.txby.web.dao.base");//扫描该路径下的dao

        Properties properties =new Properties();

        properties.setProperty("mappers", "com.txby.common.mybatis.BaseDao");//通用dao

        properties.setProperty("notEmpty", "false");

        properties.setProperty("IDENTITY", "MYSQL");

        mapperScannerConfigurer.setProperties(properties);

        return mapperScannerConfigurer;

    }

}

通用mapper 的dao


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import tk.mybatis.spring.mapper.MapperScannerConfigurer;

import java.util.Properties;

@Configuration

public class MyBatisMapperScannerConfig {

@Bean

    public MapperScannerConfigurermapperScannerConfigurer() {

MapperScannerConfigurer mapperScannerConfigurer =new MapperScannerConfigurer();

        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");

        mapperScannerConfigurer.setBasePackage("com.txby.web.dao.base");//扫描该路径下的dao

        Properties properties =new Properties();

        properties.setProperty("mappers", "com.txby.common.mybatis.BaseDao");//通用dao

        properties.setProperty("notEmpty", "false");

        properties.setProperty("IDENTITY", "MYSQL");

        mapperScannerConfigurer.setProperties(properties);

        return mapperScannerConfigurer;

    }

}

UserDao继承通用dao 就可以像JPA一样调用封装好的方法了

“`

@Mapper

public interface UserDaoextends BaseDao {

}

”’

下面service的调用

”’

@Service

public class UserService {

@Autowired

private UserDaouserDao;

@TargetDataSource(name ="test")

public UsergetUser(int id) {

return userDao.selectByPrimaryKey(id);

}

public ListgetUsers() {

return userDao.selectAll();

}

}

”’

猜你喜欢

转载自blog.csdn.net/weixin_42080535/article/details/82178106
今日推荐