SpringBoot2 整合 Sharding JDBC 实现 Mysql 读写分离

本文详细介绍如何在SpringBoot2.0环境下利用ShardingJDBC实现数据库读写分离,包括配置文件、实体类、Repository、Controller及Service层的实现,通过具体测试验证读写分离功能。

想直接要源码的,点这里


简介

Sharding-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 Java 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等
  • 支持任意实现JDBC规范的数据库。目前支持 MySQL,Oracle,SQLServer 和 PostgreSQL

前言

本例只是简单实现了 Sharding-JDBC 中的读写分离功能,请注意。

所用到的技术栈及版本:

  • SpringBoot 2.0.4
    • Spring Data JPA
    • HikariDataSource
    • Gson 2.8.5
    • lombok 1.16.22
    • mysql-connector-java 5.1.46
  • sharding-jdbc-core 2.0.3

主要部分

配置文件:application.yml

# JPA
spring:
  jpa:
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: create

# Server
server:
  port: 8888

# Sharding JDBC
sharding:
  jdbc:
    data-sources:
      ds_master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/master?characterEncoding=utf8&useSSL=false
        username: root
        password: root
      ds_slave:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/slave?characterEncoding=utf8&useSSL=false
        username: root
        password: root
    master-slave-rule:
      name: ds_ms
      master-data-source-name: ds_master
      slave-data-source-names: ds_slave
      load-balance-algorithm-type: round-robin

这里用的是 springboot2.0 默认的数据库连接池 HikariDataSource

  • load-balance-algorithm-type
    查询时的负载均衡算法,目前有2种算法,round_robin(轮询)和random(随机)
  • master-data-source-name: 主数据源名称
  • slave-data-source-names: 从数据源名称 多个用逗号隔开

存放数据源数据:ShardingMasterSlaveConfig.java

package com.example.shardingjdbc.config;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

import com.zaxxer.hikari.HikariDataSource;

import io.shardingjdbc.core.api.config.MasterSlaveRuleConfiguration;
import lombok.Data;

/**
 * 存放数据源
 * 
 * @author ffj
 *
 */
@Data
@ConfigurationProperties(prefix = "sharding.jdbc")
public class ShardingMasterSlaveConfig {

    private Map<String, HikariDataSource> dataSources = new HashMap<>();

    private MasterSlaveRuleConfiguration masterSlaveRule;
}

用了 Lombok 显得简便了些

配置数据源:ShardingDataSourceConfig.java

package com.example.shardingjdbc.config;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariDataSource;

import io.shardingjdbc.core.api.MasterSlaveDataSourceFactory;

/**
 * 配置数据源详细信息
 * 
 * @author ffj
 *
 */
@Configuration
@EnableConfigurationProperties(ShardingMasterSlaveConfig.class)
@ConditionalOnProperty({ "sharding.jdbc.data-sources.ds_master.jdbc-url",
        "sharding.jdbc.master-slave-rule.master-data-source-name" })
public class ShardingDataSourceConfig {

    private static final Logger log = LoggerFactory.getLogger(ShardingDataSourceConfig.class);

    @Autowired(required = false)
    private ShardingMasterSlaveConfig shardingMasterSlaveConfig;

    /**
     * 配置数据源
     * 
     * @return
     * @throws SQLException
     */
    @Bean("dataSource")
    public DataSource masterSlaveDataSource() throws SQLException {
        shardingMasterSlaveConfig.getDataSources().forEach((k, v) -> configDataSource(v));
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.putAll(shardingMasterSlaveConfig.getDataSources());
        DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(dataSourceMap,
                shardingMasterSlaveConfig.getMasterSlaveRule(), new HashMap<>());
        log.info("masterSlaveDataSource config complete!!");
        return dataSource;
    }

    /**
     * 可添加数据源一些配置信息
     * 
     * @param dataSource
     */
    private void configDataSource(HikariDataSource dataSource) {
        dataSource.setMaximumPoolSize(20);
        dataSource.setMinimumIdle(5);
    }
}

主要的配置内容就是这些了,接下来我们编写几个方法来测试。

测试

  • 先创建一个实体类

大众测试实体类,我选 UserEntity:

package com.example.shardingjdbc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 测试用户类
 * 
 * @author ffj
 *
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity(name = "user")
public class UserEntity implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -6171110531081112401L;
    @Id
    private int id;
    @Column(length = 32)
    private String name;
    @Column(length = 16)
    private int age;

}

同样,Lombok 不可少。由于之前 application.ymlddl-auto 设置的是 create,所以每次重启程序都会重新生成空表。

  • 我选择 JPA 的原因就是它作为简单测试最适合不过了
package com.example.shardingjdbc.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.shardingjdbc.entity.UserEntity;

public interface UserRepository extends JpaRepository<UserEntity, Integer> {

}

