• 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) {  ** }

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

    欢迎关注Java流水账公众号
  • 相关阅读:
    [原创]网页级在线性能测试网站介绍
    [原创]浅谈测试团队文化
    [原创]浅谈自动化测试中的金字塔模型理解
    [原创]如何在面试时选择合适的测试人员?
    [原创]浅谈怎么写周报
    [原创]Windows下调试工具介绍
    [原创]浅谈我对持续集成的理解
    [原创]IBM AppScan工具培训
    [原创]Jenkins持续集成工具介绍
    [原创]什么是信息安全资产管理?
  • 原文地址:https://www.cnblogs.com/guofu-angela/p/9445956.html
Copyright © 2020-2023  润新知