AWS MySQL 读写分离配置指南

AWS JDBC Wrapper读写分离配置实战:Spring Boot + MyBatis Plus完整解决方案

前言

在微服务架构中,数据库读写分离是提升系统性能的重要手段。本文将详细介绍如何在Spring Boot项目中使用AWS JDBC Wrapper实现自动读写分离,重点解决MyBatis Plus框架下的配置难点,并对比Spring JPA的差异。

**核心结论**:AWS JDBC Wrapper需要连接的`readOnly`状态来判断路由,MyBatis Plus需要手动添加`@Transactional(readOnly = true)`,而Spring JPA会自动处理。

一、AWS JDBC Wrapper简介

1.1 什么是AWS JDBC Wrapper

AWS JDBC Wrapper是Amazon提供的数据库连接增强工具,支持:

  • 自动故障转移

  • 读写分离

  • 连接池管理

  • 性能监控

1.2 读写分离原理

```mermaid

graph TD

A[应用程序] --> B[AWS JDBC Wrapper]

B --> C{检查Connection.readOnly}

C -->|true| D[Aurora Reader Endpoint]

C -->|false| E[Aurora Writer Endpoint]

D --> F[只读副本]

E --> G[主库]

```

**关键机制**:AWS JDBC Wrapper通过检测JDBC连接的`readOnly`属性来决定路由目标。

二、基础配置

2.1 Maven依赖

```xml

<dependency>

<groupId>software.amazon.jdbc</groupId>

<artifactId>aws-advanced-jdbc-wrapper</artifactId>

<version>2.2.0</version>

</dependency>

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-boot-starter</artifactId>

<version>3.4.3</version>

</dependency>

```

2.2 数据源配置

```yaml

spring:

datasource:

type: com.zaxxer.hikari.HikariDataSource

driver-class-name: software.amazon.jdbc.Driver

username: ${DB_USERNAME}

password: ${DB_PASSWORD}

url: jdbc:aws-wrapper:mysql://{AURORA_CLUSTER_ENDPOINT}:3306/{DATABASE_NAME}?wrapperPlugins=readWriteSplitting,failover&characterEncoding=utf-8&wrapperLogLevel=FINEST&useSSL=true&requireSSL=true

```

**重要参数说明**:

  • `wrapperPlugins=readWriteSplitting,failover`:启用读写分离和故障转移

  • `wrapperLogLevel=FINEST`:启用详细日志,便于调试

2.3 日志配置

```xml

<!-- logback.xml -->

<configuration>

<!-- AWS JDBC Wrapper日志 -->

<logger name="software.amazon.jdbc" level="TRACE"/>

<logger name="software.amazon.jdbc.plugin.readwritesplitting" level="TRACE"/>

<logger name="software.amazon.jdbc.plugin.failover" level="TRACE"/>

<!-- HikariCP连接池日志 -->

<logger name="com.zaxxer.hikari" level="DEBUG"/>

</configuration>

```

三、核心问题:MyBatis Plus的读写分离挑战

3.1 问题现象

**预期**:查询操作自动路由到只读副本

**实际**:所有操作都路由到主库

**关键日志**:

```

TRACE ReadWriteSplittingPlugin - Writer connection set to 'cluster-endpoint:3306'

```

3.2 根本原因分析

**Spring JPA vs MyBatis Plus的差异**:

| 框架 | 事务配置 | readOnly设置 | 读写分离效果 |

|------|----------|--------------|-------------|

| Spring JPA | Repository方法自动添加`@Transactional(readOnly=true)` | ✅ 自动 | ✅ 有效 |

| MyBatis Plus | ServiceImpl无自动事务配置 | ❌ 手动 | ❌ 无效 |

**技术原理**:

  1. AWS JDBC Wrapper依赖`Connection.setReadOnly(true)`来判断路由

  2. Spring事务管理器负责设置连接的readOnly状态

  3. 只有在`@Transactional(readOnly=true)`时,Spring才会调用`connection.setReadOnly(true)`

四、解决方案

4.1 方案一:Service层添加只读事务(推荐)

