Springboot+mybaitsPlus动态数据源配置

Springboot+mybaitsPlus动态数据源配置:

1.动态数据源配置:

首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。在@SpringBootApplication注解中添加exclude属性即可:

@SpringBootApplication(exclude = {

        DataSourceAutoConfiguration.class

})

@EnableScheduling

public class Application extends SpringBootServletInitializer{

 

    @Override

    protected SpringApplicationBuilderconfigure(SpringApplicationBuilderapplication) {

        returnapplication.sources(Application.class);

    }

    public static void main(String[] args) {

        SpringApplication.run(Application.class,args);

    }

}

Pom.xml 配置

<dependency>

     <groupId>com.alibaba</groupId>

      <artifactId>druid</artifactId>

        <version>1.0.11</version>

</dependency>

application.properties文件添加多源数据库配置

#从库数量(配置几个从库)

dataSourceNumber=1

 

druid.type: com.alibaba.druid.pool.DruidDataSource

    druid.master:

         druid.master.url= jdbc:mysql://127.0.0.1:13306/baoliyun3?characterEncoding=utf8&useSSL=true

        druid.master.driver-class-name= com.mysql.jdbc.Driver

        druid.master.username= root

        druid.master.password= 12345678

        druid.master.initial-size= 5

        druid.master.min-idle= 1

        druid.master.max-active= 100

        druid.master.test-on-borrow=true

       druid.master.validationQuery=SELECT1 FROM dual

    druid.slave1:

        druid.slave1.url= jdbc:mysql://127.0.0.1:13306/baoliyunST?characterEncoding=utf8&useSSL=true

       druid.slave1.driver-class-name= com.mysql.jdbc.Driver

        druid.slave1.username= root

        druid.slave1.password= 12345678

        druid.slave1.initial-size= 5

        druid.slave1.min-idle= 1

        druid.slave1.max-active= 100

        druid.slave1.test-on-borrow=true

       druid.slave1.validationQuery=SELECT 1 FROM dual

#  druid.slave2:

  # druid.slave2.url=jdbc:mysql://127.0.0.1:13306/baoliyun2?characterEncoding=utf8&useSSL=true

  #      druid.slave2.driver-class-name=com.mysql.jdbc.Driver

  #      druid.slave2.username= root

  #      druid.slave2.password= 12345678

  #      druid.slave2.initial-size= 5

  #      druid.slave2.min-idle= 1

  #      druid.slave2.max-active= 100

  #      druid.slave2.test-on-borrow= true

  #      druid.slave2.validationQuery=SELECT 1FROM dual

目前配置两个数据库 主库master、从库slave1,可扩展

自动数据源已被禁掉,现在手动配置:

importjavax.sql.DataSource;

importorg.springframework.beans.factory.annotation.Value;

importorg.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;

importorg.springframework.boot.context.properties.ConfigurationProperties;

importorg.springframework.context.annotation.Bean;

importorg.springframework.context.annotation.Configuration;

importorg.springframework.jdbc.datasource.DataSourceTransactionManager;

importorg.springframework.transaction.annotation.EnableTransactionManagement;

 

 

@Configuration

@EnableTransactionManagement

public class DataSourceConfiguration {

 

      @Value("${druid.type}")

      private Class<?extends DataSource> dataSourceType;

     

      @Bean(name = "slaveDataSource1")

      @ConfigurationProperties(prefix ="druid.slave1")

      public DataSource slaveDataSource1(){

        return DataSourceBuilder.create().build();

      }

//   @Bean(name= "slaveDataSource2")

//   @ConfigurationProperties(prefix = "druid.slave2")

//    publicDataSource slaveDataSource2(){

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

//    }

      @Bean(name = "masterDataSource")

      @ConfigurationProperties(prefix ="druid.master")

      public DataSource masterDataSource(){

        return DataSourceBuilder.create().build();

      }

      /**

         *配置事务管理器

        */

        @Bean

        public DataSourceTransactionManagertransactionManager(DynamicDataSourcedataSource) throws Exception {

           return new DataSourceTransactionManager(dataSource);

        }

}

使用mybaitsplus的MybatisSqlSessionFactoryBean配置动态数据源


import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.MybatisXMLLanguageDriver;
import com.baomidou.mybatisplus.entity.GlobalConfiguration;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import com.ibr.baoliyun.config.DataSourceContextHolder.DbType;


