sharding-jdbc结合mybatis实现分库分表功能

  最近忙于项目已经好久几天没写博客了,前 2 篇文章我给大家介绍了搭建基础 springMvc+mybatis 的 maven 工程,这个简单框架已经可以对付一般的小型项目。但是我们实际项目中会碰到很多复杂的场景,比如数据量很大的情况下如何保证性能。今天我就给大家介绍数据库分库分表的优化,本文介绍 mybatis 结合当当网的 sharding-jdbc 分库分表技术(原理这里不做介绍)

  首先在 pom 文件中引入需要的依赖

<dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-config-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

  二、新建一个 sharding-jdbc.xml 文件,实现分库分表的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd 
                        http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/tx/spring-tx.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.dangdang.com/schema/ddframe/rdb 
                        http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd">
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">rdb:strategy </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="tableShardingStrategy"</span><span style="color: rgba(255, 0, 0, 1)"> sharding-columns</span><span style="color: rgba(0, 0, 255, 1)">="user_id"</span><span style="color: rgba(255, 0, 0, 1)"> algorithm-class</span><span style="color: rgba(0, 0, 255, 1)">="com.meiren.member.common.sharding.MemberSingleKeyTableShardingAlgorithm"</span><span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">rdb:data-source </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="shardingDataSource"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">rdb:sharding-rule </span><span style="color: rgba(255, 0, 0, 1)">data-sources</span><span style="color: rgba(0, 0, 255, 1)">="dataSource"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
        <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">rdb:table-rules</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">rdb:table-rule </span><span style="color: rgba(255, 0, 0, 1)">logic-table</span><span style="color: rgba(0, 0, 255, 1)">="member_index"</span><span style="color: rgba(255, 0, 0, 1)"> actual-tables</span><span style="color: rgba(0, 0, 255, 1)">="member_index_tbl_${[0,1,2,3,4,5,6,7,8,9]}${0..9}"</span><span style="color: rgba(255, 0, 0, 1)">  table-strategy</span><span style="color: rgba(0, 0, 255, 1)">="tableShardingStrategy"</span><span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">rdb:table-rule </span><span style="color: rgba(255, 0, 0, 1)">logic-table</span><span style="color: rgba(0, 0, 255, 1)">="member_details"</span><span style="color: rgba(255, 0, 0, 1)"> actual-tables</span><span style="color: rgba(0, 0, 255, 1)">="member_details_tbl_${[0,1,2,3,4,5,6,7,8,9]}${0..9}"</span><span style="color: rgba(255, 0, 0, 1)">  table-strategy</span><span style="color: rgba(0, 0, 255, 1)">="tableShardingStrategy"</span><span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
        <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">rdb:table-rules</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">rdb:sharding-rule</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">rdb:data-source</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="transactionManager"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.jdbc.datasource.DataSourceTransactionManager"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="dataSource"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="shardingDataSource"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

</beans>

  这里我简单介绍下一些属性的含义,

   <rdb:strategy id="tableShardingStrategy" sharding-columns="user_id" algorithm-class="com.meiren.member.common.sharding.MemberSingleKeyTableShardingAlgorithm"/>  配置分表规则器  sharding-columns:分表规 则 

  依赖的名(根据 user_id 取模分表),algorithm-class: 分表规则的实现类 

  <rdb:sharding-rule data-sources="dataSource"> 这里填写关联数据源(多个数据源用逗号隔开),

  <rdb:table-rule logic-table="member_index" actual-tables="member_index_tbl_${[0,1,2,3,4,5,6,7,8,9]}${0..9}"  table-strategy="tableShardingStrategy"/>  logic-table:逻辑表名(mybatis 中代替的表名)actual-tables

  数据库实际的表名,这里支持 inline 表达式,比如:member_index_tbl_${0..2} 会解析成 member_index_tbl_0,member_index_tbl_1,member_index_tbl_2;member_index_tbl_${[a,b,c]} 会被解析成

    member_index_tbl_a,member_index_tbl_b 和 member_index_tbl_c, 两种表达式一起使用的时候,会采取笛卡尔积的方式:member_index_tbl_${[a,b]}${0..2} 解析为 member_index_tbl_a0,member_index_tbl_a1                                       member_index_tbl_a2,member_index_tbl_b0,member_index_tbl_b1,member_index_tbl_b2;table-strategy: 前面定义的分表规则器;

     三、配置好改文件后,需要修改之前我们的 spring-dataSource 的几个地方,把 sqlSessionFactory 和 transactionManager 原来关联的 dataSource 统一修改为 shardingDataSource(这一步作用就是把数据源全部托管给 sharding 去管理)

  

 四、实现分表(分库)逻辑,我们的分表逻辑类需要实现 SingleKeyTableShardingAlgorithm 接口的三个方法 doBetweenSharding、doEqualSharding、doInSharding

