【Java】一次SpringMVC+ Mybatis 配置多数据源经历

需求

现在在维护的是学校的一款信息服务APP的后台,最近要开发一些新功能,其中一个就是加入学校电影院的在线购票。在线购票实际上已经有一套系统了,但是是外包给别人开发的,我们拿不到代码只能拿到数据库,并且也不一定能很好的兼容之前的代码,所以需要基于这个数据库来进行新的开发。

现在用的后台是SpringMVC+Mybatis+MySQL开发的,购票用的是SQL Server 2008(好古老的东西了),因为要用一套用户体系所以不可能再去单独为了这个功能弄一个系统出来,因此要在原项目中兼容这个数据库。

问题

如何在一个web项目中使用两个数据源,并且不同的接口可以按需选择数据库。

方案

最开始的做法

因为我们的项目用的是Mybatis作为ORM框架,在其配置文件中可以配置数据源信息,原始配置如下:

spring-mybatis.xml
































然后我就天真的认为是不是再新建一个dataSource的bean、sqlSessionFactory、mapperScannerConfigurer和transactionManager,把数据库连接信息改一下,就可以同时使用两个数据库了。但是尝试之后发现第二个数据库的mapping文件根本没有被初始化进spring的context中,报了Invalid bound statement (not found)这个错,查了一下说是配置文件不对等原因造成的。后来发现实际上因为上面的配置文件中的sqlSessionFactory在spring中是单例的,因此按照我的想法第二个sqlSessionFactory根本就不会被实例化。所以此方法行不通!

改进做法

最后是在这篇博客中找到了正确可行的解决方法:使用Spring提供的AbstractRoutingDataSource类来根据请求路由到不同的数据源。具体做法是
先设置两个不同的dataSource代表不同的数据源,再建一个总的dynamicDataSource,根据不同的请求去设置dynamicDataSource。代码如下:

配置文件spring-mybatis.xml
























































DynamicDataSource.java

public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}

CustomerContextHolder.java

public class CustomerContextHolder {
public static final String DATA_SOURCE_MYSQL = "dataSource";
public static final String DATA_SOURCE_MSSQL = "mssqlDataSource";
//用ThreadLocal来设置当前线程使用哪个dataSource
private static final ThreadLocal contextHolder = new ThreadLocal();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
String dataSource = contextHolder.get();
if (StringUtils.isEmpty(dataSource)) {
return DATA_SOURCE_MYSQL;
}else {
return dataSource;
}
}
public static void clearCustomerType() {
contextHolder.remove();
}
}

ServiceImpl.java

CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MSSQL);

值得注意的是在CustomerContextHolder.java中使用了ThreadLocal类的set方法来设置当前线程要选择的dataSource,看一下set方法的源码:

ThreadLocal.set()

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

显而易见,获取当前线程,并且使用一个hashmap把需要存储的值设置进去。因为tomcat是用的线程池来处理每个请求,所以用ThreadLocal可以保证线程安全问题。同时这个AbstractRoutingDataSource类也值得好好研究一下。

总结

其实这个方案不仅仅可以用来处理不同数据源的问题,同时业务量上来之后需要把数据库进行主从分离或是把一个库分为多个库,都需要用到这样的做法。这次暴露的问题确实也了解了不少,继续学习吧!

posted @ 2016-12-05 13:11  puyangsky  阅读(7111)  评论(25)    收藏  举报