需求
現在在維護的是學校的一款信息服務APP的后台,最近要開發一些新功能,其中一個就是加入學校電影院的在線購票。在線購票實際上已經有一套系統了,但是是外包給別人開發的,我們拿不到代碼只能拿到數據庫,並且也不一定能很好的兼容之前的代碼,所以需要基於這個數據庫來進行新的開發。
現在用的后台是SpringMVC+Mybatis+MySQL開發的,購票用的是SQL Server 2008(好古老的東西了),因為要用一套用戶體系所以不可能再去單獨為了這個功能弄一個系統出來,因此要在原項目中兼容這個數據庫。
問題
如何在一個web項目中使用兩個數據源,並且不同的接口可以按需選擇數據庫。
方案
最開始的做法
因為我們的項目用的是Mybatis作為ORM框架,在其配置文件中可以配置數據源信息,原始配置如下:
spring-mybatis.xml
<!-- 引入配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化連接大小 --> <property name="initialSize" value="${jdbc.initialSize}"></property> <!-- 連接池最大數量 --> <property name="maxActive" value="${jdbc.maxActive}"></property> <!-- 連接池最大空閑 --> <property name="maxIdle" value="${jdbc.maxIdle}"></property> <!-- 連接池最小空閑 --> <property name="minIdle" value="${jdbc.minIdle}"></property> <!-- 獲取連接最大等待時間 --> <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描mapping.xml文件 --> <property name="mapperLocations" value="classpath:path/to/mapping/*.xml"></property> </bean> <!-- DAO接口所在包名,Spring會自動查找其下的類 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="path.to.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
然后我就天真的認為是不是再新建一個dataSource的bean、sqlSessionFactory、mapperScannerConfigurer和transactionManager,把數據庫連接信息改一下,就可以同時使用兩個數據庫了。但是嘗試之后發現第二個數據庫的mapping文件根本沒有被初始化進spring的context中,報了Invalid bound statement (not found)這個錯,查了一下說是配置文件不對等原因造成的。后來發現實際上因為上面的配置文件中的sqlSessionFactory在spring中是單例的,因此按照我的想法第二個sqlSessionFactory根本就不會被實例化。所以此方法行不通!
改進做法
最后是在這篇博客中找到了正確可行的解決方法:使用Spring提供的AbstractRoutingDataSource類來根據請求路由到不同的數據源。具體做法是
先設置兩個不同的dataSource代表不同的數據源,再建一個總的dynamicDataSource,根據不同的請求去設置dynamicDataSource。代碼如下:
配置文件spring-mybatis.xml
<!--統一的dataSource--> <bean id="dynamicDataSource" class="path.to.DynamicDataSource" > <property name="targetDataSources"> <map key-type="java.lang.String"> <!--通過不同的key決定用哪個dataSource--> <entry value-ref="dataSource" key="dataSource"></entry> <entry value-ref="mssqlDataSource" key="mssqlDataSource"></entry> </map> </property> <!--設置默認的dataSource--> <property name="defaultTargetDataSource" ref="dataSource"> </property> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化連接大小 --> <property name="initialSize" value="${jdbc.initialSize}"></property> <!-- 連接池最大數量 --> <property name="maxActive" value="${jdbc.maxActive}"></property> <!-- 連接池最大空閑 --> <property name="maxIdle" value="${jdbc.maxIdle}"></property> <!-- 連接池最小空閑 --> <property name="minIdle" value="${jdbc.minIdle}"></property> <!-- 獲取連接最大等待時間 --> <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> <!--電影票數據庫是mssql2008,單獨的數據庫,配置如下--> <bean id="mssqlDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc-mssql.driver}" /> <property name="url" value="${jdbc-mssql.url}" /> <property name="username" value="${jdbc-mssql.username}" /> <property name="password" value="${jdbc-mssql.password}" /> <!-- 初始化連接大小 --> <property name="initialSize" value="${jdbc.initialSize}"></property> <!-- 連接池最大數量 --> <property name="maxActive" value="${jdbc.maxActive}"></property> <!-- 連接池最大空閑 --> <property name="maxIdle" value="${jdbc.maxIdle}"></property> <!-- 連接池最小空閑 --> <property name="minIdle" value="${jdbc.minIdle}"></property> <!-- 獲取連接最大等待時間 --> <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dynamicDataSource" /> <!-- 自動掃描mapping.xml文件 --> <property name="mapperLocations" value="classpath:path/to/mapping/*.xml"></property> </bean> <!-- DAO接口所在包名,Spring會自動查找其下的類 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="path.to.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></