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

相关推荐
m0_748235951 小时前
Spring Boot问题总结
java·spring boot·后端
brhhh_sehe1 小时前
Spring Boot 热部署
java·spring boot·后端
在线打码2 小时前
SpringBoot接口自动化测试实战:从OpenAPI到压力测试全解析
spring boot·后端·功能测试·压力测试·postman
m0_748233883 小时前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
一个热爱生活的普通人3 小时前
深入解析Go语言Channel的底层实现与高效使用实践
后端·go
Victor3563 小时前
Zookeeper(78)Zookeeper的性能优化有哪些方法?
后端
不懂904 小时前
Spring Boot集成Jetty、Tomcat或Undertow及支持HTTP/2协议
spring boot·后端·http·https
总是学不会.5 小时前
从“记住我”到 Web 认证:Cookie、JWT 和 Session 的故事
java·前端·后端·开发
Neozsvc5 小时前
飞书工单审批对接腾讯电子签:开启合同流程自动化新时代
运维·人工智能·后端·自动化·飞书
℡52Hz★6 小时前
利用node.js搭配express框架写后端接口(一)
后端·node.js·express