spring+mybatis分库分表

上一篇介绍了读写分离,这一篇来说一下分库分表,废话不多说直接上代码。
1.首先配置文件,这里配置三个库

driver=com.mysql.jdbc.Driver
#定义初始连接数
initialSize=0
#定义最大连接数
maxActive=20
#定义最大空闲
maxIdle=20
#定义最小空闲
minIdle=1
#定义最长等待时间
maxWait=60000

jdbc.mysql.url0=jdbc:mysql://localhost:3306/test_02?createDatabaseIfNotExist=true&characterEncoding=utf-8&useUnicode=true
jdbc.mysql.username0=root
jdbc.mysql.password0=root

jdbc.mysql.url1=jdbc:mysql://localhost:3306/test_00?createDatabaseIfNotExist=true&characterEncoding=utf-8&useUnicode=true
jdbc.mysql.username1=root
jdbc.mysql.password1=root

jdbc.mysql.url2=jdbc:mysql://localhost:3306/test_01?createDatabaseIfNotExist=true&characterEncoding=utf-8&useUnicode=true
jdbc.mysql.username2=root
jdbc.mysql.password2=root

在每个库下边建立三个表如下图

Paste_Image.png

2.配置文件写好之后接下来配置数据源




    
    
        
    


    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    


    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    


    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    
    
    
        
            
            
                
                
                
            
        
    

    
        
        
        
        
        
        
        
            
                db0
                db1
                db2
            
        
    

    
    
        
    

    
        
        
    

    
    
        
        
        
    

    
    
        
        
    


动态获取数据源,这个和上篇读写分离一样,也是用treadLocal保证线程安全

package com.wz.dbRouting;

/**
 * 动态数据源实现中KEY的存放工具类
 * 动态数据源实现中KEY的存放工具类:使用treadLocal的方式来保证线程安全
 */
public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal();
    private static final ThreadLocal tableIndexHolder= new ThreadLocal();
    
    
    public static void setDbKey(String dbKey) {
        contextHolder.set(dbKey);
    }

    public static String getDbKey() {
        return (String) contextHolder.get();
    }

    public static void clearDbKey() {
        contextHolder.remove();
    }
    
    public static void setTableIndex(String tableIndex){
        tableIndexHolder.set(tableIndex);
    }
    
    public static String getTableIndex(){
        return (String) tableIndexHolder.get();
    }
    public static void clearTableIndex(){
        tableIndexHolder.remove();
    }
    
    
}
package com.wz.dbRouting.db;


import com.wz.dbRouting.DbContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.logging.Logger;

/**
 * @Description Spring 的动态数据源的实现
 * @Autohr wz
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    public static final Logger logger = Logger.getLogger(DynamicDataSource.class.toString());
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbKey();//获取当前数据源
    }

}

设置一些分库分表需要的参数

package com.wz.dbRouting.bean;

import java.util.List;

/**
 * @Description
 * @Autohr wz
 */
public class RouterSet {

    /**根据字符串*/
    public final static int RULE_TYPE_STR=3;

    public final static int ROUTER_TYPE_DB=0;

    public final static int ROUTER_TYPE_TABLE =1;

    public final static int ROUTER_TYPE_DBANDTABLE=2;

    /**数据库表的逻辑KEY,与数据源MAP配置中的key一致*/
    private List dbKeyArray;

    /**数据库数量*/
    private int dbNumber;
    /**数据表数量*/
    private int tableNumber;
    /**数据表index样式*/
    private String tableIndexStyle;
    /**Id开始*/
    private String routeFieldStart;
    /**Id结束*/
    private String routeFieldEnd;
    /**规则类型*/
    private int ruleType;
    /**路由类型类型*/
    private int routeType;

    public static int getRULE_TYPE_STR() {
        return RULE_TYPE_STR;
    }

    public static int getROUTER_TYPE_DB() {
        return ROUTER_TYPE_DB;
    }

    public static int getROUTER_TYPE_TABLE() {
        return ROUTER_TYPE_TABLE;
    }

    public static int getROUTER_TYPE_DBANDTABLE() {
        return ROUTER_TYPE_DBANDTABLE;
    }

    public List getDbKeyArray() {
        return dbKeyArray;
    }

    public void setDbKeyArray(List dbKeyArray) {
        this.dbKeyArray = dbKeyArray;
    }

    public int getDbNumber() {
        return dbNumber;
    }

