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<UserEntity> getUsers() {
        List<UserEntity> 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<String, DataSource> 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<BindingTableRule> bindingTableRules = new ArrayList<BindingTableRule>();
        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<Long> {

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

    @Override
    public Collection<String> doInSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
        Collection<String> 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<String> doBetweenSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(databaseNames.size());
        Range<Long> 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<Long> {
    @Override
    public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
        for (String each : tableNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    @Override
    public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
        Collection<String> 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<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        Range<Long> 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
啊哦 是不是很輕易就實現了~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章