SpringBoot+MybatisPlus配置讀寫分離


1.概述

繼承AbstractRoutingDataSource接口實現讀寫分離配置。使用的主要技術如下:

  • SpringBoot 2.1.12.RELEASE
  • MybatisPlus
  • alibaba.druid數據庫連接池
  • mysql數據庫
  • SpringAop

2.配置文件

mybatis-plus:
  # 如果是放在src/main/java目錄下 classpath:/com/yourpackage/*/mapper/*Mapper.xml
  # 如果是放在resource目錄 classpath:/mapper/*Mapper.xml
  mapper-locations: classpath:com/bbdog/dao/xml/*Mapper.xml
  #實體掃描,多個package用逗號或者分號分隔
  typeAliasesPackage: com.bbdog.dao.model
  global-config:
    #主鍵類型  0:"數據庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 0
    #字段策略 0:"忽略判斷",1:"非 NULL 判斷"),2:"非空判斷"
    field-strategy: 1
    #刷新mapper 調試神器
    refresh-mapper: true
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
    #配置JdbcTypeForNull
    jdbc-type-for-null: 'null'

spring:
  datasource:
    master:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://${spring.virtualIp}:3306/bbdog
      username: master
      password: ******
      #----數據庫連接池配置----------------------
      # 下面為連接池的補充設置,應用到上面所有數據源中
      # 初始化大小,最小,最大
      initialSize: 5
      minIdle: 1
      maxActive: 50
      # 配置獲取連接等待超時的時間
      maxWait: 60000
      # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一個連接在池中最小生存的時間,單位是毫秒
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 打開PSCache,並且指定每個連接上PSCache的大小
      poolPreparedStatements: false
      #maxPoolPreparedStatementPerConnectionSize: 20
      # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
      filters: stat,wall,slf4j
      # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
      connectionProperties:
          druid:
            stat:
              mergeSql: true
              slowSqlMillis: 5000
      # 合並多個DruidDataSource的監控數據
      #useGlobalDataSourceStat: true
    slave:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://${spring.virtualIp}:3306/bbdog
      username: slave
      password: ******
      #----數據庫連接池配置----------------------
      # 下面為連接池的補充設置,應用到上面所有數據源中
      # 初始化大小,最小,最大
      initialSize: 5
      minIdle: 1
      maxActive: 50
      # 配置獲取連接等待超時的時間
      maxWait: 60000
      # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一個連接在池中最小生存的時間,單位是毫秒
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 打開PSCache,並且指定每個連接上PSCache的大小
      poolPreparedStatements: false
      #maxPoolPreparedStatementPerConnectionSize: 20
      # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
      filters: stat,wall,slf4j
      # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
      connectionProperties:
          druid:
            stat:
              mergeSql: true
              slowSqlMillis: 5000
      # 合並多個DruidDataSource的監控數據
      #useGlobalDataSourceStat: true

3.SpringBoot啟動類設置

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) // 設置動態數據源需要,禁用數據源自動配置
@EnableTransactionManagement//開啟springBoot事務
@MapperScan("com.bbdog.dao.mapper*")
@EnableCaching//開啟基於注解的緩存
public class WebApplication {

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

}

4.創建數據源類型

public enum SourceName {
    read("read"), write("write");

    private String value;

    SourceName(String value) {
        this.value = value;
    }

    public String value() {
        return this.value;
    }
}

5.構建切換數據源類

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 將 read 數據源的 key作為默認數據源的 key
         */
        @Override
        protected String initialValue() {
            return SourceName.read.value();
        }
    };


    /**
     * 數據源的 key集合,用於切換時判斷數據源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * 切換數據源
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    /**
     * 獲取數據源
     *
     * @return
     */
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    /**
     * 重置數據源
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

    /**
     * 判斷是否包含數據源
     *
     * @param key 數據源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 添加數據源keys
     *
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}

6.繼承AbstractRoutingDataSource接口實現動態數據源

public class AutoChooseDataSource extends AbstractRoutingDataSource {

    /**
     * 如果不希望數據源在啟動配置時就加載好,可以定制這個方法,從任何你希望的地方讀取並返回數據源
     * 比如從數據庫、文件、外部接口等讀取數據源信息,並最終返回一個DataSource實現類對象即可
     */
    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }

    /**
     * 如果希望所有數據源在啟動配置時就加載好,這里通過設置數據源Key值來切換數據,定制這個方法
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
     * 設置默認數據源
     *
     * @param defaultDataSource
     */
    @Override
    public void setDefaultTargetDataSource(Object defaultDataSource) {
        super.setDefaultTargetDataSource(defaultDataSource);
    }

    /**
     * 設置數據源
     *
     * @param dataSources
     */
    @Override
    public void setTargetDataSources(Map<Object, Object> dataSources) {
        super.setTargetDataSources(dataSources);
        // 將數據源的 key 放到數據源上下文的 key 集合中,用於切換時判斷數據源是否有效
        DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
    }
}

