springboot + mybatis 实现数据库读写分离,写主库读从库

通过简单的代码描述实现,读从库写主库(数据库读写分离)

  1. yml配置如下:
yaml 复制代码
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/im?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 19901024
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/im?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 19901024
  1. 数据库配置
typescript 复制代码
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    /**
     * 获取数据源
     *
     * @return 数据源
     */
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 获取数据源
     *
     * @return 数据源
     */
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 动态数据源配置
     * <p/>
     * "@DependsOn({"masterDataSource", "slaveDataSource"})" 解决循环依赖的问题
     *
     * @return 动态数据源
     */
    @Primary
    @Bean("dynamicDataSource")
    @DependsOn({"masterDataSource", "slaveDataSource"})
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceTypeEnum.MASTER.getType(), masterDataSource());
        targetDataSources.put(DataSourceTypeEnum.SLAVE.getType(), slaveDataSource());
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    /**
     * 定义事务管理策略
     * @return 事务源
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}
  1. 使用 ThreadLocal 配置上下文
java 复制代码
```
package com.seeker.config;

import com.seeker.enums.DataSourceTypeEnum;
import lombok.extern.slf4j.Slf4j;

/**
 * @author :悬崖上的列车(Jiuling Huan)
 * @date :Created in 2025/2/27
 * @slogan: 莫听穿林打叶声 何妨吟啸且徐行 竹杖芒鞋轻胜马 一蓑烟雨任平生
 * @email: 514082870@qq.com
 * @desc:
 **/
@Slf4j
public class DbContextHandler {
    /**
     * 存储数据库类型
     */
    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源
     *
     * @param dbType 数据库类型
     */
    public static void setDbType(DataSourceTypeEnum dbType) {
        CONTEXT_HOLDER.set(dbType.getType());
    }

    /**
     * 取得当前数据源
     *
     * @return 数据源
     */
    public static String getDbType() {
        log.info("当前操作数据源为-->" + CONTEXT_HOLDER.get());
        return (String) CONTEXT_HOLDER.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        CONTEXT_HOLDER.remove();
    }
}
```
  1. 动态切换数据源配置类
scala 复制代码
```
package com.seeker.config;

import com.seeker.enums.DataSourceTypeEnum;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author :悬崖上的列车(Jiuling Huan)
 * @date :Created in 2025/2/27
 * @slogan: 莫听穿林打叶声 何妨吟啸且徐行 竹杖芒鞋轻胜马 一蓑烟雨任平生
 * @email: 514082870@qq.com
 * @desc:
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String typeKey = DbContextHolder.getDbType();
        logger.info(typeKey);
        if (typeKey == null) {
            logger.info("无法确定数据源,重置为主库(写库)");
            DbContextHolder.setDbType(DataSourceTypeEnum.MASTER);
            typeKey = DataSourceTypeEnum.MASTER.getType();
        }
        return typeKey;
    }
}
```
  1. 数据源AOP切入配置类
java 复制代码
```
package com.seeker.config;

import com.seeker.enums.DataSourceTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author :悬崖上的列车(Jiuling Huan)
 * @date :Created in 2025/2/27
 * @slogan: 莫听穿林打叶声 何妨吟啸且徐行 竹杖芒鞋轻胜马 一蓑烟雨任平生
 * @email: 514082870@qq.com
 * @desc:
 **/
@Component
@Aspect
@Slf4j
public class RoutingDataSourceAspect {

    /**
     * master主库写库AOP切入点
     */
    @Pointcut("execution(* com.seeker.mapper..*.insert*(..)) "
            + "|| execution(* com.seeker.mapper..*.save*(..))"
            + "|| execution(* com.seeker.mapper..*.update*(..))"
            + "|| execution(* com.seeker.mapper..*.delete*(..))"
            + "|| execution(* com.seeker.mapper..*.reomove*(..))")
    private void masterDataSourcePointcut() {
        log.info("切换到master主数据源");
    }

    /**
     * slave数据库读库服务AOP切入点
     */
    @Pointcut("execution(* com.seeker.mapper.ImMessageMapper.select*(..))")
    private void slaveDataSourcePointcut() {
        log.info("切换到slave从数据源");
    }