@Configuration
public class MybatisPlusConfig{

@Autowired
private MybatisProperties properties;


@Autowired
private ResourceLoader resourceLoader = new DefaultResourceLoader();


@Autowired(required = false)
private Interceptor[] interceptors;


@Autowired(required = false)
private DatabaseIdProvider databaseIdProvider;

/**
* mybatis-plus分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor page = new PaginationInterceptor();
page.setDialectType("mysql");
return page;
}
/**
* 这里全部使用mybatis-autoconfigure 已经自动加载的资源。不手动指定
* 配置文件和mybatis-boot的配置文件同步
* @return
*/
@Bean
public MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean() {
MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
mybatisPlus.setDataSource(dynamicDataSource);
mybatisPlus.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
mybatisPlus.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
mybatisPlus.setGlobalConfig(new GlobalConfiguration(){{setIdType(IdType.AUTO.getKey());}});
mybatisPlus.setConfiguration(properties.getConfiguration());
if (!ObjectUtils.isEmpty(this.interceptors)) {
mybatisPlus.setPlugins(this.interceptors);
}
MybatisConfiguration mc = new MybatisConfiguration();
mc.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
mybatisPlus.setConfiguration(mc);
if (this.databaseIdProvider != null) {
mybatisPlus.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
mybatisPlus.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
mybatisPlus.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
mybatisPlus.setMapperLocations(this.properties.resolveMapperLocations());
}
return mybatisPlus;
}
@Resource(name = "masterDataSource")
    private DataSource masterDataSource;
@Resource(name = "slaveDataSource1")
    private DataSource slaveDataSource1;
@Resource(name = "slaveDataSource2")
    private DataSource slaveDataSource2;
@Resource(name = "dynamicDataSource")
    private DataSource dynamicDataSource;


  /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     * @return
     */
    @Bean(name = "dynamicDataSource")
    public DataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);


        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap(5);
        dsMap.put(DbType.MASTER, masterDataSource);
        dsMap.put(DbType.SLAVE+"1", slaveDataSource1);
        // dsMap.put(DbType.SLAVE+"2", slaveDataSource2);
        dynamicDataSource.setTargetDataSources(dsMap);


        return dynamicDataSource;
    }
}

我们通过自定义注解 + AOP的方式实现数据源动态切换。

 

首先定义一个ContextHolder, 用于保存当前线程使用的数据源名:

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class DataSourceContextHolder {

   public enum DbType{

               MASTER,SLAVE

             }

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

 

      /**

       * 默认数据源

       */

      public static final DbType DEFAULT_DB =DbType.MASTER;

 

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

 

      // 设置数据源名

      public static void setDB(String dbType) {

          log.debug("切换到{}数据源", dbType);

          contextHolder.set(dbType.toString());

      }

 

      // 获取数据源名

      public static String getDB() {

      returncontextHolder.get()==null?DbType.MASTER.toString():contextHolder.get();

      }

 

      // 清除数据源名

      public static void clearDB() {

          contextHolder.remove();

      }

}

然后自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource即可:

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

 

public class DynamicDataSource extendsAbstractRoutingDataSource {

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

      @Override

      protected ObjectdetermineCurrentLookupKey() {

          log.debug("数据源为{}", DataSourceContextHolder.getDB());

      returnDataSourceContextHolder.getDB();

      }

}

自定义注释@DB用于在编码时指定方法使用哪个数据源:

import java.lang.annotation.*;

importcom.ibr.baoliyun.config.DataSourceContextHolder.DbType;

@Retention(RetentionPolicy.RUNTIME)

@Target({

       ElementType.METHOD

})

public @interface DB {

    DbType value() default DbType.MASTER;

}

 

编写AOP切面,实现切换逻辑:

import java.lang.reflect.Method;

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;

importorg.springframework.beans.factory.annotation.Value;

import org.springframework.core.Ordered;

import org.springframework.stereotype.Component;

 

importcom.ibr.baoliyun.config.DataSourceContextHolder.DbType;

@Aspect

@Component

public class DynamicDataSourceAspect  implements Ordered {

    @Value("${dataSourceNumber}")

    private int dataSourceNumber;

    @Before("@annotation(DB)")