    public void setDbNumber(int dbNumber) {
        this.dbNumber = dbNumber;
    }

    public int getTableNumber() {
        return tableNumber;
    }

    public void setTableNumber(int tableNumber) {
        this.tableNumber = tableNumber;
    }

    public String getTableIndexStyle() {
        return tableIndexStyle;
    }

    public void setTableIndexStyle(String tableIndexStyle) {
        this.tableIndexStyle = tableIndexStyle;
    }

    public String getRouteFieldStart() {
        return routeFieldStart;
    }

    public void setRouteFieldStart(String routeFieldStart) {
        this.routeFieldStart = routeFieldStart;
    }

    public String getRouteFieldEnd() {
        return routeFieldEnd;
    }

    public void setRouteFieldEnd(String routeFieldEnd) {
        this.routeFieldEnd = routeFieldEnd;
    }

    public int getRuleType() {
        return ruleType;
    }

    public void setRuleType(int ruleType) {
        this.ruleType = ruleType;
    }

    public int getRouteType() {
        return routeType;
    }

    public void setRouteType(int routeType) {
        this.routeType = routeType;
    }
}

3.下面开始是自定义注解
设置一个路由常量按照userNum来分表

package com.wz.dbRouting.annotation;

/**
 * @Description
 */
public class RouterConstants {

    public static final String ROUTER_FIELD_DEFAULT = "userNum";

    public static final String ROUTER_TABLE_SUFFIX_DEFAULT = "_0000";

}

package com.wz.dbRouting.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description
 * @Autohr wz
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Router {

    String routerField() default RouterConstants.ROUTER_FIELD_DEFAULT;

    String tableStyle() default RouterConstants.ROUTER_TABLE_SUFFIX_DEFAULT;
}

4.接下来是拦截器以及分库分表的规则。
主要思路是:通过拦截器来拦截方法名称以及方法参数,根据参数中的userNum字段,对该字段进行hashcode求余的方式来判断这条记录是存在几库几表。具体规则见代码
拦截器代码

package com.wz.dbRouting;

import com.wz.dbRouting.annotation.Router;
import com.wz.dbRouting.annotation.RouterConstants;
import com.wz.dbRouting.router.RouterUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Description 切面切点 在Router注解的方法执行前执行 切点织入
 * @Autohr wz
 */
@Aspect
@Component
public class DBRouterInterceptor {

    private static final Logger log = LoggerFactory.getLogger(DBRouterInterceptor.class);

    @Autowired
    private DBRouter dBRouter;

    @Pointcut("@annotation( com.wz.dbRouting.annotation.Router)")
    public void aopPoint() {
    }

    @Before("aopPoint()")
    public Object doRoute(JoinPoint jp) throws Throwable {
        
        long t1 = System.currentTimeMillis();
        boolean result = true;
        //根据JoinPoint jp 获取方法名称和参数
        Method method = getMethod(jp);
        Router router = method.getAnnotation(Router.class);
        String routeField = router.routerField();
        Object[] args = jp.getArgs();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                long t2 = System.currentTimeMillis();
                //通过反射得到对象args[i] 的 routeField 字段的值
                String routeFieldValue = BeanUtils.getProperty(args[i],
                        routeField);
                log.debug("routeFieldValue{}" + (System.currentTimeMillis() - t2));
                if (StringUtils.isNotEmpty(routeFieldValue)) {
                    //看这个值是否为默认的分库分表字段,如果是设置库和表的名称
                    if (RouterConstants.ROUTER_FIELD_DEFAULT.equals(routeField)) {
                        //根据hashcode取%
                        dBRouter.doRouteByResource("" + RouterUtils.getResourceCode(routeFieldValue));
                        break;
                    } 
                }
            }
        }
        log.debug("doRouteTime{}" + (System.currentTimeMillis() - t1));
        return result;
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature msig = (MethodSignature) sig;
        return getClass(jp).getMethod(msig.getName(), msig.getParameterTypes());
    }

    private Class getClass(JoinPoint jp)
            throws NoSuchMethodException {
        return jp.getTarget().getClass();
    }

}

具体规则代码

package com.wz.dbRouting;

/**
 * @Description DB路由接口  DB路由器接口,通过调用该接口来自动判断数据位于哪个服务器
 * @Autohr wz
 */
public interface DBRouter {
    /**
     * 进行路由
     * @param fieldId
     * @return
     * @throws
     */
    public String doRoute(String fieldId);


