java项目数据源的切换,即数据库的切换

本文介绍了在Java项目中实现数据源切换遇到的问题,当Service层加入事务控制后,数据源切换失效。原因在于事务开启后,连接被复用,导致数据源切换无效。解决方案包括:避免在包含特定关键词的方法中开启事务,或者重写事务管理器以确保数据源切换的正确性。

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

1.先在database.properties 配置文件中添加两个数据库链接(这里是作者的文件名,根据自身项目而定)

orcl.driver=oracle.jdbc.OracleDriver
orcl.url=jdbc:oracle:thin:@localhost:1521:orcl
orcl.user=scott
orcl.pwd=123456

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/appinfodb?useUnicode=true&characterEncoding=utf-8
user=root
password=root

2.修改我们的applicationContext-mybatis.xml文件(文件名根据自身项目而定)

//连接oracle 的数据源
<context:property-placeholder location="classpath:database.properties" />
	<!-- 获取数据源 使用BasicDataSource -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close" scope="singleton">
		<property name="driverClassName" value="${orcl.driver}" />
		<property name="url" value="${orcl.url}" />
		<property name="username" value="${orcl.user}" />
		<property name="password" value="${orcl.pwd}" />
	</bean>

	//连接MySQL的数据源
	<bean id="dataSource_MySql" class="org.apache.commons.dbcp.BasicDataSource"
		  destroy-method="close" scope="singleton">
		<property name="driverClassName" value="${driver}" />
		<property name="url" value="${url}" />
		<property name="username" value="${user}" />
		<property name="password" value="${password}" />
	</bean>
	
	<!-- 下面的是切换数据库的自定义类 -->
	//这里的class文件是后面进行创建的
	<bean id="multipleDataSource" class="cn.appsys.database.DynamicDataSource">  
		<property name="defaultTargetDataSource" ref="dataSource_MySql"></property>
		<property name="targetDataSources">
			<map>
				<entry key="dataSource_MySql" value-ref="dataSource_MySql"></entry>
				<entry key="dataSource" value-ref="dataSource"></entry>
			</map>
		</property>
	</bean>

<!-- 事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="multipleDataSource"/>
	</bean>

<!-- 配置SqlSessionFactoryBean -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="multipleDataSource"/>
		<property name="configLocation" value="classpath:mybatis-config.xml"></property>
		<property name="mapperLocations">
			<list>
				<value>classpath:cn/appsys/dao/**/*.xml</value>
			</list>
		</property> 
	</bean>


	<!-- 扫描mapper映射文件  MapperScannerConfigurer -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
		<property name="basePackage" value="cn.appsys.dao"/>
	</bean>

3.创建类指定配置使用哪个数据库

public class DBContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }
    public static String getDbType() {
        return ((String) contextHolder.get());
    }
    public static void clearDbType() {
        contextHolder.remove();
    }
}

4.定义动态数据源说明

public class MultipleDataSource  extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey(){
        return DBContextHolder.getDbType();
    }
}

5.最后一步在我们的Serviceimpl层为我们的方法进行标注为那个数据源进行操作

@Service("devservice")
public class DevUserServiceImpl implements DevUserService{

	@Resource(name="devUserdao")
	private DevUserDao devuserdao;
	/**
	 * 登录方法
	 */
	@Override
	public DevUser devlogin(String devCode, String devPassword) {
		// TODO Auto-generated method stub
		//这里选中为oracle数据库进行操作
		DBContextHolder.setDbType("dataSource");  
		return devuserdao.devlogin(devCode, devPassword);
	}

问题:
进行数据源切换配置运行成功后,我们数据源切换的Service层加入事务控制,发现此时数据源切换失败,dao只会访问默认的数据源。出现这个问题的原因,我在网上找了一下:

  1. AOP可以触发数据源字符串的切换,这个没问题
  2. 数据源真正切换的关键是 AbstractRoutingDataSource 的 determineCurrentLookupKey() 被调用,此方法是在open connection时触发。
  3. 事务是在connection层面管理的,启用事务后,一个事务内部的connection是复用的,所以就算AOP切了数据源字符串,但是数据源并不会被真正修改
    也就是说将数据源切换和事务处理都放在Service层,则数据源切换时失效的。
    这时候就说一下,配置文件中配置的事务切面。如下:
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
	<!-- <bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean> -->
	<!-- 配置事务通知属性 -->
	<!-- <tx:advice id="txAdvice" transaction-manager="transactionManager"> -->
		<!-- 定义事务传播属性 -->
		<!-- <tx:attributes> -->
			<!-- 不配方法将只读,但read-only默认非只读false。注解方式配置有什么区别?TODO 复习事务相关知识 -->
			<!-- <tx:method name="regist*" propagation="REQUIRED" />
			<tx:method name="insert*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="edit*" propagation="REQUIRED" />
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="add*" propagation="REQUIRED" />
			<tx:method name="new*" propagation="REQUIRED" />
			<tx:method name="set*" propagation="REQUIRED" />
			<tx:method name="remove*" propagation="REQUIRED" />
			<tx:method name="delete*" propagation="REQUIRED" />
			<tx:method name="change*" propagation="REQUIRED" />
			<tx:method name="check*" propagation="REQUIRED" />
			<tx:method name="get*" propagation="REQUIRED" read-only="true" />
			<tx:method name="find*" propagation="REQUIRED" read-only="true" />
			<tx:method name="load*" propagation="REQUIRED" read-only="true" />
			<tx:method name="*" propagation="REQUIRED"/> -->
		<!-- </tx:attributes>
	</tx:advice> -->
	<!-- 配置事务切面 -->
	
	<!-- <aop:config>
		<aop:pointcut id="serviceOperation"
			expression="execution(* com.cems.service.*.*(..))" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />
	</aop:config> -->

这就会导致到service层,检测到方法名带有“find”,“load”等字样的方法会进入事务,一旦进入事务除非整个事务结束,若是在中途中切换数据源也是无效的。这是因为当我们配置了事物管理器和拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存一些东西:DataSource、SqlSessionFactory、Connection等,所以,我们在外面再怎么设置要求切换数据源也没用,因为Conneciton都是从缓存中拿的,所以我们要想能够顺利的切换数据源,实际就是能够动态的根据DatabaseType获取不同的Connection,并且要求不能影响整个事物的特性。
1,在dao实现事务控制:此种方式不太合理,在于再dao层加入事务控制,无法保证一个方法内的事务的一致性
2.将数据源切换和事务开启按顺序进行,先切换,再开启事务。如下所示:比如在service层中方法名不带有这些“find”,“load”等这些字样,这样就不会进入事务,搜索的过程中进行数据源的切换也没有问题。若执行插入,修改的操作还是要进入事务,以防止数据的正确性受到影响。
3.重写MultiDataSourceTransaction事务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值