Mybatis动态数据源

今天遇到一个业务上的需求,因为线上数据库磁盘空间已经接近3个T,想到的一个解决方案是对线上分库分表的64个库做物理拆分,其中编号1-32库放到一个物理空间,33-64库放到一个物理空间。

网上的方案大致有二种:

1.将不同库操作分开放进不同的mapper,配置两个数据源 

2. 配置动态数据源,使用aop进行动态切换,真正实现动态数据源

很显然我的系统都是同一套mapper对象,不能拆分,因此采用第二种,一般第二种比较多用于实现读写分离,但是这里我用来做拆分分库分表

其中一个系统因为用的当当的sharding-jdbc做的分库分表,直接修改1-32库的数据库url指向新的地址,另外一个因为用的dbcp的基础BasicDataSource,这里给出实现方案:

1. 首先写一个动态数据源对象继承自AbstractRoutingDataSource

public class NewDynamicDataSource extends AbstractRoutingDataSource {
    private final Logger logger = LoggerFactory.getLogger(NewDynamicDataSource.class);
    @Override
    protected Object determineCurrentLookupKey() {
        logger.info(String.format("数据源为 %s", NewDataSourceContextHolder.getDB()));
        return NewDataSourceContextHolder.getDB();
    }
}

2. 然后配置多数据源  其中

mysqlDataSource3和mysqlDataSource4分别是1-32编号的库,33-64编号的库,都是dbcp的Datasource,也可以是DHCP、C3P0、Druid连接池的数据源连接。
 <bean id="dynamicDatasource" class="com.ppdai.realtime.dataservices.dbcp.NewDynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="mysqlDataSource3" key="db1_32"></entry>
                <entry value-ref="mysqlDataSource4" key="db33_64"></entry>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="mysqlDataSource3"></property>
    </bean>

3. 第三步 自定义拆分数据库注解

    这个注解上有三个属性  tableTotalNum:总表数  pertableNum:每个库表数量  

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DBSource {

    String tableTotalNum() default "64";

    String pertableNum() default "1";

    String value() default "db1_32";
}

4. 第四部  数据源设置Holder

  这个Holder用来设置当前线程(当前请求,一个请求一个线程处理)中所使用的datasource 名称,其实这里有个漏洞,如果一个请求需要跨库进行查询,这里是满足不了的,不过我这里业务没有这种情况。

public class DataSourceContextHolder {  
    
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
  
    public static void setDbType(String dbType) {  
        contextHolder.set(dbType);  
    }  
  
    public static String getDbType() {  
        return ((String) contextHolder.get());  
    }  
  
    public static void clearDbType() {  
        contextHolder.remove();  
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        clearDbType();
    }  
    
    
}

5. AOP实现 对查询做切面  

   在有@DBSource注解的方法上做AOP,然后根据方法参数userid做分库分表。逻辑是(userid % 总表数)/ 每个库表数

 @Aspect
@Component
public class DynamicDataSourceAspect {

    @Before("@annotation(com.ppdai.realtime.dataservices.dbcp.DBSource)")
    public void beforeSwitchDS(JoinPoint point){

        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();

        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        Object[] args = point.getArgs();

        String dataSource = NewDataSourceContextHolder.DB_1_32;
        try {

            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);

//             判断是否存在@DS注解
            if (method.isAnnotationPresent(DBSource.class)) {
                DBSource dbSource = method.getAnnotation(DBSource.class);

                if(args != null &&args.length != 0){
                    if(args[0] instanceof Integer)
                    {
                        Integer userid = (Integer) args[0];
                        if(((userid % Integer.valueOf(dbSource.tableTotalNum()))/Integer.valueOf(dbSource.pertableNum())) < 32){
                            dataSource = NewDataSourceContextHolder.DB_1_32;
                        }
                        else {
                            dataSource = NewDataSourceContextHolder.DB_33_64;
                        }
                    }
                    else if(args[0] instanceof Long)
                    {
                        Long userid = (Long) args[0];
                        if(((userid % Integer.valueOf(dbSource.tableTotalNum()))/Integer.valueOf(dbSource.pertableNum())) < 32){
                            dataSource = NewDataSourceContextHolder.DB_1_32;
                        }
                        else {
                            dataSource = NewDataSourceContextHolder.DB_33_64;
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 切换数据源
        NewDataSourceContextHolder.setDB(dataSource);
    }


    @After("@annotation(com.ppdai.realtime.dataservices.dbcp.DBSource)")
    public void afterSwitchDS(JoinPoint point){
        NewDataSourceContextHolder.clearDB();
    }
}

6. 最后就是在需要动态选库的函数上使用@DBSource注解  bingo

@DBSource(tableTotalNum = "2048", pertableNum = "32")
    public Collection<Emailbills> get***(Integer userid) {  ** }

函数方法体和方法隐去了。

猜你喜欢

转载自www.cnblogs.com/zhengwangzw/p/9445956.html