spring 动态数据源配置以及相关问题

来源:互联网 发布:软件配置管理办法 编辑:程序博客网 时间:2024/05/29 13:33

项目中要求读写分离,在spring中做到读写分离,很简单的想到在配置文件中设置两个数据源,一个datesource(只写),一个datesourceread(只读)。但是要根据上下文动态切换数据源,还需要增加两个帮助类。

      类1 ContextHolder

主要功能是帮助切换数据源,其中ThreadLocal保证线程中的一致性,不受其他线程影响。

public class ContextHolder {public static final String DATA_SOURCE = "dataSource";public static final String DATA_SOURCE_READ = "dataSourceRead";private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();public static void setCustomerType(String customerType){contextHolder.set(customerType);}public static String getCustomerType(){return contextHolder.get();}public static void clearCustomerType(){contextHolder.remove();}}

   类2 MyDataSource

自定义数据源,继承AbstractRoutingDataSource,实现determineCurrentLookupKey()方法。

public class MyDataSource extends AbstractRoutingDataSource {/* (non-Javadoc) * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */@Overrideprotected Object determineCurrentLookupKey() {return ContextHolder.getCustomerType();}}

配置文件中这样配置:

<bean id="abstractDataSource" abstract="true"                  class="com.mchange.v2.c3p0.ComboPooledDataSource"                  destroy-method="close">        <property name="driverClass"value="#{configManager.getConfigValue('trade-seller-guestbook-serv','datasource.driverClassName')}" />        <property name="acquireIncrement" value="3" />        <property name="initialPoolSize" value="3" />        <property name="minPoolSize" value="2" />        <property name="maxPoolSize" value="50" />        <property name="maxIdleTime" value="600" />        <property name="idleConnectionTestPeriod" value="900" />        <property name="maxStatements" value="100" />        <property name="numHelperThreads" value="10" /></bean><bean id="dataSourceRead" parent="abstractDataSource">        <property name="jdbcUrl"                  value="jdbc:mysql://192.168.10.57:3306/trade?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull" />        <property name="user" value="root" />        <property name="password" value="canada" />        <!-- <property name="jdbcUrl"value="#{configManager.getMysqlConfig('trade_public_mysql','1.2','trade').url}" /><property name="user"value="#{configManager.getMysqlConfig('trade_public_mysql','1.2','trade').user}" /><property name="password"value="#{configManager.getMysqlConfig('trade_public_mysql','1.2','trade').pwd}" /> -->    </bean><bean id="dataSource" parent="abstractDataSource"><property name="jdbcUrl"value="#{configManager.getMysqlConfig('trade_public_mysql','1.1','trade').url}" /><property name="user"value="#{configManager.getMysqlConfig('trade_public_mysql','1.1','trade').user}" /><property name="password"value="#{configManager.getMysqlConfig('trade_public_mysql','1.1','trade').pwd}" /></bean><bean id="myDataSource" class="com.zhe800.guestbook.database.MyDataSource"><property name="targetDataSources"><map key-type="java.lang.String"><entry value-ref="dataSource" key="dataSource"/><entry value-ref="dataSourceRead" key="dataSourceRead"/></map></property><property name="defaultTargetDataSource" ref="dataSource"/></bean><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="myDataSource" /><property name="configLocation" value="classpath:mybatis/mybatis-config.xml" /><property name="mapperLocations"><array><value>classpath*:mybatis/sqlmap/guestbook/*.xml</value><value>classpath*:mybatis/sqlmap/manual/*.xml</value></array></property></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.zhe800.guestbook.model.mapper" /><property name="sqlSessionFactory" ref="sqlSessionFactory" /></bean><tx:annotation-driven transaction-manager="transactionManager" /><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myDataSource" /></bean>


这样在做数据库操作的时候,读的时候只需要设置,写的时候还是默认数据源就ok了。

 ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_READ);


1、一直有一个疑问,既然xml配置中bean是启动的时候就加载到内存当中,且全局只有一个对象,那么在运行时设置ContextHolder参数可以修改数据源呢?

2、为什么在配置了事务的时候(@transaction),是改变不了数据源的呢?


对于第一个问题,在设置了ContextHolder之后,再开始使用sqlSessionFactory进行数据库相关操作,这时候sqlSessionFactory会找到真正的datasource

public void setDataSource(DataSource dataSource) {        if (dataSource instanceof TransactionAwareDataSourceProxy) {            // If we got a TransactionAwareDataSourceProxy, we need to perform            // transactions for its underlying target DataSource, else data            // access code won't see properly exposed transactions (i.e.            // transactions for the target DataSource).            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();        } else {            this.dataSource = dataSource;        }    }
而mydatesource继承自AbstractRoutingDataSource,类中精华部分在这里

/** * Retrieve the current target DataSource. Determines the * {@link #determineCurrentLookupKey() current lookup key}, performs * a lookup in the {@link #setTargetDataSources targetDataSources} map, * falls back to the specified * {@link #setDefaultTargetDataSource default target DataSource} if necessary. * @see #determineCurrentLookupKey() */protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;}

他会调用determineCurrentLookupKey()方法选择合适的数据源的key,而这个方法在我们的MyDataSource类中已经实现,且将向下文需要的ContextHolder设置了进去。

这样就实现了运行时的动态数据库的选择。



0 0
原创粉丝点击