JAVA 关于分库分表的学习和DEMO

在考虑分表分库之前还有不少的优化手段
第一 优化你的sql和索引
第二 加缓存,memcached,redis
第三 数据库读写分离

源码地址
https://github.com/LH-0811/sharding-sphere-demo
如果有错误 或者不足的地方 希望大家能指出谢谢!

关于这个方面的学习,只是以备不时之需。系统的过度设计是一个浪费精力和资源的事情。系统应当随着数据和业务的增多逐步生长。但是要领先业务。以备业务的爆发式增长

1、关于分表(这里指的肯定是水平分表)

在我的思路中 如果要实现分表,首先要考虑每条记录以什么样的规则来区分放到哪一张表中。最直接的方式就是以某个字段或者某些字段通过某种算法来得出一个结果,对应到一张表中。

假设user 表。字段 有 id(int) , name(varchar) , age(int)

1.1增加数据

如果就以id作为分表因子分两张表。那可以定义规则 id % 2 得出结果 0 或者1 这里就可以对应两张表 user_0 user_1 然后对应的将这条数据插入到表中。
假设id 是 1
1%2 = 1
所以要执行的sql

INSERT INTO `user_1` VALUES (1, 'test1', 10);
1.2查询数据

如果要做数据查询 。依然还是要确定要操作那张表。
依然以 id%2 的结果来确定操作那个表
比如 id = 2
2 % 2 = 0
所以执行的sql

select * from 'user_0' where id = 2;

2、关于分库

这里假设有两个 库 db0 db1
之后要做的事情就是 定义分库规则。

借鉴分表的思路 就可以用id做因子 然后 id%2 做规则确定操作那个数据库。
如果数据中的user表依然要分 user_0 user_1
那可以制定 user.age 作为分表因子 user.age%2 做规则。具体的思路跟上面的分表一样。

分库的时候 就是需要两个规则 来确定需要操作的 库 和 表。

3、关于分表分库的应用

我这个菜鸡,假设要实践,会有不小的麻烦 首先一点就是代码的入侵太严重。之前使用的

select * from user where id = ?;

就要修改为

if(id%2 == 0){
select * from user_0 where id = ?;
}else{
select * from user_1 where id = ?;
}

而且 如果某一天 变成了分三张表就要对应的修改代码。这里的举例是最简单的语句。项目中肯定不会这么简单。所以一旦修改了分表规则,那将是噩梦般的工作量。而且ORM框架几乎就是不可用了。

4、关于分表分库的应用 中间件的查找和对比

网上关于这方面的资料我找了些。引用https://www.cnblogs.com/wangzhongqiu/p/7100332.html

反正我没啥耐心意义去自己测试这些中间件。基本上选定了两个方案。mycat,Sharding-JDBC

1>Cobar 是提供关系型数据库(MySQL)分布式服务的中间件,它可以让传统的数据库得到良好的线性扩展,并看上去还是一个数据库,对应用保持透明。

Cobar以Proxy的形式位于前台应用和实际数据库之间,对前台的开放的接口是MySQL通信协议,将前台SQL语句变更并按照数据分布规则发到合适的后台数据分库,再合并返回结果,模拟单库下的数据库行为。

Cobar属于中间层方案,在应用程序和MySQL之间搭建一层Proxy。中间层介于应用程序与数据库间,需要做一次转发,而基于JDBC协议并无额外转发,直接由应用程序连接数据库,

性能上有些许优势。这里并非说明中间层一定不如客户端直连,除了性能,需要考虑的因素还有很多,中间层更便于实现监控、数据迁移、连接管理等功能。

Cobar属于阿里B2B事业群,始于2008年,在阿里服役3年多,接管3000+个MySQL数据库的schema,集群日处理在线SQL请求50亿次以上。

由于Cobar发起人的离职,Cobar停止维护。后续的类似中间件,比如MyCAT建立于Cobar之上,包括现在阿里服役的RDRS其中也复用了Cobar-Proxy的相关代码。

2>MyCAT是社区爱好者在阿里cobar基础上进行二次开发,解决了cobar当时存 在的一些问题,并且加入了许多新的功能在其中。目前MyCAT社区活 跃度很高,

目前已经有一些公司在使用MyCAT。总体来说支持度比 较高,也会一直维护下去,发展到目前的版本,已经不是一个单纯的MySQL代理了,

