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

本文介绍了在Spring中实现读写分离的解决方案,通过配置两个数据源——datesource(只写)和datesourceread(只读),并利用ContextHolder类和ThreadLocal确保线程间数据源切换的安全性。此外,自定义了继承自AbstractRoutingDataSource的数据源,重写了determineCurrentLookupKey()方法,使得在运行时可以根据业务需求动态选择合适的数据源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目中要求读写分离,在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()
	 */
	@Override
	protected 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设置了进去。

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



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值