`
QING____
  • 浏览: 2253320 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

基于Mybatis的分表设计

    博客分类:
  • JAVA
 
阅读更多

    基于ORM层面的分表实现,目前已经很多了,本人也尝试进行了开发。本人的设计目的,是希望基于Mybatis框架、通过规则引擎,实现分表策略,在接入层,更少的DAO层代码调整。

    1)分表规则,通过配置驱动,本人基于Apache Commons Digester组件实现,我们在XML中声明“分表”的策略和子表的规模等。

    2)为了对Mybatis层透明,即对于开发者而言,尽可能无需过度关注数据库层面的分表情况,也唔需要在代码层面调整太多;所以,我通过修改Mybatis的源码,实现“表名”参数的渲染。

 

    难点:

    1)规则引擎相关的开发。

    2)Mybatis源码调整,将变量“表名”根据“分表键”进行计算,并将表名变量在sql层面替换。

 

 

一、设计思路

    1、分表组、子表:比如我们表组为“user”,那么它有256张子表,那么子表的名称为:user_0、user_1、user_2....user_255,基于区间表示user_[0,256)。(约定)

    2、分表键:即shard key,我们基于shard key的值,使用指定的“分表策略”,来计算表的索引号。

    3、在Mybatis Statement中表名,通过变量替代,比如${user_group},在渲染SQL之前,需要通过shard key,计算出表的具体名字,然对表名的变量进行赋值操作。

 

二、规则与配置

    1、trouter-rules.xml

<?xml version='1.0'?>
<rules>
    <trouter>
        <router table="user">
            <key>name</key>
            <property>user_group</property>
            <strategy>hash_mod</strategy>
            <delimiter>_</delimiter>
            <size>8</size>
        </router>
    </trouter>
</rules>

    此XML是有格式限制的,格式scheme由下文的解析器确定。

    1)trouter:父节点,全文应该只能有一个。

    2)每个trouter可以有多个router节点,每个router节点表示一个表组。"table"属性表示表组,一个表组中的所有子表,均以此值开头。

    3)key:即shard key,用于分表计算,注意此key的属性需要在Mybatis的statement中,即所有的statement中必须有“key”参与。

1)select id from ${user_group} where name = #{name}
2) insert into ${user_group}(name) values(#{name})

    4)property:即在statement中,哪个属性表示表名,因为表名是个变量,但是变量的名称还是需要指定的。

    5)strategry:分表策略,对shard key使用何种策略计算表名,本实例支持“hash_mod”、“mod”两种,即根据字符串的hashcode取模、或者根据数字类型直接取模。后期可以增加“range”策略。

    6)delimiter:辅助,表示“table”与“分表索引号”组合时,使用的分隔符,假如“table”为“user”、通过shard key计算出的子表索引号为“10”,那么最终的表名为“user_10”。

    7)size:表组中子表的个数,假如有256个子表,那么size为256,那么子表的区域为user_[0,255)。

 

    2、TRouterStrategry.java

    声明分表策略处理器。

public class TRouterStrategy {

    static enum Strategy {

        HASH_MOD("hash_mod"),
        MOD("mod"),
        RANGE("range");//TODO
        public String code;

        Strategy(String code) {
            this.code = code;
        }

        public static Strategy codeOf(String code) {
            if (code == null) {
                return null;
            }
            for (Strategy e : values()) {
                if (e.code.equalsIgnoreCase(code)) {
                    return e;
                }
            }
            return null;
        }
    }

    public static int route(Strategy strategy, Object key, TRouter router) {
        switch (strategy) {
            case HASH_MOD:
                return hashMod(key, router);
            case MOD:
                return mod((Number) key, router);
            default:
                throw new RuntimeException("Strategy is not supported:" + strategy.code);
        }
    }

    public static int hashMod(Object key, TRouter router) {
        int size = router.getSize();
        return Math.abs(key.hashCode() % size);
    }

    public static int mod(Number key, TRouter router) {
        int size = router.getSize();
        return Math.abs(key.intValue() % size);
    }

    public static int range(Number key, TRouter router) {
        throw new RuntimeException("Range strategy is not supported now!");
    }
}

 

    3、TRouterStrategryEnum.java

    声明分表策略的常量。

/**
 * Created by liuguanqing on 17/5/3.
 * 数据库表路由策略,支持两种
 * 1)hash:根据key的hash值进行取模计算,得到所在的表名后缀
 * 2)range:根据key的值域区间,进行计算表名后缀
 * <p>
 * 在hash计算时,如果key为数字类型,则直接取模,如果是其他类型,将根据其hashcode进行取模,
 * 这要求key的hashcode方法是严格的。建议key是原始类型的值,而不是pojo类的对象。
 */
public enum TRouterStrategyEnum {

    HASH("hash"),
    RANGE("range");//
    public String code;

    TRouterStrategyEnum(String code) {
        this.code = code;
    }

    public TRouterStrategyEnum codeOf(String code) {
        if (code == null) {
            return null;
        }
        for (TRouterStrategyEnum e : values()) {
            if (e.code.equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

 

    4、TRouter.java

    对应XML中的<trouter>,用于保存每个节点的数据。

public class TRouter implements Serializable {

    /**
     *
     */
    protected static final String DEFAULT_DELIMITER = "_";//
    protected static final String DEFAULT_STRATEGY = TRouterStrategyEnum.HASH.code;
    private String strategy;
    private String property;
    private TRouterStrategy.Strategy _strategy;
    private String table;
    private String delimiter;//
    private Integer size = 0;//the num of  subtables
    private String key;
    private Map<Integer, String> indexes = new HashMap<Integer, String>();

    public TRouter() {
    }

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    public String getTable() {
        return table;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public String getDelimiter() {
        return delimiter;
    }

    public void setDelimiter(String delimiter) {
        this.delimiter = delimiter;
    }

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
        //初始化索引
        indexes.clear();
        for (int i = 0; i < size; i++) {
            indexes.put(i, table + delimiter + i);
        }
    }

    public String getStrategy() {
        return strategy;
    }

    public void setStrategy(String strategy) {
        _strategy = TRouterStrategy.Strategy.codeOf(strategy);
        if (_strategy == null) {
            throw new RuntimeException("Strategy:" + strategy + " is not valid!");
        }
        this.strategy = strategy;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    /**
     * @param target 根据此target值进行table路由
     * @return
     */
    public String getTable(Object target) {
        if (target == null) {
            throw new NullPointerException("route key cant be null1");
        }
        int index = TRouterStrategy.route(_strategy, target, this);
        return indexes.get(index);
    }

    //check and populate
    public String populate(Object target) {
        if(target == null) {
            return null;
        }
        try {
            int index = TRouterStrategy.route(_strategy,target,this);
            return indexes.get(index);
        } catch (Exception e) {
            //
        }
        return null;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("table:").append(table)
                .append(",delimiter:").append(delimiter)
                .append(",size:").append(size)
                .append(",strategy:").append(strategy)
                .append(",key:").append(key);
        return sb.toString();
    }
}

 

    5、TRouterPool.java

    用于保存全局的分表策略,提供一些简单的策略操作。

public class TRouterPool {

    private final Map<String, TRouter> ruleMap = new HashMap<String, TRouter>(32);

    public TRouterPool() {
    }

    ;

    public void put(TRouter router) {
        if (router.getTable() == null) {
            throw new NullPointerException("Table of rule must be defined!");
        }
        if (router.getKey() == null) {
            throw new IllegalArgumentException("Shard key of rule must be defined");
        }
        if (router.getDelimiter() == null) {
            router.setDelimiter(TRouter.DEFAULT_DELIMITER);
        }
        if (router.getStrategy() == null) {
            router.setStrategy(TRouter.DEFAULT_STRATEGY);
        }
        String property = router.getProperty();
        if (property == null) {
            throw new NullPointerException("Property of rule must be defined!");
        }
        if (ruleMap.containsKey(property)) {
            throw new IllegalArgumentException("Property '" + property + "' has been be defined,it's must be unique!");
        }
        ruleMap.put(router.getProperty(), router);
    }

    public TRouter getRule(String property) {
        return ruleMap.get(property);
    }


    public List<TRouter> getAllRules() {
        return new ArrayList<TRouter>(ruleMap.values());
    }

    /**
     * 从指定的parameters中找到shard key,并计算出对应table名称
     * @param parameters
     * @return
     */
    public Map<String,String> populate(Map<String,Object> parameters) {
        if(parameters == null || ruleMap.isEmpty()) {
            return null;
        }
        Map<String,String> tables = new HashMap<String, String>(12);
        for(Map.Entry<String,TRouter> entry : ruleMap.entrySet()) {
            TRouter router = entry.getValue();
            //如果包含shard key,则计算
            Object target = parameters.get(router.getKey());
            if(target != null) {
                tables.put(entry.getKey(), router.populate(target));
            }
        }
        return tables;
    }

}

 

    6、TRouterParser.java

     支持Spring配置,用于解析指定的XML,并生成TRouterPool对象,此后TRooterPool对象即可被外部组件使用。

public class TRouterParser {

    public static TRouterPool parse(String location) throws Exception {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Digester digester = new Digester();
        digester.setClassLoader(loader);
        final TRouterPool rulePool = new TRouterPool();
        digester.push(rulePool);
        digester.addRuleSet(new ConfigRuleSet());
        digester.parse(loader.getResource(location));
        return rulePool;
    }

    static class ConfigRuleSet extends RuleSetBase {

        @Override
        public void addRuleInstances(Digester digester) {
            //digester.push(TableRulePool.getInstance());
            digester.addObjectCreate("*/trouter/router", TRouter.class.getName());
            digester.addSetProperties("*/trouter/router");
            digester.addSetNext("*/trouter/router", "put");
            digester.addCallMethod("*/trouter/router/property", "setProperty", 0, new String[]{"java.lang.String"});
            digester.addCallMethod("*/trouter/router/size", "setSize", 0, new String[]{"java.lang.Integer"});
            digester.addCallMethod("*/trouter/router/delimiter", "setDelimiter", 0, new String[]{"java.lang.String"});
            digester.addCallMethod("*/trouter/router/strategy", "setStrategy", 0, new String[]{"java.lang.String"});
            digester.addCallMethod("*/trouter/router/key", "setKey", 0, new String[]{"java.lang.String"});
        }
    }


    public static void main(String[] args) throws Exception {
        TRouterPool routerPool = parse("trouter-rules-sample.xml");
        List<TRouter> rules = routerPool.getAllRules();
        for (TRouter item : rules) {
            System.out.println(item);
        }
    }
}

 

三、Mybatis源码修改

    不违背我们的设计初衷,我们希望在使用Mybatis时尽可能的透明,不让开发者感知太多分表的内部实现,即接入分表组件后,原来的代码尽可能少的修改,同时那些不分表的操作也应该能够平滑支持。

    1)$变量

select name from ${user_group}

    根据Mybatis的设计原理,$修饰的变量,在渲染SQL时直接“填充”,变量值不会使用''进行包括。注意这种类型的变量的渲染时机,是在Mybatis解析Statement时渲染。此前,本人曾经尝试使用Mybatis Interceptor来添加parameter的方式渲染,其实这是无法做到的;因为进入interceptor之前,这类变量已经整理和渲染完毕,将不能再次更改。

    2)#变量

    这类变量,是基于JDBC PrepareStatement进行参数设定,完全基于JDBC,所以这部分变量渲染SQL之后,将会使用''进行包含。所以对于“表名”参数,是不能使用#变量的,否则渲染的SQL将会语法错误。

    最终我们经过多次尝试,那么分表的表明,必须使用$变量声明,且需要在MyBatis Statement解析时就应该确定“表名”参数的值。所以我们只能修改Mybatis的源码来支持。

 

    我们修改Mybatis源码以提供如下支持:

    1)可以让Mybatis解析“trouter-rules.xml”,并将解析结果保存在Configuration中。注意“Configuration”类是Mybatis的顶级类,用于保存“sql-config.xml”所有的配置项。

    2)我们需要修改Mybatis Statement渲染的代码,即在渲染$变量时,应该提前将表名的参数进行赋值,即进行分表策略的计算。$变量的解析,是Mybatis根据正则表达式进行。我们通过检查源码,找到了渲染的核心类:DynamicContext.java(org.apache.ibatis.scripting.xmltags)。

 

    1、下载Mybatis源码

     我们通过gitlab,下载Mybatis源码,基于3.4.4版本,并将此源码的核心部分抽离出来,全部复制到我们自己的project中,package的名称不要改变。

 

    2、sqlmap-config.xml(Mybatis配置的声明文件),在文件中增加如下,指定“分表策略”的XML文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties>
        <property name="routerLocation" value="trouter-rules.xml"/>
    </properties>
	<settings>
		<setting name="lazyLoadingEnabled" value="false" />
		<setting name="logImpl" value="SLF4J"/>
                <setting name="routerLocation" value="trouter-rules.xml"/>
	</settings>

</configuration>

 

    声明一个自定义的属性“routerLocation”,值为“trouter-rules.xml”的位置,尽可能与sqlmap-config.xml临近。

 

    3、Configuration.java源码修改(org.apache.ibatis.session.Configuration)

    因为增加了自定的选项,那么需要增加解析的逻辑,这部分工作由Configuration类负责,此外Configuration实例中所有的属性都可以在Mybatis运行期间获取,因为configuration实例的引用将会传递给Statement等;这也是我们为什么需要在Configuration中增加解析“分表策略”的原因。(此外,在DynamicContext中也是可以访问configuration引用)

 

public class Configuration {
	//...增加两个属性
    protected String routerLocation;
    protected TRouterPool routerPool;
    
    public TRouterPool getRouterPool() {
        return routerPool;
    }

    public void setRouterPool(TRouterPool routerPool) {
        this.routerPool = routerPool;
    }

    public void setRouterLocation(String routerLocation) {
        this.routerLocation = routerLocation;
    }

	//
}

 

    4、XMLConfigBuilder.java(org.apache.ibatis.builder.xml)

    此类主要是解析sqlmap-config.xml,所以我们还需要修改此类,以支持对“trouter-rules.xml”解析,在Configuration类中,将使用它解析XML并完成configuration实例的初始化。

public class XMLConfigBuilder extends BaseBuilder {

    private void settingsElement(Properties props) throws Exception {
    	//..其他操作保留,在此方法的结尾之前,增加:
    	configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));

        //解析分表的router
        String routerLocation = props.getProperty("routerLocation","");
        if(!routerLocation.isEmpty()) {
            configuration.setRouterLocation(routerLocation);
            configuration.setRouterPool(parserTRouter(routerLocation));
        }
    }
}

 

    5、DynamicContext.java

    在Configuration实例初始化完毕之后,每次Mybatis的SQL操作(session),都会通过DynamicContext进行参数解析和渲染。对于$参数,Mybatis通过正则表达式匹配和渲染,所以,我们需要修改它的默认行为,在“渲染表名”参数之前,我们就应该从Statement中传递的parameter中,找到“shard key”,并计算表名的值。

public class DynamicContext {

	//修改构造方法
    public DynamicContext(Configuration configuration, Object parameterObject) {
        if (parameterObject != null && !(parameterObject instanceof Map)) {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            bindings = new ContextMap(metaObject);
        } else {
            bindings = new ContextMap(null);
        }

        bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
        bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
        if(parameterObject == null) {
            return;
        }

        TRouterPool routerPool = configuration.getRouterPool();
        if (routerPool == null) {
            return;
        }
        //我们根据shard key,对所有“分表”策略进行应用
        //因为我无法知道当前Statement中使用的哪个“表”,所以
        //我们使用shard key,计算所有分表策略。最终“Statement”中
        //声明的“表名参数”肯定会渲染。
        MetaObject metaObject = configuration.newMetaObject(parameterObject);
        ContextMap current = new ContextMap(metaObject);

        Map<String, String> tableMapper = routerPool.populate(current);
        if (tableMapper != null) {
            bindings.putAll(tableMapper);
        }
    }
    //...
}

 

    在DynamicContext类中,我们使用shard key对所有的分表策略计算,假如你配置了三个“router”,那么将会计算出三个分表值,那么当前Statement的表名也在其中,所以渲染SQL的结果没有问题;之所以这么做,原因是,通过DynamicContext,我无法知道Statemement究竟使用了哪个特定的router,为了简化代码设计,我不如全部router都计算一遍。

 

四、使用

    我们在使用Mybatis进行分表时需要注意几个问题

    1、Statement

select name from ${user_group} where name = #{name}

    1)shard key必须指定,比如name作为“key”,那么name参数必须在parameter中指定。

    2)表名使用$变量,变量的名称需要与trouter-rules.xml中指定的table对应,比如你在<router><property>user_group</property></router>,那么在Statement中,表名的变量必须为“user_group”。

    3)如果操作的表,没有分表,可以直接使用表名,这并不会带来影响。

    4)参与操作的分表,只能有一个,如果有关联查询,那么分表应该作为主表。通常,我们尽可能避免关联子查询。

 

    2、Mybatis DAO设计:原则上,不需要调整太多代码,只需要注意传递的parameter中必须包含shard key。

1、基于map
Map<String,Object> params = new HashMap<>();
params.put("name","zhangsan");
this.sqlsession.selectOne("UserMapper.getByName",params);

2、基于对象
UserDo user = new UserDo();
user.setName("zhangsan");
this.sqlsession.selectOne("UserMapper.getByName",user);

 

 

  • 大小: 73.6 KB
分享到:
评论

相关推荐

    springmvc +mybatis采用策略设计模式基于拦截器实现按年分表

    总结起来,这个项目是关于如何使用SpringMVC和MyBatis框架,结合策略设计模式和拦截器技术,实现一个动态的按年分表策略。这样的设计不仅可以提升系统的性能,还能灵活适应业务需求的变化,是一个典型的面向现代Web...

    Java+Springboot+mybatis+sharding jdbc 实现分库分表

    本项目基于Java、SpringBoot、MyBatis以及ShardingJDBC实现了一个分库分表的解决方案,旨在帮助开发者理解并掌握这一技术。以下是关于这些技术的详细介绍: **Java**: Java是一种广泛使用的面向对象的编程语言,...

    Mybatis分库分表扩展插件

    Mybatis的插件机制基于拦截器(Interceptor)设计模式,允许在执行SQL之前或之后插入自定义逻辑。分库分表插件主要工作在SQL路由阶段,根据特定的规则(如哈希、范围等)对原始SQL进行修改,添加分片信息,确保数据...

    基于mybatis框架,数据库垂直、水平拆分及读写分离实现

    总结来说,基于MyBatis的数据库拆分和读写分离实现,涉及到对MyBatis框架的深入理解和扩展,包括对SqlSessionTemplate的改造、数据库拆分策略的设计(如垂直拆分和水平拆分的一致性哈希)、以及读写分离的路由机制。...

    基于mybatis-mate的mp企业级模块设计源码

    该源码项目是一个基于mybatis-mate的企业级模块设计,涵盖了207个文件,其中包含110个Java源文件、28个SQL脚本、20个XML配置、17个Gradle构建文件、15个YAML配置文件、5个PDF文档、2个Markdown文件、2个JAR包文件、1...

    spring动态数据源+mybatis分库分表

    "spring动态数据源+mybatis分库分表"是一个针对大型数据库场景的解决方案,它利用Spring框架的动态数据源功能和MyBatis的SQL映射能力,实现数据库的透明化分片。以下是这个主题的详细知识点: 1. **Spring动态数据...

    sharding + mybatis-plus 分库分表

    在Java开发领域,数据库的扩展性和性能优化是一个重要的...在实际项目中,需要根据业务需求和数据规模,合理设计分片策略,并充分利用Sharding-JDBC和Mybatis-Plus提供的工具和特性,以实现高效、稳定的数据库操作。

    基于mybatis-plus+sharding+mysql的分库分表项目源码.zip

    本项目是基于Mybatis-Plus、Sharding-JDBC以及MySQL实现的一个分库分表示例,旨在帮助开发者理解和实践数据库水平扩展。 **Mybatis-Plus** Mybatis-Plus(简称MP)是一个Mybatis的增强工具,在Mybatis的基础上只做...

    spring+mybatis下分表插件shardbatis

    而Shardbatis则是一个专门针对Spring和MyBatis设计的分库分表插件,用于简化数据库分片的实现过程。 首先,我们需要理解Spring的核心概念。Spring是一个开源的Java平台,它提供了全面的软件基础设施服务,用于构建...

    基于mybatis插件实现轻量级分库分表方案-亿级数据mysql存储解决方案-mybatis-sharding.zip

    MyBatis-Sharding 是一种基于 MyBatis 的轻量级分库分表解决方案,它可以帮助开发者有效地解决亿级数据量下的 MySQL 存储问题。下面将详细介绍 MyBatis-Sharding 的核心概念、实现原理以及如何在实际项目中进行应用...

    基于MyBatis Generator的MySQL大小写敏感配置扩展类与分表动态替换表名设计源码

    本项目为MyBatis Generator(MBG)定制扩展,旨在处理MySQL大小写敏感配置及分表时的动态表名替换。包含69个文件,涵盖38个Java源文件、16个XML配置文件、3个Markdown文档、3个JAR包、2个PNG图片、1个Git忽略文件、1...

    基于yml 配置方式 ,实现springBoot+sharding-jdbc+mybatis-plus 实现分库分表,读写分离,以及全局表,子表的配置

    1、基于yml 配置方式 ,实现springBoot+sharding-jdbc+mybatis-plus 实现分库分表,读写分离,以及全局表,子表的配置。 2、实现mybatis-plus 整合到springboot 详细使用请看 测试用例

    spring+mybatis+sharding-jdbc 1.3.1实现分库分表案例(可直接运行)

    本案例基于Spring、MyBatis和Sharding-JDBC 1.3.1版本,提供了一个可以直接运行的分库分表实现,帮助开发者快速理解和实践这一技术。 首先,我们要理解什么是分库分表。分库是指将一个大型数据库拆分为多个小型...

    基于mybatis,springboot开箱即用的读写分离插件.zip

    【标题】"基于mybatis,springboot开箱即用的读写分离插件.zip" 提供了一个集成mybatis和springboot的解决方案,旨在简化数据库读写分离的实现过程。这个插件使得开发者能够快速地在自己的应用中部署读写分离功能,...

    sharding-jdbc开源分表框架整合mybatis-demo

    标题"sharding-jdbc开源分表框架整合mybatis-demo"表明这是一个示例项目,展示了如何将`sharding-jdbc`这个开源的分库分表框架与`MyBatis`持久层框架集成在一起。这通常涉及到数据库水平扩展、数据分布以及事务管理...

    转:Mybatis分库分表扩展插件

    源码分析方面,Mybatis-Sharding的实现基于AOP(面向切面编程)和动态代理技术,通过拦截器链来拦截SQL执行,然后插入分片逻辑。这种方式使得插件可以透明地介入到Mybatis的执行流程中,同时保持了代码的清晰和可...

    基于Mybatis Plus实现代码生成器CodeGenerator

    基于Mybatis Plus实现代码生成器CodeGenerator 基于Mybatis Plus实现代码生成器CodeGenerator是指使用Mybatis Plus框架来实现代码生成器的功能。Mybatis Plus是一个基于Mybatis的增强型ORM框架,提供了许多实用的...

    Mybaits-plus优雅的实现多数据源及分表

    另一种策略是范围分表,基于时间或者其他连续的字段,如年份或月份,将数据分配到不同的表中,这种方式可以更好地保证数据的均衡分布。 在具体实现时,还需要注意以下几点: 1. 事务管理:多数据源环境下,事务的...

    SpringBoot+Mybatis-Plus整合Sharding-JDBC5.1.1实现单库分表【全网最新】.doc

    ### SpringBoot+Mybatis-Plus ...至此,我们已经成功实现了基于SpringBoot、Mybatis-Plus及Sharding-JDBC 5.1.1的单库分表方案。通过上述步骤,可以有效地提升系统的性能和扩展性,为处理高并发场景打下坚实的基础。

Global site tag (gtag.js) - Google Analytics