只要继承 JpaRepository 就可以了,我们只需要使用它的基本方法即可。

  • 写个 Controller
package com.example.shardingjdbc.controller;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.shardingjdbc.entity.UserEntity;
import com.example.shardingjdbc.service.UserService;
import com.google.gson.Gson;

/**
 * 用户测试类
 * 
 * @author ffj
 *
 */
@RestController
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping("/save")
    public String saveUser() {
        UserEntity user = new UserEntity(1, "张三", 22);
        userService.saveUser(user);
        return "success";
    }

    @PostMapping("/getUser")
    public String getUsers() {
        List<UserEntity> users = userService.getUsers();
        return new Gson().toJson(users);
    }

}

Service 就不贴了,就是简单调用。

方便查看测试结果,这里用 Gson 来转化为 Json 输出。

  • 启动程序

从上到下看启动日志:

2018-12-27 15:16:12.907  INFO 2940 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2018-12-27 15:16:13.132  INFO 2940 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2018-12-27 15:16:13.141  INFO 2940 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2018-12-27 15:16:13.147  INFO 2940 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2018-12-27 15:16:13.148  INFO 2940 --- [           main] c.e.s.config.ShardingDataSourceConfig    : masterSlaveDataSource config complete

可以看出有两个数据源,没毛病。

Hibernate: drop table if exists user
Hibernate: create table user (id integer not null, age integer, name varchar(32), primary key (id)) engine=InnoDB

程序启动 user 表重建,没毛病。

2018-12-27 15:16:15.198  INFO 2940 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-12-27 15:16:15.550  INFO 2940 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-12-27 15:16:15.587  INFO 2940 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8888 (http) with context path ''
2018-12-27 15:16:15.591  INFO 2940 --- [           main] c.e.s.ShardingJdbcApplication            : Started ShardingJdbcApplication in 5.36 seconds (JVM running for 5.725)
2018-12-27 15:16:15.592  INFO 2940 --- [           main] c.e.s.ShardingJdbcApplication            : ----------启动成功----------

端口为配置文件中指定的 8888,启动成功日志打印,也没毛病,成功。

注意:启动程序前别忘了先自行创建数据库!

现在我们在 slave 库中执行以下提供的 sql 文件,或者自行创建对应表(表结构必须一致,可以先建库然后从主库中复制已生成的表),并在其中添加数据。

/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 50720
Source Host           : localhost:3306
Source Database       : slave

Target Server Type    : MYSQL
Target Server Version : 50720
File Encoding         : 65001