    /**
     * 切换master数据源
     */
    @Before("masterDataSourcePointcut()")
    public void master() {
        DbContextHolder.setDbType(DataSourceTypeEnum.MASTER);
    }

    /**
     * 返回后清除master数据源
     */
    @AfterReturning("masterDataSourcePointcut()")
    public void masterClear() {
        DbContextHolder.clearDbType();
    }

    /**
     * 异常时清除master数据源
     */
    @AfterThrowing("masterDataSourcePointcut()")
    public void masterExceptionClear() {

        DbContextHolder.clearDbType();
    }

    /**
     * 切换slave数据源
     */
    @Before("slaveDataSourcePointcut()")
    public void slave() {

        DbContextHolder.setDbType(DataSourceTypeEnum.SLAVE);
    }

    /**
     * 返回后清除slave数据源
     */
    @AfterReturning("slaveDataSourcePointcut()")
    public void slaveClear() {

        DbContextHolder.clearDbType();
    }

    /**
     * 异常时清除slave数据源
     */
    @AfterThrowing("slaveDataSourcePointcut()")
    public void slaveExceptionClear() {

        DbContextHolder.clearDbType();
    }



}
```
  1. 枚举类
typescript 复制代码
package com.seeker.enums;

/**
 * @author :悬崖上的列车(Jiuling Huan)
 * @date :Created in 2025/2/27
 * @slogan: 莫听穿林打叶声 何妨吟啸且徐行 竹杖芒鞋轻胜马 一蓑烟雨任平生
 * @email: 514082870@qq.com
 * @desc:
 **/
public enum DataSourceTypeEnum {

    /**
     * mysqlMaster主库
     */
    MASTER("master"),

    /**
     * slave从库
     */
    SLAVE("slave");

    /**
     * 数据库类型
     */
    private String type;

    /**
     * 构造函数
     */
    DataSourceTypeEnum(String type) {
        this.type = type;
    }

    /**
     * 数据库类型
     *
     * @return type
     */
    public String getType() {
        return type;
    }

}

7.Mapper类

csharp 复制代码
package com.seeker.mapper;

import com.seeker.entity.ImMessage;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ImMessageMapper {

    int deleteByPrimaryKey(Integer id);

    int insert(ImMessage record);

    int insertSelective(ImMessage record);

    ImMessage selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(ImMessage record);

    int updateByPrimaryKey(ImMessage record);
}

总结:上述代码稍作修改即可使用,需修改AOP切面部分的execution表达式,换成自己的DAO层。分享出来给大家,同时留作笔记备忘。

相关推荐
张彦峰ZYF15 分钟前
功能齐了不等于能上线:智能工单系统的AI回归测试、安全防护、成本可观测与CI/CD生产部署
后端·企业智能工单系统·评测线·安全线·成本线·部署线
Rain的Java大神之路18 分钟前
LeetCode 37 解数独面试攻略:回溯 + 位掩码 + MRV,从暴力到毫秒级求解
java·后端·leetcode
量化分析码农31 分钟前
【Python量化波动率实战 05】你的组合真的分散了吗:滚动相关与分散化收益
后端
IT_陈寒1 小时前
Vue这个奇葩的响应式问题让我加班到凌晨
前端·人工智能·后端
脚本自动化开发8 小时前
存储涨价后,磐镭WO5迷你主机值得买吗
后端
一只爱撸猫的程序猿11 小时前
我看着一个 AI 引擎在 14 天里,从"记忆"走进了"搜索"
人工智能·后端·架构
陈随易13 小时前
纯 Go 写的 JS 引擎,跑分超过了 C 语言的 QuickJS
前端·后端·程序员
卷无止境14 小时前
RAGFlow 项目全景报告:从文档解析引擎到 Agent 原生数据库
后端·python
小Z先生15 小时前
我用 Go + 系统 WebView 写了个纯本地 Postman:不装 Electron、不连云端
后端·github
IT_陈寒15 小时前
Redis连接池泄漏那次,我差点把生产环境搞崩了
前端·人工智能·后端