Sharding-JDBC 简单应用

作为一种简单的分表分库的中间件,sharding一种完全基于程序的分表分库策略,无需其他的代理服务,是一种能够快速应用在开发中的策略。

本文是对我参加过的一个项目使用sharding的一个简单总结。我们使用的很简单,把原来纯使用程序代码分表的程序替换为sharding,sharding集成到jdbcTemplate中使用。

pom
<!-- 引入sharding-jdbc核心模块 -->
        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-config-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

首先要引入sharding的依赖,之前我们项目使用的是1.4.0,在并发高的时候会产生一些Caused by:java.lang.IndexOutOfBoundsException: Index: 1, Size: 1的异常,应该是1.4.0版本内部有些变量公用导致的,升级1.4.1就没有这个问题了。

sprint-data.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:rdb="http://www.dangdang.com/schema/ddframe/rdb"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans.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 
                        http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
                        ">
                        
    <bean id="defaultDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">      
        <property name="driverClassName">
            <value>${datasource.driverClassName}</value>
        </property>
        <property name="url">
            <value>${datasource.url}</value>
        </property>
        <property name="username">
            <value>${datasource.username}</value>
        </property>
        <property name="password">
            <value>${datasource.password}</value>
        </property>
        <property name="maxActive">
           <value>${datasource.maxActive}</value>
        </property>
        <property name="maxIdle">
            <value>${datasource.maxIdle}</value>
        </property>
        <property name="maxWait">
            <value>${datasource.maxWait}</value>
        </property>
        <property name="defaultAutoCommit">
            <value>${datasource.defaultAutoCommit}</value>
        </property>
    </bean>
    
    <bean id="readDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">      
        <property name="driverClassName">
            <value>${datasource.readonly.driverClassName}</value>
        </property>
        <property name="url">
            <value>${datasource.readonly.url}</value>
        </property>
        <property name="username">
            <value>${datasource.readonly.username}</value>
        </property>
        <property name="password">
            <value>${datasource.readonly.password}</value>
        </property>
        <property name="maxActive">
            <value>${datasource.readonly.maxActive}</value>
        </property>
        <property name="maxIdle">
            <value>${datasource.readonly.maxIdle}</value>
        </property>
        <property name="maxWait">
            <value>${datasource.readonly.maxWait}</value>
        </property>
        <property name="defaultAutoCommit">
            <value>${datasource.readonly.defaultAutoCommit}</value>
        </property>
    </bean>
    
    <rdb:master-slave-data-source id="masterSlaveDataSource" master-data-source-ref="defaultDataSource" slave-data-sources-ref="readDataSource" />
    <!-- user_ticket的分表策略 -->
    <rdb:strategy id="userTicketTableStrategy" sharding-columns="username"
        algorithm-class="com.common.sharding.SingleKeyHashShardingAlgorithm" />

    <rdb:data-source id="shardingDataSource">
        <rdb:sharding-rule data-sources="masterSlaveDataSource">
            <rdb:table-rules>
                <rdb:table-rule logic-table="user_ticket" actual-tables="user_ticket_01,user_ticket_02,user_ticket_03,user_ticket_04,user_ticket_05,user_ticket_06,user_ticket_07,user_ticket_08,user_ticket_09,user_ticket_10,user_ticket_11,user_ticket_12,user_ticket_13,user_ticket_14,user_ticket_15,user_ticket_16,user_ticket_17,user_ticket_18,user_ticket_19,user_ticket_20"
                    table-strategy="userTicketTableStrategy" />
            </rdb:table-rules>
        </rdb:sharding-rule>
    </rdb:data-source>

    <!--JdbcTemplate -->
    <bean id="shardingJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource">
            <ref bean="shardingDataSource" />
        </property>
    </bean>
    
    <!--TransactionManager -->
    <bean name="shardingTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="shardingDataSource"></property>
    </bean>

    <!-- 使用annotation定义事务,实际做法就是在类上使用@Transactional注解,这个配置才能开启注解事务支持 -->
    <tx:annotation-driven transaction-manager="shardingTransactionManager" proxy-target-class="true"/>
</beans>

在我们的项目中使用了读写分离,其实就是只实现单库的分表,多库应用也差不多,如果有同道找到相关分库的文章请在下面留言。sharding的使用方式很简单,就是对数据源进行包装,配上分表策略,然后把包装的数据源配到JdbcTemplate和事务中。

            <rdb:table-rules>
                <rdb:table-rule logic-table="user_ticket" actual-tables="user_ticket_01,user_ticket_02,user_ticket_03,user_ticket_04,user_ticket_05,user_ticket_06,user_ticket_07,user_ticket_08,user_ticket_09,user_ticket_10,user_ticket_11,user_ticket_12,user_ticket_13,user_ticket_14,user_ticket_15,user_ticket_16,user_ticket_17,user_ticket_18,user_ticket_19,user_ticket_20"
                    table-strategy="userTicketTableStrategy" />
            </rdb:table-rules>

把20张表合成一个来使用。

<rdb:strategy id="userTicketTableStrategy" sharding-columns="username"
        algorithm-class="com.common.sharding.SingleKeyHashShardingAlgorithm" />

userTicketTableStrategy策略以username做hash分区,具体策略代码如下:

package com.common.sharding;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;