它的后端可以支持MySQL, SQL Server, Oracle, [DB2](http://www.2cto.com/database/DB2/), PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。

MyCAT是一个强大的数据库中间件,不仅仅可以用作读写分离,以及分表分库、容灾管理,而且可以用于多租户应用开发、云平台基础设施,让你的架构具备很强的适应性和灵活性,

借助于即将发布的MyCAT只能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表隐射到不同存储引擎上,而整个应用的代码一行也不用改变。

MyCAT是在Cobar基础上发展的版本,两个显著提高:后端由BIO改为NIO,并发量有大幅提高; 增加了对Order By, Group By, Limit等聚合功能

(虽然Cobar也可以支持Order By, Group By, Limit语法,但是结果没有进行聚合,只是简单返回给前端,聚合功能还是需要业务系统自己完成)

3>TDDL是Tabao根据自己的业务特点开发了(Tabao Distributed Data Layer, 外号:头都大了)。主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,

它是一个基于集中式配置的jdbc datasourcce实现,具有主备,读写分离,动态数据库配置等功能。

TDDL并非独立的中间件,只能算作中间层,处于业务层和JDBC层中间,是以Jar包方式提供给应用调用,属于JDBC Shard的思想。

TDDL源码:[https://github.com/alibaba/tb_tddl](https://github.com/alibaba/tb_tddl) 
TDDL复杂度相对较高。当前公布的文档较少,只开源动态数据源,分表分库部分还未开源,还需要依赖diamond,不推荐使用。

4>DRDS是阿里巴巴自主研发的分布式数据库服务(此项目不开源),DRDS脱胎于阿里巴巴开源的Cobar分布式数据库引擎,吸收了Cobar核心的Cobar-Proxy[源码](http://www.2cto.com/ym/),

实现了一套独立的类似MySQL-Proxy协议的解析端,能够对传入的SQL进行解析和处理,对应用程序屏蔽各种复杂的底层DB拓扑结构,获得单机数据库一样的使用体验,

同时借鉴了淘宝TDDL丰富的分布式数据库实践经验,实现了对分布式Join支持,SUM/MAX/COUNT/AVG等聚合函数支持以及排序等函数支持,

通过异构索引、小表广播等解决分布式数据库使用场景下衍生出的一系列问题,最终形成了完整的分布式数据库方案。

5>Atlas是一个位于应用程序与MySQL之间的*基于MySQL协议的数据中间层项目*,**它是在mysql-proxy 0.8.2版本上对其进行优化,**360团队基于mysql proxy 把lua用C改写,****

*它实现了MySQL的客户端和服务端协议,**作为服务端与应用程序通讯,同时作为客户端与MySQL通讯。它对应用程序屏蔽了DB的细节。*

*Altas不能实现分布式分表,所有的字表必须在同一台DB的同一个DataBase里且所有的字表必须实现建好,Altas没有自动建表的功能。*

*原有版本是不支持分库分表, 目前已经放出了分库分表版本。在网上看到一些朋友经常说在高并 发下会经常挂掉,如果大家要使用需要提前做好测试。*

6>DBProxy是美团点评DBA团队针对公司内部需求,在奇虎360公司开源的Atlas做了很多改进工作,形成了新的高可靠、高可用企业级数据库中间件

其特性主要有:读写分离、负载均衡、支持分表、IP过滤、sql语句黑名单、DBA平滑下线DB、从库流量配置、动态加载配置项

项目的Github地址是[https://github.com/Meituan-Dianping/DBProxy](https://github.com/Meituan-Dianping/DBProxy)

7>sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问。

Sharding-JDBC是继dubbox和elastic-job之后,ddframe系列开源的第3个项目。

Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零:

*   可适用于任何基于Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。
*   可基于任何第三方的数据库连接池,如DBCP、C3P0、 BoneCP、Druid等。
*   理论上可支持任意实现JDBC规范的数据库。虽然目前仅支持MySQL,但已有支持Oracle、SQLServer等数据库的计划。

Sharding-JDBC定位为轻量Java框架,使用客户端直连数据库,以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖,DBA也无需改变原有的运维方式。

Sharding-JDBC分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。

SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持Binding Table以及笛卡尔积表查询。

5、mycat的调研。。

http://www.mycat.io/
具体不多做评价了 这个中间件确实是厉害。

6、Sharding-JDBC的调研

最后 选择了Sharding-JDBC。为啥不选mycat 我也想用啊 不太敢。怕万一哪天就商业化了。

最终 Sharding-JDBC 其实可以满足基本的需要 量级也比较轻

7、Sharding-JDBC DEMO

源码地址
https://github.com/LH-0811/sharding-sphere-demo

准备两个数据库


image.png

这里为了做一些测试 在db1中添加一个分数表

/*
 Navicat Premium Data Transfer

 Source Server         : 192.168.199.127_3316
 Source Server Type    : MySQL
 Source Server Version : 50724
 Source Host           : 192.168.199.127:3316
 Source Schema         : db0

 Target Server Type    : MySQL
 Target Server Version : 50724
 File Encoding         : 65001

 Date: 01/06/2019 10:17:49
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user_0
-- ----------------------------
DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;



-- ----------------------------
-- Table structure for user_1
-- ----------------------------
DROP TABLE IF EXISTS `user_1`;
CREATE TABLE `user_1` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for score
-- ----------------------------
DROP TABLE IF EXISTS `score`;
CREATE TABLE `score` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `score` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Table structure for user_0
-- ----------------------------
DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for user_1
-- ----------------------------
DROP TABLE IF EXISTS `user_1`;
CREATE TABLE `user_1` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建一个springboot程序 然后修改配置文件
,这个配置文件的来源是https://mp.weixin.qq.com/s/M0e5u8V_c2sv6aYJHl0Y6Q

# 数据源 db0,db1
sharding.jdbc.datasource.names=db0,db1
# 第一个数据库
sharding.jdbc.datasource.db0.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.db0.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.db0.jdbc-url=jdbc:mysql://192.168.199.127:3316/db0?characterEncoding=utf-8
sharding.jdbc.datasource.db0.username=root
sharding.jdbc.datasource.db0.password=123456

# 第二个数据库
sharding.jdbc.datasource.db1.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.db1.jdbc-url=jdbc:mysql://192.168.199.127:3326/db1?characterEncoding=utf-8
sharding.jdbc.datasource.db1.username=root
sharding.jdbc.datasource.db1.password=123456

# 水平拆分的数据库(表) 配置分库 + 分表策略 行表达式分片策略
# 分库策略
sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=id
sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=db$->{id % 2}

# 分表策略 其中user为逻辑表 分表主要取决于age行
sharding.jdbc.config.sharding.tables.user.actual-data-nodes=db$->{0..1}.user_$->{0..1}
sharding.jdbc.config.sharding.tables.user.table-strategy.inline.sharding-column=age
# 分片算法表达式
sharding.jdbc.config.sharding.tables.user.table-strategy.inline.algorithm-expression=user_$->{age % 2}

sharding.jdbc.config.sharding.tables.score.actual-data-nodes=db1.score


# 主键 UUID 18位数 如果是分布式还要进行一个设置 防止主键重复
#sharding.jdbc.config.sharding.tables.user.key-generator-column-name=id

# 打印执行的数据库以及语句
sharding.jdbc.config.props..sql.show=true
spring.main.allow-bean-definition-overriding=true

这边的配置文件 几个注意的地方

分库配置

# 水平拆分的数据库(表) 配置分库 + 分表策略 行表达式分片策略
# 分库策略 指定分库的因子 这里使用id来分库
sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=id
# 这里是通过id来计算 应该操作那个数据库
sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=db$->{id % 2}


分表配置

# 分表策略 其中user为逻辑表 分表主要取决于age行

#这里是在分库的基础上 添加了分表 
#sharding.jdbc.config.sharding.tables.user 是指定逻辑表user 对应的物理表 在那个数据库中的那几张表
sharding.jdbc.config.sharding.tables.user.actual-data-nodes=db$->{0..1}.user_$->{0..1}

# 这里是指定user表的分表 是通过那个因子来计算
sharding.jdbc.config.sharding.tables.user.table-strategy.inline.sharding-column=age
# 分片算法表达式 
sharding.jdbc.config.sharding.tables.user.table-strategy.inline.algorithm-expression=user_$->{age % 2}

# 如果不指定逻辑表 score 对应在db1中的score sharding.jdbc中间件会默认找db0 db1 中的score 但是db0中并没有创建这个表 所以这里就指定他 不找db0中的表
sharding.jdbc.config.sharding.tables.score.actual-data-nodes=db1.score

这里贴一下pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lh.sharding-sphere</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sharding-sphere-demo</name>
    <description>sharding-sphere jdbc 分库分表测试demo</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- https://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>


        <!-- for spring boot -->

        <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- for spring namespace -->

        <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-namespace</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!--shardingsphere end-->


        <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.10</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Model

@Data
@Table(name = "user")
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @Id
    @KeySql(useGeneratedKeys = true)
    private Integer id;

    private String name;

    private Integer age;

}


@Data
@Table(name = "score")
@NoArgsConstructor
@AllArgsConstructor
public class Score {

    @Id
    @KeySql(useGeneratedKeys = true)
    private Integer id;

    private Integer userId;

    private Integer score;

}

Dao

package com.lh.shardingsphere.demo.dao;


import com.lh.shardingsphere.demo.model.Score;
import tk.mybatis.mapper.common.BaseMapper;
import tk.mybatis.mapper.common.ExampleMapper;

public interface ScoreDao extends BaseMapper<Score>, ExampleMapper<Score> {
}

package com.lh.shardingsphere.demo.dao;


import com.lh.shardingsphere.demo.model.User;
import com.lh.shardingsphere.demo.vo.UserScoreVo;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.BaseMapper;
import tk.mybatis.mapper.common.ExampleMapper;

public interface UserDao extends BaseMapper<User>, ExampleMapper<User> {

//     这是个反面的错误!!。。本来一条sql就只能在一个session中执行,
//    只有在两张表在同一个数据库中存在的时候才可以通过join的方式来获取数据
//
//    @Select("SELECT \n" +
//            "u.id as userId,\n" +
//            "s.id as scoreId,\n" +
//            "u.name as name,\n" +
//            "s.score as score\n" +
//            "FROM \n" +
//            "user u \n" +
//            "LEFT JOIN \n" +
//            "score s \n" +
//            "ON u.id = s.user_id \n" +
//            "WHERE u.id = ${userId}")
    @Select("SELECT \n" +
            "u.id as userId,\n" +
            "s.id as scoreId,\n" +
            "u.name as name,\n" +
            "s.score as score\n" +
            "FROM\n" +
            "user_0 u,score s\n" +
            "WHERE u.id = s.user_id AND u.id = ${userId}")
    UserScoreVo selectScoreByUserId(@Param("userId") Integer userId);
}

Service

package com.lh.shardingsphere.demo.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lh.shardingsphere.demo.dao.ScoreDao;
import com.lh.shardingsphere.demo.dao.UserDao;
import com.lh.shardingsphere.demo.model.Score;
import com.lh.shardingsphere.demo.model.User;
import com.lh.shardingsphere.demo.vo.UserScoreVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private ScoreDao scoreDao;


    public void insertUser(User user) {
        userDao.insert(user);
    }


    public User selectUserById(Integer userId) {
        return userDao.selectByPrimaryKey(userId);
    }


    public List<User> selectUserList() {
        Example example = new Example(User.class);
        example.setOrderByClause(" id asc ");
        return userDao.selectByExample(example);
    }


    public List<User> selectByIdAndAge(Integer id, Integer age) {
        User user = new User();
        user.setAge(age);
        user.setId(id);
        return userDao.select(user);
    }

    public List<User> selectByAge(Integer age) {
        User user = new User();
        user.setAge(age);
        return userDao.select(user);
    }

    public List<User> selectByName(String name) {
        User user = new User();
        user.setName(name);
        return userDao.select(user);
    }

    public User selectByOneName(String name) {
        User user = new User();
        user.setName(name);
        return userDao.selectOne(user);
    }

    public List<User> selectByUser(User user) {
        return userDao.select(user);
    }


    public PageInfo<User> pageUser(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        Example example = new Example(User.class);
        example.setOrderByClause(" id asc ");
        example.createCriteria().andLike("name", "%1%");
        List<User> users = userDao.selectByExample(example);
        PageInfo<User> pageInfo = new PageInfo<>(users);
        PageHelper.clearPage();
        return pageInfo;
    }


    public UserScoreVo selectUserScoreByUserId(Integer userId) {

        User user = userDao.selectByPrimaryKey(userId);

        if (user != null) {
            Score score = new Score();
            score.setUserId(userId);
            Score score1 = scoreDao.selectOne(score);

            UserScoreVo userScoreVo = new UserScoreVo();
            userScoreVo.setUserId(user.getId());
            userScoreVo.setScoreId(score1.getId());
            userScoreVo.setName(user.getName());
            userScoreVo.setScore(score1.getScore());
            return userScoreVo;
        } else {
            return null;
        }


    }

    public List<Score> getUserScore(Integer userId) {
        Score score = new Score();
        score.setUserId(userId);
        return scoreDao.select(score);
    }
}


8、demo的测试

package com.lh.shardingsphere.demo.service;

import com.github.pagehelper.PageInfo;
import com.lh.shardingsphere.demo.dao.ScoreDao;
import com.lh.shardingsphere.demo.dao.UserDao;
import com.lh.shardingsphere.demo.model.Score;
import com.lh.shardingsphere.demo.model.User;
import com.lh.shardingsphere.demo.vo.UserScoreVo;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {


    @Autowired
    private UserService userService;

    @Autowired
    private UserDao userDao;

    @Autowired
    private ScoreDao scoreDao;

    // 插入 方法。通过打印的sql执行日志 可以看出
    // id为基数  分配得到 db1 偶数分配到 db0
    // user 中 age 奇数分配到 user_1  偶数分配到user_0
    @Test
    public void insertUser() {


        for (int i = 1; i < 50;i++){
            User user = new User(i,"test"+i,i+11);
            userService.insertUser(user);
        }

        for (int i = 50; i < 100;i++){
            User user = new User(i,"test"+i,i+10);
            userService.insertUser(user);
        }
    }

    // 通过id查询数据 这里也是通过id 确定了数据库
    @Test
    public void selectUserById() {


        User user1 = userService.selectUserById(11);
        log.info(user1.toString());
        User user2 = userService.selectUserById(12);
        log.info(user2.toString());
    }

    // 查询列表 这里是每个数据库中每个表返回值的集合
    // 从结果看出 排序是生效的
    @Test
    public void selectUserList() {
        List<User> users = userService.selectUserList();
        log.info(users.toString());
    }


    // 这里通过 age确定了 是哪张表 但是不确定库 所以两个库都执行了
    @Test
    public void selectByAge(){
        List<User> users = userService.selectByAge(12);
        log.info(users.toString());
        List<User> users1 = userService.selectByAge(13);
        log.info(users1.toString());
    }

    //这里通过 id和age 确定了 库和表 所以就
    @Test
    public void  selectByIdAndAge(){
        List<User> users = userService.selectByIdAndAge(1, 12);
        log.info(users.toString());
    }


    //这里 因为没有指定 id 和 age 所以 是每个库的每个表中搜索结果的集合
    @Test
    public void  selectByName(){
        List<User> test33 = userService.selectByName("test33");
        log.info(test33.toString());
    }

    //这里 如果在数据库中每张表的数据不重复的情况下 可以正常执行
    //如果 任意两个表中 通过都可以通过name获取到数据 就会报错 跟不分库时一样
    @Test
    public void selectByOneName(){
        User user = userService.selectByOneName("test33");
        log.info(user.toString());
    }




    // 这里分页正常进行 条件查询正常进行
    @Test
    public void pageUser(){
        PageInfo<User> pageInfo = userService.pageUser(1, 10);
        for (User user : pageInfo.getList()) {
            log.info(user.getId()+"");
        }
        log.info(pageInfo.toString());
    }




    @Test
    public void selectUserScoreByUserId() {
//        UserScoreVo userScoreVo1 = userService.selectUserScoreByUserId(1);
//        log.info(userScoreVo1.toString());
        UserScoreVo userScoreVo2 = userService.selectUserScoreByUserId(2);
        log.info(userScoreVo2.toString());
    }

    @Test
    public void selectScoreByUserId(){
        List<Score> userScore = userService.getUserScore(1);
        log.info(userScore.toString());
    }


    //这里的事务在数据库中正常进行 如果抛出异常 数据会回滚数据,即使是不在一个数据库中也会分别进行
    @Test
    @Transactional(rollbackFor = Exception.class)
    public void testSingleTableTransactional() throws Exception{
        User user = new User(1,"test111111",2222);
        userDao.updateByPrimaryKey(user);
        throw  new Exception("异常");
    }



    @Test
    @Transactional(rollbackFor = Exception.class)
    public void testTwoTableTransactional() throws Exception{
        User user = new User(2,"test111111",2222);
        userDao.updateByPrimaryKey(user);
        Score score = new Score();
        score.setUserId(1);
        score.setId(1);
        score.setScore(10000);
        scoreDao.updateByPrimaryKey(score);
        throw  new Exception("异常");
    }
}

推荐阅读更多精彩内容