在项目中,有遇到随业务场景的不同,而切换不同库的操作,本来考虑利用mycat来实现,但后来发现基于mycat本身的适用场景与我们的需求并不符,如果用的话,会有两个问题:
- mycat需要单独部署,虽不大,但又增加了现场的docker数,浪费资源;
- 我们的业务并不是基于数据库字段来切库,并且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>
扩展:
切库的代码,此处因为只是调研版,所以没做优化,真正在项目中,建议用注解的方式来实现,不要手动切,显得比较笨 。
另外,如果分源存数据,会引进事务问题,由于我们项目中,只是查询的时候要切库,存库时已保证了只针对一个库,所以不存在此问题,所以没做研究。