/**
 * 分表逻辑
 * @author zhangwentao
 *
 */
public class MemberSingleKeyTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * sql between 规则
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> Collection&lt;String&gt; doBetweenSharding(Collection&lt;String&gt; tableNames, ShardingValue&lt;Long&gt;<span style="color: rgba(0, 0, 0, 1)"> shardingValue) {
    Collection</span>&lt;String&gt; result = <span style="color: rgba(0, 0, 255, 1)">new</span> LinkedHashSet&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)">(tableNames.size());
    Range</span>&lt;Long&gt; range = (Range&lt;Long&gt;<span style="color: rgba(0, 0, 0, 1)">) shardingValue.getValueRange();
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">long</span> i = range.lowerEndpoint(); i &lt;= range.upperEndpoint(); i++<span style="color: rgba(0, 0, 0, 1)">) {
        Long modValue </span>= i % 100<span style="color: rgba(0, 0, 0, 1)">;
        String modStr </span>= modValue &lt; 10 ? "0" +<span style="color: rgba(0, 0, 0, 1)"> modValue : modValue.toString();
        </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (String each : tableNames) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (each.endsWith(modStr)) {
                result.add(each);
            }
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * sql == 规则
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> String doEqualSharding(Collection&lt;String&gt; tableNames, ShardingValue&lt;Long&gt;<span style="color: rgba(0, 0, 0, 1)"> shardingValue) {
    Long modValue </span>= shardingValue.getValue() % 100<span style="color: rgba(0, 0, 0, 1)">;
    String modStr </span>= modValue &lt; 10 ? "0" +<span style="color: rgba(0, 0, 0, 1)"> modValue : modValue.toString();
    </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (String each : tableNames) {
        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (each.endsWith(modStr)) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> each;
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IllegalArgumentException();
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * sql in 规则
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> Collection&lt;String&gt; doInSharding(Collection&lt;String&gt; tableNames, ShardingValue&lt;Long&gt;<span style="color: rgba(0, 0, 0, 1)"> shardingValue) {

    Collection</span>&lt;String&gt; result = <span style="color: rgba(0, 0, 255, 1)">new</span> LinkedHashSet&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)">(tableNames.size());
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> value : shardingValue.getValues()) {
        Long modValue </span>= value % 100<span style="color: rgba(0, 0, 0, 1)">;
        String modStr </span>= modValue &lt; 10 ? "0" +<span style="color: rgba(0, 0, 0, 1)"> modValue : modValue.toString();
        </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (String tableName : tableNames) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (tableName.endsWith(modStr)) {
                result.add(tableName);
            }
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}

}

五、以上四步,我们就完成了 sharding-jdbc 的搭建,我们可以写一个测试 demo 来检查我们的成果

<select id="getDetailsById" resultType="com.meiren.member.dataobject.MemberDetailsDO"
        parameterType="java.lang.Long">
        select user_id userId ,qq,email from member_details where     user_id =#{userId} limit 1
    </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static final String SERVICE_PROVIDER_XML = "/spring/member-service.xml";
      private static final String BEAN_NAME = "idcacheService";
       
      private ClassPathXmlApplicationContext context = null;
      IdcacheServiceImpl bean = null;
      IdcacheDao idcacheDao;
       
      @Before
      public void before() {
          context= new ClassPathXmlApplicationContext(
                  new String[] {SERVICE_PROVIDER_XML});
         idcacheDao=context.getBean("IdcacheDao", IdcacheDao.class);
      }
       
      @Test
      public void getAllCreditActionTest() {
       // int id = bean.insertIdcache();
          Long s=100l;
        MemberDetailsDO memberDetailsDO=idcacheDao.getDetailsById(s);
        System.out.println("QQ---------------------"+memberDetailsDO.getQq());
      }

  打印 sql 语句,输出结果:QQ-------------------------------------100,证明成功!

  注意点:这次搭建过程中,我有碰到一个小坑,就是执行的时候会报错:,官方文档是有解决方案:引入 <context:property-placeholder location="classpath:/member_service.properties" ignore-unresolvable="true" />  ,引入这行代码的时候,·必须要要把这边管理配配置文件的 bean 删除,换句话说,即 Spring 容器仅允许最多定义一个 PropertyPlaceholderConfigurer(或 <context:property-placeholder/>),其余的会被 Spring 忽略掉(当时搞了半天啊)

小结:这次给大家分享了 sharding-jdbc 的配置是为了解决大数据量进行分库分表的架构,下一张,我将介绍拆分业务所需的 duboo+zookeeper 的配置(分布式),欢迎关注!