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层。分享出来给大家,同时留作笔记备忘。

相关推荐
David爱编程3 分钟前
happens-before 规则详解:JMM 中的有序性保障
java·后端
小张学习之旅4 分钟前
ConcurrentHashMap
java·后端
PetterHillWater25 分钟前
阿里Qoder的Quest小试牛刀
后端·aigc
程序猿阿伟29 分钟前
《支付回调状态异常的溯源与架构级修复》
后端·架构
dreams_dream1 小时前
django错误记录
后端·python·django
Tony Bai1 小时前
泛型重塑 Go 错误检查:errors.As 的下一站 AsA?
开发语言·后端·golang
猿java2 小时前
Elasticsearch有哪几种分页方式?该如何选择?
后端·elasticsearch·架构
绝无仅有2 小时前
服务器Docker 安装和常用命令总结
后端·面试·github
Chiy2 小时前
架构设计避坑指南:读写分离后,分表分库到底该何时用?
后端