```java

@Service

@Slf4j

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

/**

* 查询方法 - 走读库

*/

@Override

@Transactional(readOnly = true)

public List<User> list(QueryWrapper<User> queryWrapper) {

return super.list(queryWrapper);

}

/**

* 分页查询 - 走读库

*/

@Override

@Transactional(readOnly = true)

public IPage<User> page(IPage<User> page, QueryWrapper<User> queryWrapper) {

return super.page(page, queryWrapper);

}

/**

* 统计查询 - 走读库

*/

@Override

@Transactional(readOnly = true)

public int count(QueryWrapper<User> queryWrapper) {

return super.count(queryWrapper);

}

/**

* 写操作 - 走写库

*/

@Override

@Transactional

public boolean save(User entity) {

return super.save(entity);

}

}

```

4.2 方案二:Controller层添加只读事务

```java

@RestController

@RequestMapping("/api/users")

public class UserController {

@Autowired

private IUserService userService;

/**

* 查询接口 - 走读库

*/

@GetMapping("/list")

@Transactional(readOnly = true)

public Result<List<User>> getUserList() {

List<User> users = userService.list();

return Result.success(users);

}

/**

* 创建接口 - 走写库

*/

@PostMapping

@Transactional

public Result<Boolean> createUser(@RequestBody User user) {

boolean success = userService.save(user);

return Result.success(success);

}

}

```

4.3 方案三:创建专门的只读Service

```java

@Service

@Transactional(readOnly = true) // 类级别只读事务

public class UserReadOnlyService {

@Autowired

private UserMapper userMapper;

public List<User> queryUsers(QueryWrapper<User> queryWrapper) {

return userMapper.selectList(queryWrapper);

}

public IPage<User> queryUsersPage(IPage<User> page, QueryWrapper<User> queryWrapper) {

return userMapper.selectPage(page, queryWrapper);

}

public long queryCount(QueryWrapper<User> queryWrapper) {

return userMapper.selectCount(queryWrapper);

}

}

```

4.4 方案四:混合使用JDBC和MyBatis Plus

```java

@Service

public class UserHybridService {

@Autowired

private DataSource dataSource;

@Autowired

private IUserService userService;

/**

* 简单查询用JDBC - 走读库

*/

public long getUserCount() throws SQLException {

try (Connection conn = dataSource.getConnection()) {

conn.setReadOnly(true);

try (PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(*) FROM user")) {

try (ResultSet rs = stmt.executeQuery()) {

return rs.next() ? rs.getLong(1) : 0;

}

}

}

}

/**

* 复杂操作用MyBatis Plus - 走写库

*/

@Transactional

public boolean createUserWithRelations(User user) {

return userService.save(user);

}

}

```

五、验证方法

5.1 测试代码

```java

@RestController

@RequestMapping("/api/test")

public class ReadWriteTestController {

@Autowired

private IUserService userService;

@Autowired

private DataSource dataSource;

/**

* 测试JDBC读操作

*/

@GetMapping("/jdbc-read")

public String testJdbcRead() throws SQLException {

try (Connection conn = dataSource.getConnection()) {

conn.setReadOnly(true);

// 执行查询...

return "JDBC读测试完成";

}

}

/**

* 测试MyBatis Plus读操作

*/

@GetMapping("/mybatis-read")

@Transactional(readOnly = true)

public String testMybatisRead() {

userService.list();

return "MyBatis Plus读测试完成";

}

}

```

5.2 期望的日志输出

**走读库的日志**:

```

TRACE ReadWriteSplittingPlugin - Reader connection set to 'cluster-ro-endpoint:3306'

TRACE ReadWriteSplittingPlugin - Routing read operation to reader endpoint

```

**走写库的日志**:

```

TRACE ReadWriteSplittingPlugin - Writer connection set to 'cluster-endpoint:3306'

TRACE ReadWriteSplittingPlugin - Routing write operation to writer endpoint

```

六、最佳实践

6.1 设计原则

  1. **查询操作**:统一添加`@Transactional(readOnly = true)`

  2. **写操作**:使用`@Transactional`或不添加注解

  3. **事务边界**:在Service层或Controller层明确定义

  4. **职责分离**:考虑创建专门的只读Service类

6.2 实施优先级

**高优先级**:

  • 核心业务Service(订单、支付、用户等)

  • 高频查询接口

  • 报表和统计功能

**中优先级**:

  • 基础数据Service

  • 管理后台查询

  • 定时任务查询

**低优先级**:

  • 低频管理功能

  • 工具类查询

6.3 注意事项

  1. **事务传播**:在事务中的所有操作都会走主库

  2. **连接复用**:HikariCP可能复用连接,观察日志时注意时间戳

  3. **故障转移**:读库不可用时会自动转移到主库

  4. **复制延迟**:业务逻辑需要考虑主从复制延迟

