本文目录导读:

在Java中实现主从数据源(读写分离)有多种方式,这里我给你一个最常用、最实用的完整方案,使用Spring Boot + MyBatis + 动态数据源(基于AbstractRoutingDataSource)。
核心实现方案
Maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.18</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
数据库配置(application.yml)
spring:
datasource:
# 主库(写)
master:
url: jdbc:mysql://localhost:3306/db_master?useUnicode=true&characterEncoding=utf-8
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库(读) - 可以有多个
slave:
- url: jdbc:mysql://localhost:3307/db_slave1?useUnicode=true&characterEncoding=utf-8
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3308/db_slave2?useUnicode=true&characterEncoding=utf-8
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
定义数据源枚举
public enum DataSourceType {
MASTER("master"),
SLAVE("slave");
private String name;
DataSourceType(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
实现动态数据源核心类
/**
* 动态数据源上下文持有者
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
/**
* 动态数据源 - 继承AbstractRoutingDataSource
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
/**
* 数据源配置类
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dynamicDataSource() {
// 这里简化处理,实际应该从配置读取多个从库
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.getName(), masterDataSource());
targetDataSources.put(DataSourceType.SLAVE.getName(), slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
注解定义(用于标记读写操作)
/**
* 读操作注解 - 走从库
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
boolean value() default true;
}
/**
* 写操作注解 - 走主库
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MasterOnly {
boolean value() default true;
}
AOP切面实现数据源自动切换
@Aspect
@Component
@Order(1) // 优先级高,先于事务执行
public class DataSourceAspect {
private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
/**
* 对service层方法进行拦截(这里简化为拦截所有方法)
*/
@Pointcut("execution(* com.example..service.*.*(..))")
public void servicePointcut() {}
@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 1. 检查方法上的@MasterOnly注解
MasterOnly masterOnly = method.getAnnotation(MasterOnly.class);
if (masterOnly != null) {
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.getName());
log.debug("Set DataSource to MASTER for method: {}", method.getName());
return;
}
// 2. 检查方法上的@ReadOnly注解
ReadOnly readOnly = method.getAnnotation(ReadOnly.class);
if (readOnly != null) {
// 从从库中随机选择一个(负载均衡)
String slaveKey = selectSlave();
DynamicDataSourceContextHolder.setDataSourceType(slaveKey);
log.debug("Set DataSource to SLAVE for method: {}", method.getName());
return;
}
// 3. 默认走主库
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.getName());
}
@After("servicePointcut()")
public void after(JoinPoint joinPoint) {
DynamicDataSourceContextHolder.clearDataSourceType();
log.debug("Cleared DataSource context for method: {}",
joinPoint.getSignature().getName());
}
/**
* 简单的从库负载均衡 - 轮询
*/
private String selectSlave() {
// 可以在这里实现随机、轮询、最少连接等算法
// 这里简单返回SLAVE,如果有多个从库可以扩展
return DataSourceType.SLAVE.getName();
}
}
事务支持(重要)
/**
* 事务管理器配置 - 使用动态数据源
*/
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* 支持在事务内切换数据源
*/
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
业务层使用示例
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 写操作 - 自动走主库
*/
@Transactional
@MasterOnly
public void createUser(User user) {
userMapper.insert(user);
}
/**
* 读操作 - 自动走从库
*/
@ReadOnly
public User getUserById(Long id) {
return userMapper.selectById(id);
}
/**
* 读写混合操作
* 注意:事务内读操作需要特别注意,默认走主库
*/
@Transactional
@MasterOnly
public User updateAndGetUser(User user) {
userMapper.updateById(user);
// 这里虽然是读,但在事务内,需要走主库
return userMapper.selectById(user.getId());
}
/**
* 复杂查询 - 走从库
*/
@ReadOnly
public List<User> getUserList(String name) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, name);
return userMapper.selectList(wrapper);
}
}
高级扩展方案
多从库负载均衡
/**
* 动态数据源负载均衡器
*/
@Component
public class DynamicDataSourceBalancer {
// 模拟多个从库
private static final List<String> SLAVE_KEYS = Arrays.asList(
"slave1", "slave2", "slave3"
);
private AtomicInteger counter = new AtomicInteger(0);
public String getSlaveKey() {
// 轮询
int index = counter.getAndIncrement() % SLAVE_KEYS.size();
if (counter.get() > Integer.MAX_VALUE - 10000) {
counter.set(0);
}
return SLAVE_KEYS.get(index);
}
}
读写分离数据源配置优化
@Configuration
public class MultiDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slaves")
public List<DataSource> slaveDataSources() {
// 从配置读取多个从库
return new ArrayList<>();
}
@Bean
@Primary
public DataSource routingDataSource() {
DynamicDataSource routingDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
// 注册多个从库
List<DataSource> slaves = slaveDataSources();
for (int i = 0; i < slaves.size(); i++) {
targetDataSources.put("slave" + (i + 1), slaves.get(i));
}
routingDataSource.setDefaultTargetDataSource(masterDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
注意事项
- 事务内一致性:在事务内读操作建议走主库,避免读到脏数据
- 数据同步延迟:主从复制有延迟,实时性要求高的读操作走主库
- 连接池配置:主从库使用独立的连接池配置
- 监控告警:监控主从同步状态和延迟时间
- 异常处理:从库不可用自动切换到主库
这个方案是最常用的注解驱动 + AOP拦截方式,易于理解、便于维护,适合大部分业务场景。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播