如何在项目中动态切库

在项目中,有遇到随业务场景的不同,而切换不同库的操作,本来考虑利用mycat来实现,但后来发现基于mycat本身的适用场景与我们的需求并不符,如果用的话,会有两个问题:

  1. mycat需要单独部署,虽不大,但又增加了现场的docker数,浪费资源;
  2. 我们的业务并不是基于数据库字段来切库,并且mycat已提供的几种切库方式没有我们的场景,要套用的话,虽可用注解的方式来加, 但对代码侵入性有点高,而且需要对jpa 和 mybatic 进行分别组装sql ;

故,最后采用自己写代码封装实现切库的方式来完成, 具体方法依赖:Spring中的AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。

具体实现切库步骤:

1.在platform-common工程中建以下三个类:DataSourceHolder、DynamicDataSource、SwitchDataSource
具体类的实现如下:

package com.star.boss.common.switchdatesource;
 
/**
 * Created by 10000474 on 2018/6/8 0008.
 */
public class DataSourceHolder {
 
    //使用ThreadLocal把数据源与当前线程绑定
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
 
    public static void setDataSource(String dataSourceName) {
        dataSources.set(dataSourceName);
    }
 
    public static String getDataSource() {
        return (String) dataSources.get();
    }
 
    public static void clearDataSource() {
        dataSources.remove();
    }
 
}
package com.star.boss.common.switchdatesource;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
/**
 * Created by 10000474 on 2018/6/8 0008.
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
}
package com.star.boss.common.switchdatesource;
 
import javax.servlet.*;
import java.io.IOException;
 
/**
 * Created by 10000474 on 2018/6/8 0008.
 */
public class SwitchDataSource implements  Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //此处比较简单,根据自己的业务可扩展;
        System.out.println("... start switch datasource ...");
        DataSourceHolder.setDataSource("dataSource2"); //这是要切的dataSoure的名称;
        System.out.println("... end to switch datasource ...");
        System.out.println(DataSourceHolder.getDataSource());
        chain.doFilter(request, response);
    }
 
    @Override
    public void destroy() {
}
}

2. 在具体项目中的dao配置文件里做以下工作:

先配置两数据源:dataSource1、dataSource2

<bean id="dataSource1"
   class="com.alibaba.druid.pool.DruidDataSource">
   <property name="driverClassName" value="${dataSource_driverClassName}" />
   <property name="url" value="${dataSource_url}" />
   <property name="username" value="${dataSource_username}" />
   <property name="password" value="${dataSource_password}" />
 
   <property name="filters" value="${filters}"/>
 
   <property name="maxActive" value="${maxActive}"/>
   <property name="initialSize" value="${initialSize}"/>
   <property name="maxWait" value="${maxWait}"/>
   <property name="minIdle" value="${minIdle}"/>
 
   <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
   <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
 
   <property name="validationQuery" value="${validationQuery}"/>
   <property name="testWhileIdle" value="${testWhileIdle}"/>
   <property name="testOnBorrow" value="${testOnBorrow}"/>
   <property name="testOnReturn" value="${testOnReturn}"/>
   <property name="maxOpenPreparedStatements"
           value="${maxOpenPreparedStatements}"/>
   <property name="removeAbandoned" value="${removeAbandoned}"/> <!-- 打开removeAbandoned功能 -->
   <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/> <!-- 1800秒,也就是30分钟 -->
   <property name="logAbandoned" value="${logAbandoned}"/> <!-- 关闭abanded连接时输出错误日志 -->
</bean>
 
<bean id="dataSource2"
     class="com.alibaba.druid.pool.DruidDataSource">
   <property name="driverClassName" value="${dataSource_driverClassName}" />
   <property name="url" value="${dataSource_init_url}" />
   <property name="username" value="${dataSource_username}" />
   <property name="password" value="${dataSource_password}" />
 
   <property name="filters" value="${filters}"/>
 
   <property name="maxActive" value="${maxActive}"/>
   <property name="initialSize" value="${initialSize}"/>
   <property name="maxWait" value="${maxWait}"/>
   <property name="minIdle" value="${minIdle}"/>
 
   <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
   <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
 
   <property name="validationQuery" value="${validationQuery}"/>
   <property name="testWhileIdle" value="${testWhileIdle}"/>
   <property name="testOnBorrow" value="${testOnBorrow}"/>
   <property name="testOnReturn" value="${testOnReturn}"/>
   <property name="maxOpenPreparedStatements"
           value="${maxOpenPreparedStatements}"/>
   <property name="removeAbandoned" value="${removeAbandoned}"/> <!-- 打开removeAbandoned功能 -->
   <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/> <!-- 1800秒,也就是30分钟 -->
   <property name="logAbandoned" value="${logAbandoned}"/> <!-- 关闭abanded连接时输出错误日志 -->
</bean>

并定义动态数据源:dataSource

<!-- 配置多数据源映射关系 -->
<bean id="dynamicDataSource" class="com.star.boss.common.switchdatesource.DynamicDataSource">
   <property name="targetDataSources">
      <map key-type="java.lang.String">
         <entry key="dataSource1" value-ref="dataSource1"></entry>
         <entry key="dataSource2" value-ref="dataSource2"></entry>
      </map>
   </property>
   <!-- 默认目标数据源为你主库数据源 -->
   <property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>

其它地方配置照旧

3.在具体项目中web.xml 中增加以下配置:

<filter>
    <filter-name>switchDataSourceFilter</filter-name>
    <filter-class>com.star.boss.common.switchdatesource.SwitchDataSource</filter-class>
</filter>
 
<filter-mapping>
    <filter-name>switchDataSourceFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Ok,测试完成;还有一点需要注意的是,一定要在注入bean之前,就要切库,否则一切白费,由于我们项目中前后端分离,又用的注解注入,我开始的时候,想在service层切库,一切貌似正常,实际已经是马后炮了,大家不要犯这样的错误;
另外, 由于一个服务如果要切源,会引起需要初始化多个库的需求,这个需要在flyway中实现,注意一下配置参数又交给zookeep;(请将MamjTest改为有业务意义的名字!)

<bean id="staribossDbMigrationMamjTest" class="com.star.boss.migration.StaribossDbMigration" init-method="migrate">
    <!--<property name="dbInit" ref="dbInit"/>-->
    <property name="dbMigration" ref="dbMigration1"/>
    <property name="autoMigrate" value="true"/>
</bean>
  
<!-- 升级数据库 -->
<bean id="dbMigration1" class="org.flywaydb.core.Flyway">
    <property name="dataSource" ref="db_migration_dataSource_MamjTest">
    <property name="baselineOnMigrate" value="true"/>
    <property name="locations" value="classpath:db/migration"/>
    <property name="placeholders">
        <map>
            <entry key="table_space" value="${table_space}"/>
            <entry key="db_script_i18n" value="${db_script_i18n}"/>
        </map>
    </property>
</bean>
  
<!-- 用于升级数据库的数据源 -->
<bean name="db_migration_dataSourceMamjTest" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${dataSource_driverClass_MamjTest}"/>
    <property name="url" value="${dataSource_url_MamjTest}"/>
    <property name="username" value="${dataSource_username_MamjTest}"/>
    <property name="password" value="${dataSource_password_MamjTest}"/>
</bean>

扩展:

          切库的代码,此处因为只是调研版,所以没做优化,真正在项目中,建议用注解的方式来实现,不要手动切,显得比较笨 。

          另外,如果分源存数据,会引进事务问题,由于我们项目中,只是查询的时候要切库,存库时已保证了只针对一个库,所以不存在此问题,所以没做研究。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值