/**
 * 根据单个字段hash来分表的实现
 *
 */
public class SingleKeyHashShardingAlgorithm implements SingleKeyTableShardingAlgorithm<String> {

    /**
     * 
     * allActualTableNames 所有的物理表名;shardingValue 分表的key值属性
     */
    public String doEqualSharding(final Collection<String> allActualTableNames,
            final ShardingValue<String> shardingValue) {
        // 逻辑表名
        String logicTableName = shardingValue.getLogicTableName();
        // 根据比较的值,算出物理分表
        String actualTableName = logicTableName
                + "_"
                + String.format("%02d",
                        (Math.abs(shardingValue.getValue().hashCode()) % allActualTableNames.size()) + 1);
        if (allActualTableNames.contains(actualTableName))
            return actualTableName;

        // 如果没有匹配到相应的物理表名,那一定是有问题的
        throw new UnsupportedOperationException();
    }

    /**
     * 支持分表字段的in表达式
     */
    @Override
    public Collection<String> doInSharding(Collection<String> allActualTableNames,
            ShardingValue<String> paramShardingValue) {
        // in表达式的值对应的数据表
        Set<String> inValueTables = new HashSet<String>();
        Collection<String> inValues = paramShardingValue.getValues();

        String logicTableName = paramShardingValue.getLogicTableName();
        for (String value : inValues) {
            String actualTableName = logicTableName + "_" + value.hashCode() % allActualTableNames.size();
            if (allActualTableNames.contains(actualTableName))
                inValueTables.add(actualTableName);
        }

        if (inValueTables.size() == 0)
            throw new UnsupportedOperationException();

        return inValueTables;
    }

    @Override
    public Collection<String> doBetweenSharding(Collection<String> allActualTableNames,
            ShardingValue<String> paramShardingValue) {
        // 不支持between操作,有需求的时候再实现
        throw new UnsupportedOperationException();
    }
}

使用方式其实和普通jdbctemplate没什么区别,用user_ticket代替所有的分区表。

package com.base.dao;

import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.base.bean.UserTicket;

@Repository
public class UserTicketDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * @param username
     * @param page
     * @return
     * @throws Exception
     */
    public List<UserTicket> getUserTicketDetail(String username, int pageid, int pagesize) throws Exception {
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder
                .append("select * from user_ticket where username = ? and typecode='vod' and displaytime < now() order by id desc limit ?, ?");
        return jdbcTemplate.query(sqlBuilder.toString(), new Object[] {username, (pageid - 1) * pagesize, pagesize},
                new int[] {Types.VARCHAR, Types.INTEGER, Types.INTEGER}, new BeanPropertyRowMapper<UserTicket>(
                        UserTicket.class));
    }

    /**
     * 
     * @param ut
     */
    public void updateUserTicket(UserTicket ut) {
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder
                .append("update user_ticket set username=?, times=?, validtime=?, typecode=?, fromcode=?, createtime=?, usetime=?, channelid=?, status=?, displaytime=?, starttime=? where ticketno=? and username=?");
        Object[] args = new Object[] {ut.getUsername(), ut.getTimes(), ut.getValidtime(), ut.getTypecode(),
                ut.getFromcode(), ut.getCreatetime(), ut.getUsetime(), ut.getChannelid(), ut.getStatus(),
                ut.getDisplaytime(), ut.getStarttime(), ut.getTicketno(), ut.getUsername()};
        int[] types = new int[] {Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR,
                Types.TIMESTAMP, Types.TIMESTAMP, Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP,
                Types.VARCHAR, Types.VARCHAR};
        jdbcTemplate.update(sqlBuilder.toString(), args, types);
    }

    /**
     * @param list
     * @return
     */
    public void batchSaveUserTicket(final List<UserTicket> list) {
        for (int k = 0; k < list.size(); k++) {
            StringBuffer sb = new StringBuffer();
            UserTicket ut = list.get(k);
            sb.append("insert into user_ticket(ticketno, username, validtime, typecode, fromcode, createtime, channelid, status, displaytime, starttime)");
            sb.append("values(");
            sb.append("'").append(ut.getTicketno()).append("'").append(",");
            sb.append("'").append(ut.getUsername()).append("'").append(",");
            sb.append("'").append(new SuperDate(ut.getValidtime()).getDateTimeString()).append("'").append(",");
            sb.append("'").append(ut.getTypecode()).append("'").append(",");
            sb.append("'").append(ut.getFromcode()).append("'").append(",");
            sb.append("'").append(new SuperDate(ut.getCreatetime()).getDateTimeString()).append("'").append(",");
            sb.append(ut.getChannelid()).append(",");
            sb.append(ut.getStatus()).append(",");
            sb.append("'").append(new SuperDate(ut.getDisplaytime()).getDateTimeString()).append("'").append(",");
            sb.append("'").append(new SuperDate(ut.getStarttime()).getDateTimeString()).append("'");
            sb.append(")");
            jdbcTemplate.update(sb.toString());
        }
    }
}

最后提一下迁移过程中发现的坑,虽然说使用基本和原生相同,但还有一些需要注意一下,1.表名钱不要加上库名,原生的情况加库名,不加库名其实是一样的,但使用shareding的表就会报错;2.shareding是不支持jdbctemplate的批量修改操作的。

欢迎大家指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容