七、Spring JPA对比

7.1 为什么Spring JPA更容易实现读写分离

```java

// Spring Data JPA - 自动只读

@Repository

public interface UserRepository extends JpaRepository<User, Long> {

// 框架自动为查询方法添加 @Transactional(readOnly = true)

List<User> findByStatus(String status); // 自动走读库

// 框架自动为写方法添加 @Transactional

User save(User user); // 自动走写库

}

```

7.2 JPA vs MyBatis Plus总结

| 特性 | Spring JPA | MyBatis Plus |

|------|------------|--------------|

| 学习曲线 | 简单,开箱即用 | 需要理解事务配置 |

| 自动化程度 | 高度自动化 | 需要手动配置 |

| 性能控制 | 抽象层较厚 | 更接近原生SQL |

| 读写分离 | 自动支持 | 需要手动实现 |

| SQL优化 | 相对困难 | 灵活度高 |

八、故障排查

8.1 常见问题

**问题1:看不到ReadWriteSplittingPlugin日志**

  • 检查URL中的`wrapperLogLevel=FINEST`

  • 确认logback.xml中的日志级别

  • 重启应用重新观察

**问题2:所有操作都连接同一endpoint**

  • 检查`@Transactional(readOnly = true)`是否正确添加

  • 确认Aurora集群是否有只读副本

  • 验证URL中的`wrapperPlugins`参数

**问题3:连接失败**

  • 检查SSL证书配置

  • 验证网络连通性

  • 确认Aurora集群状态

8.2 调试技巧

  1. **启用详细日志**:

```yaml

logging:

level:

software.amazon.jdbc: TRACE

com.zaxxer.hikari: DEBUG

```

  1. **诊断连接状态**:

```java

@GetMapping("/diagnose")

public Map<String, Object> diagnoseDatasource() {

try (Connection conn = dataSource.getConnection()) {

DatabaseMetaData metaData = conn.getMetaData();

Map<String, Object> info = new HashMap<>();

info.put("driverName", metaData.getDriverName());

info.put("url", metaData.getURL());

info.put("isAwsWrapper", metaData.getDriverName().contains("Amazon"));

return info;

}

}

```

九、总结

AWS JDBC Wrapper是一个强大的数据库连接工具,但在MyBatis Plus环境下需要正确配置事务注解才能实现读写分离。核心要点:

  1. **理解原理**:读写分离依赖`Connection.setReadOnly()`状态

  2. **正确配置**:为查询方法添加`@Transactional(readOnly = true)`

  3. **验证效果**:通过日志确认路由行为

  4. **渐进实施**:按优先级逐步改造现有代码

通过本文的配置方案,可以有效提升系统的数据库读性能,减轻主库压力,为系统的高可用和高性能打下坚实基础。


> **作者经验**:在实际项目中,建议先在测试环境验证配置,观察日志确认读写分离生效后,再逐步推广到生产环境。同时要注意监控Aurora集群的读写负载分布,确保达到预期的性能提升效果。

**技术栈**:Spring Boot 2.x + MyBatis Plus 3.4.x + AWS JDBC Wrapper 2.2.x + Aurora MySQL

相关推荐
KellenKellenHao1 小时前
MySQL数据库主从复制
数据库·mysql
一只fish2 小时前
MySQL 8.0 OCP 1Z0-908 题目解析(16)
数据库·mysql
泊浮目2 小时前
未来数据库硬件-网络篇
数据库·架构·云计算
叁沐3 小时前
MySQL 07 行锁功过:怎么减少行锁对性能的影响?
mysql
Java烘焙师4 小时前
架构师必备:业务扩展模式选型
mysql·elasticsearch·架构·hbase·多维度查询
飞翔的佩奇4 小时前
Java项目:基于SSM框架实现的忘忧小区物业管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】
java·数据库·mysql·vue·毕业设计·ssm框架·小区物业管理系统
@Ryan Ding4 小时前
MySQL主从复制与读写分离概述
android·mysql·adb
运维开发王义杰11 小时前
金融安全生命线:用AWS EventBridge和CloudTrail构建主动式入侵检测系统
安全·金融·aws
feifeigo12311 小时前
升级到MySQL 8.4,MySQL启动报错:io_setup() failed with EAGAIN
数据库·mysql·adb
A__tao16 小时前
一键将 SQL 转为 Java 实体类,全面支持 MySQL / PostgreSQL / Oracle!
java·sql·mysql