情况:
项目过程中遇到这样一个需求,系统启动后动态设置数据源,不同用户登录系统后访问的数据库不同。
我们的系统有很多版本,不同版本开发在不同的数据库上,但是系统需要的一些配置依赖于数据库,所以需要有一个能配置数据源的后台,让开发人员自己处理。
解决方案:
在动态设置数据源方面,可以通过两种方式实现:
1.在action(项目使用struts)中进行设置,可以确保在每个servlet线程中数据源是一致的。
2.以aop方式,对service方法进行拦截,根据需求设置不同数据源。
数据库datasource,需要继承AbstractRoutingDataSource ,在spring包里面。
- /**
- * <p>
- * Title: 动态获取数据源
- * </p>
- *
- * <p>
- * Description:
- * </p>
- *
- * <p>
- * Copyright: 融博技术有限公司 2010
- * </p>
- *
- * @author 袁泉锋HO174959
- * @version 1.0
- * @date Jul 23, 2010
- *
- */
- public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
- /**
- * <Description>寻找当前线程数据库Key值
- *
- * @since Jul 23, 2010
- * @return <Description>
- *
- */
- @Override
- protected Object determineCurrentLookupKey() {
- return CustomerContextHolder.getDataBase();
- }
- }
AbstractRoutingDataSource 做了简单处理,我是反编译的,然后将属性targetDataSources修改为了map结构,key为数据源的ID,value是正常的datasource,看下面的spring配置文件:
- package com.rb.util;
- import java.sql.Connection;
- import java.sql.SQLException;
- import java.util.*;
- import javax.sql.DataSource;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.jdbc.datasource.AbstractDataSource;
- import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
- import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
- import org.springframework.util.Assert;
- public abstract class AbstractRoutingDataSource extends AbstractDataSource
- implements InitializingBean {
- public AbstractRoutingDataSource() {
- dataSourceLookup = new JndiDataSourceLookup();
- }
- public void setTargetDataSources(Map targetDataSources) {
- this.targetDataSources = targetDataSources;
- }
- public Map getTargetDataSources() {
- return this.targetDataSources;
- }
- public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
- this.defaultTargetDataSource = defaultTargetDataSource;
- }
- public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
- this.dataSourceLookup = ((DataSourceLookup) (dataSourceLookup == null ? ((DataSourceLookup) (new JndiDataSourceLookup()))
- : dataSourceLookup));
- }
- public void afterPropertiesSet() {
- if (targetDataSources == null)
- throw new IllegalArgumentException("targetDataSources is required");
- resolvedDataSources = new HashMap(targetDataSources.size());
- Object lookupKey;
- DataSource dataSource;
- for (Iterator it = targetDataSources.entrySet().iterator(); it
- .hasNext(); resolvedDataSources.put(lookupKey, dataSource)) {
- java.util.Map.Entry entry = (java.util.Map.Entry) it.next();
- lookupKey = resolveSpecifiedLookupKey(entry.getKey());
- dataSource = resolveSpecifiedDataSource(entry.getValue());
- }
- if (defaultTargetDataSource != null)
- resolvedDefaultDataSource = resolveSpecifiedDataSource(defaultTargetDataSource);
- }
- protected DataSource resolveSpecifiedDataSource(Object dataSource)
- throws IllegalArgumentException {
- if (dataSource instanceof DataSource)
- return (DataSource) dataSource;
- if (dataSource instanceof String)
- return dataSourceLookup.getDataSource((String) dataSource);
- else
- throw new IllegalArgumentException(
- "Illegal data source value - only [javax.sql.DataSource] and String supported: "
- + dataSource);
- }
- public Connection getConnection() throws SQLException {
- return determineTargetDataSource().getConnection();
- }
- public Connection getConnection(String username, String password)
- throws SQLException {
- return determineTargetDataSource().getConnection(username, password);
- }
- protected DataSource determineTargetDataSource() {
- Assert
- .notNull(resolvedDataSources,
- "DataSource router not initialized");
- Object lookupKey = determineCurrentLookupKey();
- DataSource dataSource = (DataSource) resolvedDataSources.get(lookupKey);
- if (dataSource == null)
- dataSource = resolvedDefaultDataSource;
- if (dataSource == null)
- throw new IllegalStateException(
- "Cannot determine target DataSource for lookup key ["
- + lookupKey + "]");
- else
- return dataSource;
- }
- protected Object resolveSpecifiedLookupKey(Object lookupKey) {
- return lookupKey;
- }
- protected abstract Object determineCurrentLookupKey();
- private Map targetDataSources;
- private Object defaultTargetDataSource;
- private DataSourceLookup dataSourceLookup;
- private Map resolvedDataSources;
- private DataSource resolvedDefaultDataSource;
- }
- /*
- DECOMPILATION REPORT
- Decompiled from: E:\myeclipse6.0_project\ToolServerFlex\WebRoot\WEB-INF\lib\spring.jar
- Total time: 218 ms
- Jad reported messages/errors:
- The class file version is 48.0 (only 45.3, 46.0 and 47.0 are supported)
- Exit status: 0
- Caught exceptions:
- */
CustomerContextHolder如下:
- /**
- * <p>
- * Title: 动态获取数据源
- * </p>
- *
- * <p>
- * Description:
- * </p>
- *
- * <p>
- * Copyright: 融博技术有限公司 2010
- * </p>
- *
- * @author 袁泉锋HO174959
- * @version 1.0
- * @date Jul 23, 2010
- *
- */
- public class CustomerContextHolder {
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
- public static void setDataBase(String dataBase) {
- contextHolder.set(dataBase);
- }
- public static String getDataBase() {
- return (String) contextHolder.get();
- }
- public static void clearDataBase() {
- contextHolder.remove();
- }
- }
开发人员请求过来的时候,调用方法setDataBase,将数据库配置key放入线程变量中。
自己定义一个数据库事务管理器:
- package com.rb.util;
- import java.sql.Connection;
- import java.sql.SQLException;
- import javax.sql.DataSource;
- public class TransactionManager {
- //private Connection conn;
- private DataSource dataSource;
- public static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
- public DataSource getDataSource() {
- return dataSource;
- }
- public void setDataSource(DataSource dataSource) {
- this.dataSource = dataSource;
- }
- public TransactionManager() {
- }
- // public synchronized Connection getConnection(){
- // Connection conn = tl.get();
- // if(conn==null){
- // try {
- // conn = dataSource.getConnection();
- // } catch (SQLException e) {
- // e.printStackTrace();
- // }
- // tl.set(conn);
- // }
- // return conn;
- // }
- /** 开启事务 */
- public void beginTransaction() throws SQLException{
- try {
- Connection conn = tl.get();
- if(conn==null){
- conn = dataSource.getConnection();
- tl.set(conn);
- }
- conn.setAutoCommit(false); //把事务提交方式改为手工提交
- } catch (SQLException e) {
- throw new SQLException("开户事务时出现异常");
- }
- }
- /** 提交事务并关闭连接 */
- public void commitAndClose() throws SQLException{
- Connection conn = null;
- try {
- conn = tl.get();
- conn.commit(); //提交事务
- } catch (SQLException e) {
- throw new SQLException("提交事务时出现异常");
- }finally{
- if(conn!=null){
- conn.close();
- }
- tl.remove(); //卸装线程绑定
- }
- }
- /** 回滚并关闭连接 */
- public void rollbackAndClose()throws SQLException{
- Connection conn = null;
- try {
- conn = tl.get();
- conn.rollback();
- } catch (SQLException e) {
- throw new SQLException("回滚事务时出现异常");
- } finally{
- if(conn!=null){
- conn.close();
- }
- tl.remove(); //卸装线程绑定
- }
- }
- }
调用完setDataBase方法之后,调用beginTransaction方法,第一次取的时候,connection是null,然后去datasource取连接,这个时候的datasource里面有很多数据源的连接,因为是一个map结构。当调用conn = dataSource.getConnection();的时候,会调用AbstractRoutingDataSource类的
方法:
- public Connection getConnection() throws SQLException {
- return determineTargetDataSource().getConnection();
- }
然后是:
- protected DataSource determineTargetDataSource() {
- Assert
- .notNull(resolvedDataSources,
- "DataSource router not initialized");
- Object lookupKey = determineCurrentLookupKey();
- DataSource dataSource = (DataSource) resolvedDataSources.get(lookupKey);
- if (dataSource == null)
- dataSource = resolvedDefaultDataSource;
- if (dataSource == null)
- throw new IllegalStateException(
- "Cannot determine target DataSource for lookup key ["
- + lookupKey + "]");
- else
- return dataSource;
- }
里面会用到方法:
- Object lookupKey = determineCurrentLookupKey();
但是我们的子类重写了他,是去线程变量里面取刚才我们setDataBase进去的,所以只会取到一个datasource。
这样就能取到自己选择的数据库连接了。