    public String doRouteByResource(String resourceCode);
}

package com.wz.dbRouting.router;

import com.wz.dbRouting.DBRouter;
import com.wz.dbRouting.DbContextHolder;
import com.wz.dbRouting.annotation.RouterConstants;
import com.wz.dbRouting.bean.RouterSet;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.List;

/**
 * @Description 根据指定变量动态切 库和表
 * @Autohr supers【weChat:13031016567】
 */
@Service("dBRouter")
public class DBRouterImpl implements DBRouter {

    private static final Logger log = LoggerFactory.getLogger(DBRouterImpl.class);

    /**
     * 配置列表
     */
    @Autowired
    private List routerSetList;

    public String doRoute(String fieldId) {
        if (StringUtils.isEmpty(fieldId)) {
            throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");
        }
        int routeFieldInt = RouterUtils.getResourceCode(fieldId);
        String dbKey = getDbKey(routerSetList, routeFieldInt);
        return dbKey;
    }

    public String doRouteByResource(String resourceCode) {
        if (StringUtils.isEmpty(resourceCode)) {
            throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");
        }
        int routeFieldInt = Integer.valueOf(resourceCode);
        String dbKey = getDbKey(routerSetList, routeFieldInt);
        return dbKey;
    }


    /**
     * @Description 根据数据字段来判断属于哪个段的规则,获得数据库key
     * @Autohr wz
     */
    private String getDbKey(List routerSets, int routeFieldInt) {
        RouterSet routerSet = null;
        if (routerSets == null || routerSets.size() <= 0) {
            throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");
        }
        String dbKey = null;
        for (RouterSet item : routerSets) {
            if (item.getRuleType() == routerSet.RULE_TYPE_STR) {
                routerSet = item;
                if (routerSet.getDbKeyArray() != null && routerSet.getDbNumber() != 0) {
                    long dbIndex = 0;
                    long tbIndex = 0;
                    //默认按照分库进行计算
                    long mode = routerSet.getDbNumber();
                    //如果是按照分库分表的话,计算
                    if (item.getRouteType() == RouterSet.ROUTER_TYPE_DBANDTABLE && item.getTableNumber() != 0) {
                        mode = routerSet.getDbNumber() * item.getTableNumber();
                        dbIndex = routeFieldInt % mode / item.getTableNumber();
                        tbIndex = routeFieldInt % item.getTableNumber();
                        String tableIndex = getFormateTableIndex(item.getTableIndexStyle(), tbIndex);
                        DbContextHolder.setTableIndex(tableIndex);
                    } else if (item.getRouteType() == RouterSet.ROUTER_TYPE_DB) {
                        mode = routerSet.getDbNumber();
                        dbIndex = routeFieldInt % mode;
                    } else if (item.getRouteType() == RouterSet.ROUTER_TYPE_TABLE) {
                        tbIndex = routeFieldInt % item.getTableNumber();
                        String tableIndex = getFormateTableIndex(item.getTableIndexStyle(), tbIndex);
                        DbContextHolder.setTableIndex(tableIndex);
                    }
                    dbKey = routerSet.getDbKeyArray().get(Long.valueOf(dbIndex).intValue());
                    log.debug("getDbKey resource:{}------->dbkey:{},tableIndex:{},", new Object[]{routeFieldInt, dbKey, tbIndex});
                    DbContextHolder.setDbKey(dbKey);
                }
                break;
            }
        }
        return dbKey;
    }


    /**
     * @Description 此方法是将例如+++0000根式的字符串替换成传参数字例如44 变成+++0044
     * @Autohr supers【weChat:13031016567】
     */
    private static String getFormateTableIndex(String style, long tbIndex) {
        String tableIndex = null;
        DecimalFormat df = new DecimalFormat();
        if (StringUtils.isEmpty(style)) {
            style = RouterConstants.ROUTER_TABLE_SUFFIX_DEFAULT;//在格式后添加诸如单位等字符
        }
        df.applyPattern(style);
        tableIndex = df.format(tbIndex);
        return tableIndex;
    }

    public List getRouterSetList() {
        return routerSetList;
    }

    public void setRouterSetList(List routerSetList) {
        this.routerSetList = routerSetList;
    }
}

现在分库分表的规则就告一段落了,接下来进行单元测试。
5.控制台打印:

log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
db2库 _0003表 的插入结果:1

分库分表成功!

你可能感兴趣的:(spring+mybatis分库分表)