通过简单的代码描述实现,读从库写主库(数据库读写分离)
- 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
- 数据库配置
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());
}
}
- 使用 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();
}
}
```
- 动态切换数据源配置类
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;
}
}
```
- 数据源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();
}
}
```
- 枚举类
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层。分享出来给大家,同时留作笔记备忘。