SpringBoot集成sharding-jdbc学习笔记与demo实现

昨天学习公司项目的时候,发现公司项目实现了数据库分库分表的组件,我们只需要自己覆盖使用到的分库或者分表策略方法即可。自己对数据库还算比较感兴趣,那么就决定也研究研究他们是如何实现的~
网上一搜,找到很多借助Mycat中间件实现的,这种方法是手动在数据库建立多张表,然后在mycat中进行逻辑判断来操作对应不同的表或者实现表的关联。然后再找一找,发现原来SpringBoot也可以通过集成sharding-jdbc这个jar包来实现,这个jar包呢,是在代码层面对这些逻辑控制的,使用这个依赖,只需要配置分表分库的策略,即可简单实现分库分表操作。
于是,参考一位大神的demo链接https://www.jianshu.com/p/74c02a2a89de,在此基础上添加了更新、删除方法以及实现生成表的全局Id,以保持数据的唯一性的功能,从而我的完成了分库分表操作demo(代码链接:https://pan.baidu.com/s/1OWZOEm3nmFRopxCsRMy2kQ,
提取码:p1w5 ),本文列出主要代码如下:
1、建表脚本:
DROP TABLE IF EXISTS `t_order_0`;
CREATE TABLE `t_order_0` (
  `id` varchar(20) NOT NULL COMMENT '主键id',
  `order_id` varchar(32) DEFAULT NULL COMMENT '顺序编号',
  `user_id` varchar(32) DEFAULT NULL COMMENT '用户编号',
  `userName` varchar(32) DEFAULT NULL COMMENT '用户名',
  `passWord` varchar(32) DEFAULT NULL COMMENT '密码',
  `user_sex` varchar(32) DEFAULT NULL,
  `nick_name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、配置文件 application.properties
#使用user_id作为分库列;使用order_id作为分表列
#datasource配置

#data source1
spring.datasource.test1.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test1.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.test1.username=root
spring.datasource.test1.password=1234

#data source2
spring.datasource.test2.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test2.url=jdbc:mysql://127.0.0.1:3306/test2
spring.datasource.test2.username=root
spring.datasource.test2.password=1234
9、UserController.java
package com.wj.controller;

import com.wj.entity.UserEntity;
import com.wj.entity.UserSexEnum;
import com.wj.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

@Service
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService user1Service;

    @RequestMapping("/getUsers")
    public List getUsers() {
        List users=user1Service.getUsers();
        return users;
    }
    @RequestMapping("/getUserById")
    public UserEntity getUserById(@RequestParam(value = "id") String id) {
        UserEntity user=user1Service.getUserById();
        return user;
    }
    //测试insert
    @RequestMapping(value="add")
    public String insertTransactional(
                                      @RequestParam(value = "user_id") Long user_id,
                                      @RequestParam(value = "order_id") Long order_id,
                                      @RequestParam(value = "nickName") String nickName,
                                      @RequestParam(value = "passWord") String passWord,
                                      @RequestParam(value = "userName") String userName
    ) {
        UserEntity user2 = new UserEntity();
        String nowTime = getId();//注意:此处使用当前毫秒级时间拼成唯一序列作为id,保证id的唯一性
        user2.setId(nowTime);
        user2.setUser_id(user_id);
        user2.setOrder_id(order_id);
        user2.setNickName(nickName);
        user2.setPassWord(passWord);
        user2.setUserName(userName);
        user2.setUserSex(UserSexEnum.WOMAN);
        user1Service.insertTransactional(user2);
        return "insert success!";
    }
    //测试update
    @RequestMapping(value="update")
    public String updateTransactional(@RequestParam(value = "id") String id,
                                      @RequestParam(value = "user_id") Long user_id,
                                      @RequestParam(value = "order_id") Long order_id,
                                      @RequestParam(value = "nickName") String nickName,
                                      @RequestParam(value = "passWord") String passWord,
                                      @RequestParam(value = "userName") String userName
    ) {
        UserEntity user2 = new UserEntity();
        user2.setId(id);
        user2.setUser_id(user_id);
        user2.setOrder_id(order_id);
        user2.setNickName(nickName);
        user2.setPassWord(passWord);
        user2.setUserName(userName);
        user2.setUserSex(UserSexEnum.WOMAN);
        user1Service.updateTransactional(user2);
        return "update success!";
    }

    //测试delete
    @RequestMapping(value="delete")
    public String delete(@RequestParam(value = "id") Long id
    ) {
        user1Service.deleteById(id);
        return "delete success!";
    }

    private static String getId() {
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");//设置日期格式
        return df.format(new Date());
    }
}
接下来进入正式的数据库配置以及分库分表策略配置部分:
3、DataSourceConfig
package com.wj.config;
import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSourceFactory;
import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 *  数据源及分表配置
 *
 */
@Configuration
@MapperScan(basePackages = "com.wj.mapper", sqlSessionTemplateRef  = "test1SqlSessionTemplate")
public class DataSourceConfig {


    /**
     * 配置数据源0,数据源的名称最好要有一定的规则,方便配置分库的计算规则
     * @return
     */
    @Bean(name="dataSource0")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    public DataSource dataSource0(){
        return DataSourceBuilder.create().build();
    }
    /**
     * 配置数据源1,数据源的名称最好要有一定的规则,方便配置分库的计算规则
     * @return
     */
    @Bean(name="dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource dataSource1(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置数据源规则,即将多个数据源交给sharding-jdbc管理,并且可以设置默认的数据源,
     * 当表没有配置分库规则时会使用默认的数据源
     * @param dataSource0
     * @param dataSource1
     * @return
     */
    @Bean
    public DataSourceRule dataSourceRule(@Qualifier("dataSource0") DataSource dataSource0,
                                         @Qualifier("dataSource1") DataSource dataSource1){
        Map dataSourceMap = new HashMap<>(); //设置分库映射
        dataSourceMap.put("dataSource0", dataSource0);
        dataSourceMap.put("dataSource1", dataSource1);
        return new DataSourceRule(dataSourceMap, "dataSource0"); //设置默认库,两个库以上时必须设置默认库。默认库的数据源名称必须是dataSourceMap的key之一
    }

    /**
     * 配置数据源策略和表策略,具体策略需要自己实现
     * @param dataSourceRule
     * @return
     */
    @Bean
    public ShardingRule shardingRule(DataSourceRule dataSourceRule){
        //具体分库分表策略
        TableRule orderTableRule = TableRule.builder("t_order")
                .actualTables(Arrays.asList("t_order_0", "t_order_1"))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
                .dataSourceRule(dataSourceRule)
                .build();

        //绑定表策略,在查询时会使用主表策略计算路由的数据源,因此需要约定绑定表策略的表的规则需要一致,可以一定程度提高效率
        List bindingTableRules = new ArrayList();
        bindingTableRules.add(new BindingTableRule(Arrays.asList(orderTableRule)));
        return ShardingRule.builder()
                .dataSourceRule(dataSourceRule)
                .tableRules(Arrays.asList(orderTableRule))
                .bindingTableRules(bindingTableRules)
                .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
                .build();
    }

    /**
     * 创建sharding-jdbc的数据源DataSource,MybatisAutoConfiguration会使用此数据源
     * @param shardingRule
     * @return
     * @throws SQLException
     */
    @Bean(name="dataSource")
    public DataSource shardingDataSource(ShardingRule shardingRule) throws SQLException {
        return ShardingDataSourceFactory.createDataSource(shardingRule);
    }

    /**
     * 需要手动配置事务管理器
     * @param dataSource
     * @return
     */
    @Bean
    public DataSourceTransactionManager transactitonManager(@Qualifier("dataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
4、分库策略的实现
通过实现SingleKeyDatabaseShardingAlgorithm接口,并重写对应的方法实现分库策略。
该demo的分库策略为:user_id % 2 = 0的数据存储到test ,为1的数据存储到test2。
package com.wj.config;

import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
import com.google.common.collect.Range;

import java.util.Collection;
import java.util.LinkedHashSet;

/**
 * 分库策略的简单实现
 * user_id % 2 = 0的数据存储到test ,为1的数据存储到test2,
 */
public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm {

    @Override
    public String doEqualSharding(Collection databaseNames, ShardingValue shardingValue) {
        for (String each : databaseNames) {
            if (each.endsWith(Long.parseLong(shardingValue.getValue().toString()) % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    @Override
    public Collection doInSharding(Collection databaseNames, ShardingValue shardingValue) {
        Collection result = new LinkedHashSet<>(databaseNames.size());
        for (Long value : shardingValue.getValues()) {
            for (String tableName : databaseNames) {
                if (tableName.endsWith(value % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    @Override
    public Collection doBetweenSharding(Collection databaseNames, ShardingValue shardingValue) {
        Collection result = new LinkedHashSet<>(databaseNames.size());
        Range range =  shardingValue.getValueRange();
        for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : databaseNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }
}
5、分表策略的实现
通过实现接口SingleKeyTableShardingAlgorithm并重写对应方法进行实现分表策略。
该demo的分表策略为:order_id % 2 = 0的数据存储到表order_0 ,为1的数据存储到表order_1,
package com.wj.config;

import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
import com.google.common.collect.Range;

import java.util.Collection;
import java.util.LinkedHashSet;
/**
 * 分表策略的基本实现
 * order_id % 2 = 0的数据存储到表order_0 ,为1的数据存储到表order_1,
 */
public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm {
    @Override
    public String doEqualSharding(Collection tableNames, ShardingValue shardingValue) {
        for (String each : tableNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    @Override
    public Collection doInSharding(Collection tableNames, ShardingValue shardingValue) {
        Collection result = new LinkedHashSet<>(tableNames.size());
        for (Long value : shardingValue.getValues()) {
            for (String tableName : tableNames) {
                if (tableName.endsWith(value % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    @Override
    public Collection doBetweenSharding(Collection tableNames, ShardingValue shardingValue) {
        Collection result = new LinkedHashSet<>(tableNames.size());
        Range range = shardingValue.getValueRange();
        for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : tableNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }
}

二、测试

1、插入数据
在网页中分别输入地址,测试如下功能。
http://localhost:8080/user/add?user_id=12&order_id=7&nickName=xiaohua22&passWord=123456&userName=xaiohua
2、更新数据
http://localhost:8080/user/update?id=20190925151743797&user_id=12&order_id=7&nickName=xiaomei&passWord=123456&userName=admin
3、删除数据
http://localhost:8080/user/delete?id=20190925151743797
啊哦 是不是很轻易就实现了~

你可能感兴趣的:(Spring,Boot,数据库,sharding-jdbc)