Spring+Mybatis分库分表总结

收到一个用户需求要求时时展现填报信息,之前项目使用的数据库是MySQL,但是由于数据量较大而且计算过程复杂,所以决定将数据放到Oracle中利用存储过程计算,这样就需要项目同时连接OracleMySQL数据库。下边简单总结下分库分表

分表

当一个表的数据量很大以后,业务上就需要根据规则对数据库进行水平切分,如注册用户表user_01,user_02等,但是当数据库表进行切分以后,程序就需要根据规格进行相应的处理。利用Mybatisinterceptor可以实现根据路由规则去操作相应的数据库,开源的有Shardbatis实现。但是sharding只能区分一个数据源中的不同表,并不能根据规则切换数据源。

分库

如果想连接多个数据库,需要配置两个数据源,如

1
2
3
4
5
6
<bean id="oracleDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="${oracle.db.url}" />
<property name="username" value="${oracle.db.username}" />
<property name="password" value="${oracle.db.password}" />
</bean>
  1. 利用AbstractRoutingDataSource.

在配置文件中加入DynamicDataSource配置,其中可以配置多个数据源链接和默认的链接

1
2
3
4
5
6
7
8
9
10
<bean id="dataSource" class="com.xxx.core.dataSource.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSource1" key="dataSource1"></entry>
<entry value-ref="dataSource2" key="dataSource2"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource1">
</property>
</bean>

然后继承AbstractRoutingDataSource,该方法中实现数据库的动态切换。

1
2
3
4
5
6
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}

定义一个可以设置当前线程的变量的工具类,用于设置对应的数据源名称。由于Spring的Bean是单例的,需要将数据源放入ThreadLocal中来避免线程安全的问题。

1
2
3
4
5
6
7
8
9
10
11
12
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}

也可以利用AOP+Annotation来实现基于注解的数据库动态切换。

  1. 直接配置两个数据源。
    这种方式简单粗暴,配置多个数据源直接访问不同的数据库,但是数据库的事务会存在问题,不能同时保证多个数据库的事务。配置时遇到了spring-mybatis的一个大坑。

天真的以为同时配置两套datasource,sessionFactory,transaction,mapperscannerconfigurer就能用了,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean id="oracleDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="${oracle.db.url}" />
<property name="username" value="${oracle.db.username}" />
<property name="password" value="${oracle.db.password}" />
</bean>
<bean id="oracleSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="oracleDataSource" />
<property name="mapperLocations" value="classpath:com/xxx/oracle/mapper/*.xml" />
</bean>
<bean id="oracleTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="oracleDataSource" />
</bean>
<tx:annotation-driven transaction-manager="oracleTransactionManager"
proxy-target-class="true" />
<bean id="oracleMapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xxx.oracle" />
</bean>

但是并没有。。。一直抱一个Mapped Statements collection does not contain value for的错误,直接将mybatis的xml放到另一个数据源下就会报表不存在的错误,事实证明配置的Oracle数据源在Mybatis中并没有生效。在stackoverflow上也并没有找到相关问题的解。无意中看到一篇文章,原文地址

https://www.iflym.com/index.php/code/201211010001.html

才发现spring-mybatis的一个大坑。原文指出在MapperFactoryBean的父类SqlSessionDaoSupport

1
2
3
4
5
6
7
8
9
10
11
12
@Autowired(required = false)
public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
@Autowired(required = false)
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}

你绝对想不到,因为经过Autowired的处理,MapperFactoryBean即会运行setSqlSessionFactory方法,也会运行setSqlSessionTemplate方法。而更让人郁闷的是,你设置的sqlSessionFactoryBeanName根本没有用。这来自于内部,自以为是的externalSqlSession变量。当此变量为true时,setSqlSessionFactory方法会直接返回。因为,setSqlSessionTemplate会比属性注入的applyPropertyValues更先运行,这一切是不是很让人郁闷。

所以在xml中配置文件改成

1
2
3
4
5
6
7
8
<bean id="oracleSqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="oracleSqlSessionFactory" />
</bean>
<bean id="oracleMapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xxx.oracle" />
<!-- 由于spring-mybatis的问题,不能使用sqlSessionFactoryBeanName -->
<property name="sqlSessionTemplateBeanName" value="oracleSqlSession" />
</bean>

问题解决了!!!

如果您觉得对您有帮助,谢谢您的赞赏!