ShardingSphere 简介

官网:https://shardingsphere.apache.org/index_zh.html Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

注意事项:

支持项:

  • 提供一主多从的读写分离配置,可独立使用,也可配合分库分表使用;
  • 独立使用读写分离支持SQL透传;
  • 同一线程且同一数据库连接内,如有写入操作,以后的读操作均从主库读取,用于保证数据一致性;
  • 基于Hint的强制主库路由。

不支持项:

  • 主库和从库的数据同步;
  • 主库和从库的数据同步延迟导致的数据不一致;
  • 主库双写或多写;
  • 跨主库和从库之间的事务的数据不一致。主从模型中,事务中读写均用主库。

本文目标

Spring Boot 2.x 整合 ShardingSphere 5.x 实现MySQL读写分离(一主二从),
注:MySQL主从复制请参考其他资料自行实现

开发环境

IDEA + JDK11 + Maven + MySQL 8.x + Spring Boot 2.x

MySQL数据准备

  1. 主库(数据的增删改)与从库(数据的查询):
  2. springboot 整合 seata_springboot 整合 seata

  3. 数据库表:
CREATE TABLE `t_user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `user_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户姓名',
  `user_age` tinyint(4) DEFAULT NULL COMMENT '用户年龄',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

为了演示直观,可以先初始化从库数据:

从库1:

springboot 整合 seata_java_02


从库2:

springboot 整合 seata_java_03

代码实操

  1. 创建Spring Boot工程,项目结构
  2. springboot 整合 seata_分布式_04

  3. 引入Maven依赖
    (因为是Spring Boot项目,所以我们优选spring-boot-starter依赖)
<!-- ShardingSphere -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.0.0-alpha</version>
        </dependency>
  1. 配置application.yml
spring:
  shardingsphere:
    # 数据源配置
    datasource:
      common:
        # 数据库驱动类名
        driver-class-name: com.mysql.cj.jdbc.Driver
        # 数据库连接池类名称
        type: com.zaxxer.hikari.HikariDataSource
      # 数据源名称,多数据源以逗号分隔
      names: main,slave1,slave2
      main:
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingsphere-main?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: root
      slave1:
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingsphere-slave1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: root
      slave2:
        jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingsphere-slave2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: root
    rules:
      replica-query:
        # 负载均衡算法配置(只配置一种也可以)
        load-balancers:
          # 轮询算法(此处是枚举)(名称自定义)
          round-robin:
            type: ROUND_ROBIN
            props:
              # 工作机器唯一标识
              worker-id: 1
          # 随机访问算法(此处是枚举)(名称自定义)
          random:
            type: RANDOM
            props:
              # 工作机器唯一标识
              worker-id: 1
        data-sources:
          # 名称(名称自定义)
          main-slave:
            # 写数据源名称
            primary-data-source-name: main
            # 读数据源名称,多个从数据源用逗号分隔
            replica-data-source-names: slave1,slave2
            # 负载均衡算法名称(把枚举的名称填写到这里)
            load-balancer-name: round-robin

    # 属性配置
    props:
      # 展示修改以后的sql语句
      sql-show: true
  1. 业务层代码

UserServiceImpl 类

package com.wjh.service.impl;

import com.wjh.dto.input.UserInputDTO;
import com.wjh.entity.User;
import com.wjh.repository.UserRepository;
import com.wjh.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 用户表 业务层接口实现类
 *
 * @author Jiahai
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initData() {
        Date date = new Date();
        this.userRepository.saveAll(
                List.of(
                        new User().setUserName("张三").setUserAge(16).setCreateTime(date),
                        new User().setUserName("李四").setUserAge(17).setCreateTime(date),
                        new User().setUserName("王五").setUserAge(18).setCreateTime(date),
                        new User().setUserName("喜羊羊").setUserAge(19).setCreateTime(date),
                        new User().setUserName("灰太狼").setUserAge(20).setCreateTime(date)
                )
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(UserInputDTO userInputDTO) {
        this.userRepository.save(
                new User()
                        .setUserName(userInputDTO.getUserName())
                        .setUserAge(userInputDTO.getUserAge())
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer userId) {
        this.userRepository.deleteById(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Integer userId, UserInputDTO userInputDTO) {
        User user = this.findByUserId(userId);
        if (!Objects.equals(user.getUserName(), userInputDTO.getUserName()) || !Objects.equals(user.getUserAge(), userInputDTO.getUserAge())) {
            BeanUtils.copyProperties(user, userInputDTO);
        }
    }

    @Override
    public User findByUserId(Integer userId) {
        return this.userRepository.findById(userId).orElseThrow(() -> new RuntimeException("用户不存在"));
    }

    @Override
    public List<User> list() {
        return this.userRepository.findAll();
    }
}

UserController类

package com.wjh.controller;

import com.wjh.dto.input.UserInputDTO;
import com.wjh.entity.User;
import com.wjh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 用户表 API
 *
 * @author Jiahai
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 初始化数据到数据库中
     */
    @PostMapping("/initData")
    public void initDate() {
        userService.initData();
    }

    /**
     * 新增用户
     *
     * @param userInputDTO 入参
     */
    @PostMapping
    public void save(@RequestBody UserInputDTO userInputDTO) {
        userService.save(userInputDTO);
    }

    /**
     * 根据主键删除用户
     *
     * @param userId 主键
     */
    @DeleteMapping("/{userId}")
    public void delete(@PathVariable Integer userId) {
        userService.delete(userId);
    }

    /**
     * 修改用户
     *
     * @param userId       用户ID
     * @param userInputDTO 入参
     */
    @PutMapping("/{userId}")
    public void update(@PathVariable Integer userId, @RequestBody UserInputDTO userInputDTO) {
        userService.update(userId, userInputDTO);
    }

    /**
     * 根据主键查询
     *
     * @param userId
     * @return
     */
    @GetMapping("/{userId}")
    public User findByUserId(@PathVariable Integer userId) {
        return userService.findByUserId(userId);
    }

    /**
     * 列表查询
     *
     * @return
     */
    @GetMapping("/list")
    public List<User> list() {
        return userService.list();
    }
}
  1. 效果演示(yml中配置了从库采用轮询算法)
    调用初始化数据接口 localhost:8080/user/initData(将对主库操作)

    主库数据:

    调用查询接口 localhost:8080/user/list(将对从库操作)

springboot 整合 seata_mysql_05


再次调用查询接口 localhost:8080/user/list(将对从库操作)

springboot 整合 seata_分布式_06


springboot 整合 seata_分布式_07