Sharding-JDBC 简单应用

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

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

pom

        
            com.dangdang
            sharding-jdbc-core
            1.4.1
        

        
            com.dangdang
            sharding-jdbc-config-spring
            1.4.1
        

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

sprint-data.xml


                        
          
        
            ${datasource.driverClassName}
        
        
            ${datasource.url}
        
        
            ${datasource.username}
        
        
            ${datasource.password}
        
        
           ${datasource.maxActive}
        
        
            ${datasource.maxIdle}
        
        
            ${datasource.maxWait}
        
        
            ${datasource.defaultAutoCommit}
        
    
    
          
        
            ${datasource.readonly.driverClassName}
        
        
            ${datasource.readonly.url}
        
        
            ${datasource.readonly.username}
        
        
            ${datasource.readonly.password}
        
        
            ${datasource.readonly.maxActive}
        
        
            ${datasource.readonly.maxIdle}
        
        
            ${datasource.readonly.maxWait}
        
        
            ${datasource.readonly.defaultAutoCommit}
        
    
    
    
    
    

    
        
            
                
            
        
    

    
    
        
            
        
    
    
    
    
        
    

    
    

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

            
                
            

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


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 {

    /**
     * 
     * allActualTableNames 所有的物理表名;shardingValue 分表的key值属性
     */
    public String doEqualSharding(final Collection allActualTableNames,
            final ShardingValue 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 doInSharding(Collection allActualTableNames,
            ShardingValue paramShardingValue) {
        // in表达式的值对应的数据表
        Set inValueTables = new HashSet();
        Collection 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 doBetweenSharding(Collection allActualTableNames,
            ShardingValue 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 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.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 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的批量修改操作的。

欢迎大家指正。

你可能感兴趣的:(Sharding-JDBC 简单应用)