      public void beforeSwitchDB(JoinPointpoint){

 

          //获得当前访问的class

          Class<?> className =point.getTarget().getClass();

          //获得访问的方法名

          String methodName =point.getSignature().getName();

          //得到方法的参数的类型

          Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();

          DbType dataSource =DataSourceContextHolder.DEFAULT_DB;

          try {

              // 得到访问的方法对象

              Method method = className.getMethod(methodName,argClass);

 

              // 判断是否存在@DB注解

              if(method.isAnnotationPresent(DB.class)) {

                  DB annotation =method.getAnnotation(DB.class);

                  // 取出注解中的数据源名

                  dataSource =annotation.value();

              }

          } catch (Exception e) {

              e.printStackTrace();

          }

       //多个从库可选时

          if(dataSource.equals(DbType.SLAVE)){

             intsize = (int)((Math.random()*10) % dataSourceNumber )+1;

             //切换数据源

                  DataSourceContextHolder.setDB(dataSource.toString().concat(size+""));

          }else{

             DataSourceContextHolder.setDB(dataSource.toString());

          }

  }

 

      @After("@annotation(DB)")

      public void afterSwitchDB(JoinPoint point){

 

          DataSourceContextHolder.clearDB();

 

      }

      @Override

      public int getOrder() {

        return 0;

      }

}

Ordered排序接口,在这是用用于解决@Transactional问题,事务会在缓存中获取数据源,因为前面配置了默认数据源,所以首先读取默认数据源并且后面不能再次修改, 使用Ordered接口让动态数据源先配置

完成上述配置后就可以在Service中使用注解的方式切换数据源了

//      @DB(DbType.MASTER)

//      @DB(DbType.SLAVE)

         @Override

         public Page<User>getUserList(String name,String tel,int affiliatedFactoringEnterpriseId,ReqPagepage) {

                   return  new Page<User>(){{

                            setRecords(baseMapper.getUserList(name,tel,affiliatedFactoringEnterpriseId, page.getPageStart(), page.getPageSize()));

                            setTotal(selectCount(newEntityWrapper<User>()

                                               .where("affiliatedFactoringEnterpriseId={0}",affiliatedFactoringEnterpriseId)

                                               .addFilterIfNeed(!StringUtils.isEmpty(name),"name={0}", name)

                                               .addFilterIfNeed(!StringUtils.isEmpty(tel),"tel={0}", tel)));

                   }};

         }

如果使用多个从库,可以放开配置中注释掉的部分,从库选择方式为随机选择,如要修改可在AOP切面逻辑中修改

2.在方法中切换数据源

在需要切换数据源的地方添加如下代码:

DataSourceContextHolder.setDB(DbType.MASTER.toString());

DataSourceContextHolder.setDB(DbType.SLAVE.toString()+"1");

注意:如果存在事务,则事务中无法使用以上切换数据源,需要用到分布式事务,待完成

3.如何在不修改现有代码的情况下,动态切换数据源

添加全局拦截器,所有以get开头的方法走从库,其他走主库,可以与前面的配置共存,全局拦截中只会对没有@DS注解的方法进行处理

@Aspect 

@Component

public classGlobalDateSourceInterceptor extends HandlerInterceptorAdapter{

          @Value("${dataSourceNumber}")

          private  int dataSourceNumber;

         protected static  final Logger mlog = LoggerFactory.getLogger(GlobalDateSourceInterceptor.class);

         @Around("execution(*com.ibr.baoliyun.service.impl..*(..))")

         public ObjectgetGlobalDateSource(ProceedingJoinPoint point){

                    //获得当前访问的class

        Class<?> className =point.getTarget().getClass();

 

        //获得访问的方法名

        String methodName =point.getSignature().getName();

        //得到方法的参数的类型

        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();

        DbType dataSource =DataSourceContextHolder.DEFAULT_DB;

        try {

            // 得到访问的方法对象

            Method method =className.getMethod(methodName, argClass);

 

            // 判断是否存在@DB注解

            if(!method.isAnnotationPresent(DB.class)) {

                 String str = methodName.substring(0, 3);

                 if(str.equals("get")){

                      dataSource =DbType.SLAVE;

                      int size =(int)((Math.random()*10) % dataSourceNumber )+1;

                         //切换数据源

                               DataSourceContextHolder.setDB(dataSource.toString().concat(size+""));

                 }else{

                    DataSourceContextHolder.setDB(DbType.MASTER.toString());

                 }

            }

           

        } catch (Exception e) {

            e.printStackTrace();

        }

      

        try {

                            returnpoint.proceed();

                   } catch (Throwable e) {

                            return null;

                   }

         }


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值