Spring Boot + Mybatis多数据源和动态数据源配置

参考原创地址:https://blog.csdn.net/neosmith/article/details/61202084

说明:使用动态数据源的初衷,是能在应用层做到读写分离,即在程序代码中控制不同的查询方法去连接不同的库

1.第一步 关闭spring boot的单数据源:

/**
 * 通过配置多数据源,实现动态数据源切换
 * 1.禁用掉spring boot自带的数据源处理类DataSourceAutoConfiguration.class。
 * 2.在配置文件application.yml中配置连接多个数据操作
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan("com.example.server.dao")
public class ServerApplication {

   public static void main(String[] args) {
      SpringApplication.run(ServerApplication.class, args);
   }

2,第二步 定义一个ContextHolder, 用于保存当前线程使用的数据源名

/**
 * 使用动态数据源的初衷,是能在应用层做到读写分离,即在程序代码中控制不同的查询方法去连接不同的库。除了这种方法以外,
 * 数据库中间件也是个不错的选择,它的优点是数据库集群对应用来说只暴露为单库,不需要切换数据源的代码逻辑。
 * 我们通过自定义注解 + AOP的方式实现数据源动态切换。
 * 首先定义一个ContextHolder, 用于保存当前线程使用的数据源名
 * @author liuqiuping 
 * @date 2018-4-21
 */
public class DataSourceContextHolder {
    public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);

    /**
     * 默认数据源
     */
    public static final String DEFAULT_DS = "Master";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源名
    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();
    }
}

3 第三步 自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource

package com.example.server.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 动态数据源配置,
 *自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource
 * @author liuqiuping
 * @date  2018-4-21
 */
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();
    }

}

4 第四步 创建自定义sqlSessionFactory

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 创建自定义sqlSessionFactory,实现多个数据源动态注入sqlSessionFactory
 * @author liuqiuping
 * @date 2018-4-21
 */
@Configuration
@MapperScan(basePackages = {"com.example.server.dao"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisDbAConfig {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    @Bean(name = "Master")
    @ConfigurationProperties(prefix = "spring.datasource.master") // application.properteis中对应属性的前缀
    public DataSource MasterDataSource() {
        logger.info("注入 druid MasterDataSource!!!");
//        DruidDataSource datasource = new DruidDataSource();
////        datasource.setUrl(propertyResolver.getProperty("url"));
////        datasource.setDriverClassName(propertyResolver.getProperty("driver-class-name"));
////        datasource.setUsername(propertyResolver.getProperty("username"));
////        datasource.setPassword(propertyResolver.getProperty("password"));
////        datasource.setInitialSize(Integer.valueOf(propertyResolver.getProperty("initialSize")));
////        datasource.setMinIdle(Integer.valueOf(propertyResolver.getProperty("minIdle")));
////        datasource.setMaxWait(Long.valueOf(propertyResolver.getProperty("maxWait")));
////        datasource.setMaxActive(Integer.valueOf(propertyResolver.getProperty("maxActive")));
////        datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver.getProperty("minEvictableIdleTimeMillis")));
////        return datasource;

        return DataSourceBuilder.create().build();
    }

    @Bean(name = "Cluster")
    @ConfigurationProperties(prefix = "spring.datasource.cluster") // application.properteis中对应属性的前缀
    public DataSource clusterDataSource() {
        logger.info("注入 druid MasterDataSource!!!");
//        DruidDataSource datasource = new DruidDataSource();
//        datasource.setUrl(propertyResolver.getProperty("url"));
//        datasource.setDriverClassName(propertyResolver.getProperty("driver-class-name"));
//        datasource.setUsername(propertyResolver.getProperty("username"));
//        datasource.setPassword(propertyResolver.getProperty("password"));
//        datasource.setInitialSize(Integer.valueOf(propertyResolver.getProperty("initialSize")));
//        datasource.setMinIdle(Integer.valueOf(propertyResolver.getProperty("minIdle")));
//        datasource.setMaxWait(Long.valueOf(propertyResolver.getProperty("maxWait")));
//        datasource.setMaxActive(Integer.valueOf(propertyResolver.getProperty("maxActive")));
//        datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver.getProperty("minEvictableIdleTimeMillis")));
//        return datasource;
        return DataSourceBuilder.create().build();
    }

    @Bean(name="dynamicDataSource")
    @Primary    //优先使用,多数据源
    public DataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        DataSource master = MasterDataSource();
        DataSource slave = clusterDataSource();
        //设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(master);
        //配置多数据源
        Map<Object,Object> map = new HashMap<>();
        map.put("Master", master);   //key需要跟ThreadLocal中的值对应
        map.put("Cluster", slave);
        dynamicDataSource.setTargetDataSources(map);
        return dynamicDataSource;
    }


}

5 第五步,编写AOP切面,实现切换逻辑,根据具体方法调用不用的数据源

package com.example.server.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 编写AOP切面,实现切换逻辑:
 * @author liuqiuping
 * @date 2018-4-21
 */
@Aspect
@Component
public class DynamicDataSourceAspect {

    @Before("@annotation(DS)")
    public void beforeSwitchDS(JoinPoint point){

        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();

        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        String dataSource = DataSourceContextHolder.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);

            // 判断是否存在@DS注解
            if (method.isAnnotationPresent(DS.class)) {
                DS annotation = method.getAnnotation(DS.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 切换数据源
        DataSourceContextHolder.setDB(dataSource);

    }


    @After("@annotation(DS)")
    public void afterSwitchDS(JoinPoint point){

        DataSourceContextHolder.clearDB();

    }
}

6 第六步 自定义DS注解

/**
 * 自定义DS注解
 * @author  liuqiuping
 * @date  2018-4-21
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.METHOD
})
public @interface DS {
    String value() default "master";
}

 到此完成多数据源的配置

接下来配置两个不同数据数据源的配置连接

spring:
  application:
    name: sso-server
##  选择 数据源
  datasource:
    master:
      url: jdbc:mysql://127.0.0.1:3306/sso?&useUnicode=true&characterEncoding=utf8 
      username: root                                                             
      password: 123                                                               
      driver-class-name: com.mysql.jdbc.Driver
      initialSize: 5
      minIdle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
    cluster:
      url: jdbc:mysql://127.0.0.1:3306/ssoCluster?&useUnicode=true&characterEncoding=utf8
      username: root                                                             
      password: 123                                                              
      driver-class-name: com.mysql.jdbc.Driver
      initialSize: 5
      minIdle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000

动态数据源测试

@DS("Master")
@Override
public User findByLoginName(String name) {
   User user = userMapper.findByLoginName(name);
   if(null!=user){
      user.setRoles(roleMapper.selectByUserId(user.getId()));
   }

   return user;
}

/**
 * 使用数据源2
 * @param loginName
 * @return
 */
@DS("Cluster")
@Override
public User findByLoginNameTest(String loginName) {
   return userMapper.findByLoginNameTest(loginName);
}

亲测,调用不同的方法,执行不同的数据查询成功。

猜你喜欢

转载自my.oschina.net/u/2364025/blog/1797740
今日推荐