Date: 2018-12-27 16:23:00
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `age` int(11) DEFAULT NULL,
  `name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('2', '23', '李四');

好了,从库中表和数据都有了,进入正题。

  • 测试主库插入数据
    我用的是 Postman :选择 POST 方式,url:localhost:8888/save,点击 Send
Hibernate: insert into user (age, name, id) values (?, ?, ?) // 打印出这条日志

返回 success,成功执行。查看主数据库中 user 表数据,确实插入,成功!

  • 测试从库查询数据
    还是 Postman :选择 POST 方式,url:localhost:8888/getUser,点击 Send
Hibernate: select userentity0_.id as id1_0_, userentity0_.age as age2_0_, userentity0_.name as name3_0_ from user userentity0_ // 打印出这条日志

返回: [{"id":2,"name":"李四","age":23}],数据正确,成功!

以上就是 SpringBoot2.0 + ShardingJDBC 实现数据库读写分离的全部内容了。

参考博文

<think> 嗯,用户想了解如何使用Sharding JDBC实现MySQL读写分离。之前我已经详细解释过Sharding JDBC的原理和分库分表用法,但这次用户聚焦在读写分离这个特定场景上。 从用户引用的资料来看,ta手头应该有几个实际参考资料,特别是引用[2]和[3]明确提到了读写分离实现方式。用户似乎是个Java开发者,正在Spring Boot项目中尝试实现数据库读写分离,可能之前自己实现过多数据源路由,现在想用更优雅的解决方案。 用户可能遇到的核心痛点是:如何在不修改业务代码的前提下,让写操作自动走主库,读操作自动走从库。引用[2]特别强调了Sharding JDBC在这方面的优势——"不需要程序员在程序中关注这个实现"。 让我梳理关键实现步骤: 首先需要物理上配置好MySQL主从同步,这是基础前提(如引用[4]所提)。然后在Spring Boot中配置: 1. 声明主从数据源(如引用[3]的YAML示例) 2. 启用ShardingSphere的读写分离模块(注意不是分片模块) 3. 配置负载均衡策略(比如轮询或随机) 特别要注意的是,用户可能混淆分片和读写分离的配置。在YAML中应该使用shardingsphere.rules.readwrite-splitting而非sharding.tables。引用[3]的配置示例其实混合了两种场景,需要帮用户区分清楚。 另外用户可能关心的实践细节: - 如何避免读操作误路由到主库?需要明确spring.shardingsphere.props.sql-show=true开启日志验证 - 主从延迟如何处理?建议在配置中支持设置读库负载权重,对实时性要求高的读操作可以指定走主库 最后考虑到用户是实践导向,应该在代码示例中展示Controller层调用效果,让ta看到业务代码完全不需要区分数据源这个核心优势。 </think>使用 Sharding JDBC 实现 MySQL 读写分离的完整方法如下(基于 Spring Boot 环境): --- ### 一、核心步骤 #### 1. **MySQL 主从环境准备** - 主库(Master):处理写操作(INSERT/UPDATE/DELETE) - 从库(Slave):处理读操作(SELECT) - 确保主从数据同步正常(通过 binlog 复制) #### 2. **添加 Maven 依赖** ```xml <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> <!-- 使用最新稳定版本 --> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.16</version> <!-- 数据源连接池 --> </dependency> ``` #### 3. **配置读写分离规则(YAML 示例)** ```yaml spring: shardingsphere: # 1. 定义数据源 datasource: names: master, slave1, slave2 # 主库 + 从库 master: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.1.100:3306/db?useSSL=false username: root password: master_pwd slave1: # 从库1 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.1.101:3306/db?useSSL=false username: read_user password: slave_pwd slave2: # 从库2 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.1.102:3306/db?useSSL=false username: read_user password: slave_pwd # 2. 配置读写分离规则 rules: readwrite-splitting: data-sources: rw-ds: # 逻辑数据源名称 type: Static # 静态配置 props: write-data-source-name: master # 写操作指向主库 read-data-source-names: slave1, slave2 # 读操作指向从库 load-balancer-name: round_robin # 读库负载均衡策略 # 3. 定义负载均衡算法 load-balancers: round_robin: type: ROUND_ROBIN # 轮询策略 # 4. 启用SQL日志(调试用) props: sql-show: true ``` #### 4. **业务代码示例** 直接使用 Spring Data JPA 或 MyBatis 操作数据库,**无需修改业务代码**: ```java @RestController public class OrderController { @Autowired private OrderRepository orderRepo; // JPA Repository // 写操作自动路由到主库 @PostMapping("/order") public Order createOrder(@RequestBody Order order) { return orderRepo.save(order); // INSERT 操作 } // 读操作自动路由到从库 @GetMapping("/order/{id}") public Order getOrder(@PathVariable Long id) { return orderRepo.findById(id).orElse(null); // SELECT 操作 } } ``` --- ### 二、关键配置解析 | **配置项** | **作用** | |---------------------------|--------------------------------------------------------------------------| | `write-data-source-name` | 指定写操作的数据源(主库) | | `read-data-source-names` | 指定读操作的数据源列表(从库),多个用逗号分隔 | | `load-balancer-name` | 读操作的负载均衡策略(支持 ROUND_ROBIN/RANDOM/权重) | | `sql-show: true` | 在日志中打印实际路由的 SQL 和数据源,便于调试 | --- ### 三、高级特性 1. **强制读主库** 某些需要实时性的读操作可强制走主库: ```java try (HintManager hint = HintManager.getInstance()) { hint.setWriteRouteOnly(); // 强制后续操作走主库 orderRepo.findByStatus("PENDING"); } ``` 2. **从库故障转移** 若某个从库宕机,Sharding JDBC 会自动跳过故障节点,将请求路由到其他从库[^2]。 3. **负载均衡策略** - `ROUND_ROBIN`:轮询(默认) - `RANDOM`:随机选择 - 自定义权重(如给高性能从库更高权重) --- ### 四、验证读写分离 观察日志输出(开启 `sql-show: true`): ```log # 写操作日志 Logic SQL: INSERT INTO orders (...) VALUES (...) Actual SQL: master ::: INSERT INTO orders (...) VALUES (...) # 读操作日志 Logic SQL: SELECT * FROM orders WHERE id=? Actual SQL: slave1 ::: SELECT * FROM orders WHERE id=? # 第一次请求 Actual SQL: slave2 ::: SELECT * FROM orders WHERE id=? # 第二次请求(轮询) ``` --- ### 注意事项 1. **主从延迟问题** 写后立即读可能因主从延迟获取旧数据,可通过**强制读主库**解决[^1]。 2. **事务处理** - 跨多个库的写操作需用分布式事务(如 Seata) - 单库事务由本地事务保证 3. **版本兼容性** 确保 ShardingSphere 版本与 Spring Boot 兼容(最新版本支持 Spring Boot 3.x)[^4]。 > 通过以上配置,Sharding JDBC 会自动将写操作路由到主库,读操作按负载均衡策略分发到从库,业务代码无需感知底层数据源变化[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值