7.數據源配置類設置

參照自動配置類MybatisPlusAutoConfiguration.java中的SqlSessionFactory配置來為添加自己的動態數據源

@SuppressWarnings("ConstantConditions")
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})
@ConditionalOnBean(DataSource.class)//容器中有DataSource類就可以調用該配置類的方法了
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration {   

    ...
        
    /*
    將自己配置的動態數據源放入容器中,容器會自動注入到該方法的入參。
    由於容器中有多個DataSource類,所以要將自己的動態數據源設置為默認@Primary
    */
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        ....
        return factory.getObject();
    }
}

數據源配置類內容:

@Configuration
public class DruidConfiguration {

    /**
     * 動態數據源配置**********************************↓↓↓↓↓↓↓↓↓↓↓↓↓↓
     ***************************/

    @Bean(name = "write", destroyMethod = "close", initMethod = "init")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource master() {
        return druidDataSource();
    }

    @Bean(name = "read", destroyMethod = "close", initMethod = "init")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slave() {
        return druidDataSource();
    }

    @Bean("dataSource")
    @Primary//自動裝配時當出現多個Bean候選者時,被注解為@Primary的Bean將作為首選者,否則將拋出異常
    public DataSource autoChooseDataSource() {
        AutoChooseDataSource autoChooseDataSource = new AutoChooseDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(SourceName.write.value(), master());
        dataSourceMap.put(SourceName.read.value(), slave());
        // 將 read 數據源作為默認指定的數據源
        autoChooseDataSource.setDefaultTargetDataSource(slave());
        // 將 read 和 write 數據源作為指定的數據源
        autoChooseDataSource.setTargetDataSources(dataSourceMap);
        return autoChooseDataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // 配置事務管理, 使用事務時在方法頭部添加@Transactional注解即可
        return new DataSourceTransactionManager(autoChooseDataSource());
    }

    /**
     * 動態數據源配置**********************************↑↑↑↑↑↑↑↑↑↑↑↑↑↑
     ***************************/

    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
}

8.創建數據源切換注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    /**
     * 數據源key值
     * @return
     */
    SourceName value();

}

9.創建數據源切換切面

@Aspect
@Order(-1)  // 該切面應當先於 @Transactional 執行
@Component
public class DynamicDataSourceAspect {
    private static Logger _log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    /**
     * 切換數據源
     *
     * @param point
     * @param dataSource
     */
    @Before("@annotation(dataSource))")
    public void switchDataSource(JoinPoint point, DataSource dataSource) {
        if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value().name())) {
            _log.error("DataSource [{}] 不存在,使用默認 DataSource [{}] ",
                    dataSource.value(),
                    DynamicDataSourceContextHolder.getDataSourceKey());
        } else {
            // 切換數據源
            DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value().name());
            _log.debug("切換 DataSource 至 [{}] ,引起切換方法是 [{}]",
                    DynamicDataSourceContextHolder.getDataSourceKey(),
                    point.getSignature());
        }
    }

    /**
     * 重置數據源
     *
     * @param point
     * @param dataSource
     */
    @After("@annotation(dataSource))")
    public void restoreDataSource(JoinPoint point, DataSource dataSource) {
        // 將數據源置為默認數據源
        DynamicDataSourceContextHolder.clearDataSourceKey();
        _log.debug("重置 DataSource 至 [{}] ,引起重置的方法是 [{}]",
                DynamicDataSourceContextHolder.getDataSourceKey(),
                point.getSignature());
    }
}

10.示例

    /**
     * 刪除角色
     *
     * @param role
     * @return
     */
    @Override
    @DataSource(SourceName.write)
    @Transactional
    public R deleteRoleById(Role role) {
        role.setUpdateTime(Utils.getCurrentFormatDateStr());
        role.setValid("0");
        Integer delete = baseMapper.updateById(role);
        this.clearPermissionsInRoleId(role.getRoleId());
        return new R(1 == delete);
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM