spring 使用AbstractRoutingDataSource自定义动态数据源时的事务处理问题


最近在网上看到了一篇博客,继承spring的AbstractRoutingDataSource定义自己的动态数据源,可以根据需要动态的切换不同数据库的数据源,感觉使用起来非常方便。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 自定义的动态路由数据源 继承自 spring jdbc的AbstractRoutingDataSource
 * 
 * @author 
 *
 */
public class DbRouteDataSource extends AbstractRoutingDataSource {

	/**
	 * 获取与数据源相关的key
	 * 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
	 * 在通过determineTargetDataSource获取目标数据源时使用
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		
		return RouteHolder.getRouteKey();
	}

}

通过容器RouteHolder存储当前线程使用的数据源的key

/**
 * 保存当前线程数据源的key
 * @author 
 * @version 1.0
 *
 */
public class RouteHolder {
	private static ThreadLocal<String> routeKey = new ThreadLocal<String>();
	
	/**
	 * 获取当前线程的数据源路由的key
	 * @return
	 */
	public static String getRouteKey()
	{
		String key = routeKey.get();
		return key;
	}
	/**
	 * 绑定当前线程数据源路由的key
	 * 使用完成后必须调用removeRouteKey()方法删除
	 * @param key
	 */
	public static void  setRouteKey(String key)
	{
		routeKey.set(key);
	}
	
	/**
	 * 删除与当前线程绑定的数据源路由的key
	 */
	public static void removeRouteKey()
	{
		routeKey.remove();
	}
}


使用spring 的aop编程在业务逻辑方法运行前将当前方法使用数据源的key从业务逻辑方法上自定义注解@DataSource中解析数据源key并添加到RouteHolder中

/**
 * 执行dao方法之前的切面
 * 获取datasource对象之前往RouteHolder中指定当前线程数据源路由的key
 * @author Administrator
 *
 */
public class DataSourceAspect
{
	/**
	 * 在dao层方法之前获取datasource对象之前在切面中指定当前线程数据源路由的key
	 */
	public void beforeDaoMethod(JoinPoint point)
	{
		//dao方法上配置的注解
		DataSource datasource = ((MethodSignature)point.getSignature()).getMethod().getAnnotation(DataSource.class);
		RouteHolder.setRouteKey(datasource.value());
		
	}
}

业务逻辑方法

@Named("userService")
public class UserService 
{
	@Inject
	private UserDao userDao;
	
	@DataSource("master")
	@Transactional(propagation=Propagation.REQUIRED)
	public void updatePasswd(int userid,String passwd)
	{
		User user = new User();
		user.setUserid(userid);
		user.setPassword(passwd);
		userDao.updatePassword(user);
	}
	@DataSource("slave")
	@Transactional(propagation=Propagation.REQUIRED)
	public User getUser(int userid)
	{
		User user = userDao.getUserById(userid);
		System.out.println("username------:"+user.getUsername());
		return user;
	}
}

spring的配置文件

<bean id="dataSource" class="com.westone.datasource.DbRouteDataSource">
		<property name="targetDataSources">
				<map>
				   <!-- write -->
				   <entry key="master" value-ref="master"></entry>
				   <!-- read -->
				   <entry key="slave" value-ref="slave"></entry>
				</map>
		</property>	
	</bean>
	
	
	<bean id="master" class="org.apache.commons.dbcp.BasicDataSource">  
        <property name="driverClassName" value="${jdbc.driverclass}" />  
        <property name="url" value="${jdbc.masterurl}" />  
        <property name="username" value="${jdbc.username}" />  
        <property name="password" value="${jdbc.password}" />  
        <property name="maxActive" value="${jdbc.maxActive}"></property>  
        <property name="maxIdle" value="${jdbc.maxIdle}"></property>  
        <property name="maxWait" value="${jdbc.maxWait}"></property>  
    </bean>  
    
    <bean id="slave" class="org.apache.commons.dbcp.BasicDataSource">  
        <property name="driverClassName" value="${jdbc.driverclass}" />  
        <property name="url" value="${jdbc.slaveurl}" />  
        <property name="username" value="${jdbc.username}" />  
        <property name="password" value="${jdbc.password}" />  
        <property name="maxActive" value="${jdbc.maxActive}"></property>  
        <property name="maxIdle" value="${jdbc.maxIdle}"></property>  
        <property name="maxWait" value="${jdbc.maxWait}"></property>  
    </bean>
      
      
      
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" >  
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="configLocation" value="classpath:config/mybatis/mybatis.cfg.xml"></property>  
        <property name="dataSource" ref="dataSource" />  
    </bean> 
    
    <!--  配置mapper的映射扫描器 根据包中定义的接口自动生成dao的实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="com.westone.dao"></property>
    </bean>
   
   
   <!-- 为业务逻辑层的方法解析@DataSource注解  为当前线程的routeholder注入数据源key -->
   	<bean id="aspectBean" class="com.westone.datasource.aspect.DataSourceAspect"></bean>
    
    <aop:config>
    	<aop:aspect id="dataSourceAspect" ref="aspectBean">
    	   	<aop:pointcut id="dataSourcePoint" expression="execution(public * com.westone.service.*.*(..))" />
    	   	<aop:before method="beforeDaoMethod" pointcut-ref="dataSourcePoint"/>
    	</aop:aspect>
    </aop:config>
   
   
    <!-- 事务管理器配置 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean> 
	<!-- 开启事务注解驱动    在业务逻辑层上使用@Transactional 注解 为业务逻辑层管理事务-->  
    <tx:annotation-driven  transaction-manager="transactionManager"/>

事务管理配置一定要配置在往RouteHolder中注入数据源key之前  否则会报 Could not open JDBC Connection for transaction; nested exception is java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null]   找不到数据源错误


最后测试业务逻辑层的方法发现   可以根据方法上的@DataSource("master")  注解配置不同的数据源key  使用动态数据源。  不需要再业务逻辑方法层中定义不同的数据源对象,人为的使用不同的数据源操作数据。




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上增加了许多实用的功能,其中就包括动态数据源的支持。 MyBatis-Plus 的动态数据源实现,主要是通过使用 Spring 提供的 AbstractRoutingDataSource 类和 ThreadLocal 来实现的。具体步骤如下: 1. 首先需要在 Spring 的配置文件中配置多个数据源,并且将这些数据源注入到 AbstractRoutingDataSource 类中。 2. 然后通过自定义一个继承自 AbstractRoutingDataSource数据源路由类,重写它的 determineCurrentLookupKey() 方法,该方法根据当前线程的 ThreadLocal 中保存的数据源标识来选择对应的数据源。 3. 最后,将自定义数据源路由类注入到 Spring事务管理器中即可。 示例配置: ```xml <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource"> <!-- 数据源配置 --> </bean> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"> <!-- 数据源配置 --> </bean> <bean id="dataSource" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="mapperLocations" value="classpath*:mapper/*.xml"/> <property name="dataSource" ref="dataSourceRouter"/> </bean> <bean id="dataSourceRouter" class="com.example.DataSourceRouter"> <property name="targetDataSources"> <map> <entry key="dataSource1" value-ref="dataSource1"/> <entry key="dataSource2" value-ref="dataSource2"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource1"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSourceRouter"/> </bean> ``` 自定义数据源路由类: ```java public class DataSourceRouter extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>(); public static void setDataSource(String dataSource) { dataSourceHolder.set(dataSource); } @Override protected Object determineCurrentLookupKey() { return dataSourceHolder.get(); } } ``` 在业务代码中,可以通过调用 DataSourceRouter.setDataSource() 方法来设置当前线程要使用数据源标识。 ```java // 使用 dataSource1 数据源 DataSourceRouter.setDataSource("dataSource1"); // 使用 dataSource2 数据源 DataSourceRouter.setDataSource("dataSource2"); ``` 这样就可以实现动态切换数据源了。注意,数据源切换要在事务开启之前进行,否则事务失效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值