一、动态切换数据源理论知识
项目中我们经常会遇到多数据源的问题,尤其是数据同步或定时任务等项目更是如此;又例如:读写分离数据库配置的系统。
1、相信很多人都知道JDK代理,分静态代理和动态代理两种,同样的,多数据源设置也分为类似的两种:
1)静态数据源切换:
一般情况下,我们可以配置多个数据源,然后为每个数据源写一套对应的sessionFactory和dao层,我们称之为静态数据源配置,这样的好处是想调用那个数据源,直接调用dao层即可。但缺点也很明显,每个Dao层代码中写死了一个SessionFactory,这样日后如果再多一个数据源,还要改代码添加一个SessionFactory,显然这并不符合开闭原则。
2)动态数据源切换:
配置多个数据源,只对应一套sessionFactory,根据需要,数据源之间可以动态切换。
2、动态数据源切换时,如何保证数据库的事务:
目前事务最灵活的方式,是使用spring的声明式事务,本质是利用了spring的aop,在执行数据库操作前后,加上事务处理。
spring的事务管理,是基于数据源的,所以如果要实现动态数据源切换,而且在同一个数据源中保证事务是起作用的话,就需要注意二者的顺序问题,即:在事物起作用之前就要把数据源切换回来。
举一个例子:web开发常见是三层结构:controller、service、dao。一般事务都会在service层添加,如果使用spring的声明式事物管理,在调用service层代码之前,spring会通过aop的方式动态添加事务控制代码,所以如果要想保证事物是有效的,那么就必须在spring添加事务之前把数据源动态切换过来,也就是动态切换数据源的aop要至少在service上添加,而且要在spring声明式事物aop之前添加.根据上面分析:
最简单的方式是把动态切换数据源的aop加到controller层,这样在controller层里面就可以确定下来数据源了。不过,这样有一个缺点就是,每一个controller绑定了一个数据源,不灵活。对于这种:一个请求,需要使用两个以上数据源中的数据完成的业务时,就无法实现了。
针对上面的这种问题,可以考虑把动态切换数据源的aop放到service层,但要注意一定要在事务aop之前来完成。这样,对于一个需要多个数据源数据的请求,我们只需要在controller里面注入多个service实现即可。但这种做法的问题在于,controller层里面会涉及到一些不必要的业务代码,例如:合并两个数据源中的list…
此外,针对上面的问题,还可以再考虑一种方案,就是把事务控制到dao层,然后在service层里面动态切换数据源。
二、下面是我在实际项目中的一点应用:
1、首先,要有数据库的相关配置文件jdbc.properties:
#mysql
cms.mysql.driver=com.mysql.jdbc.Driver
cms.mysql.url=jdbc:mysql://127.0.0.1:3306/VSRecStream?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
cms.mysql.username=root
cms.mysql.password=******
edition.mysql.driver=com.mysql.jdbc.Driver
edition.mysql.url=jdbc:mysql://127.0.0.1:3306/ResourcePublish?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
edition.mysql.username=root
edition.mysql.password=******
2、有了数据源,肯定需要将这些源管理起来,此时很多人肯定想到了spring,对的,看下面:
<!-- cms configuration -->
<bean id="cmsBaseDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${cms.mysql.driver}" />
<property name="url" value="${cms.mysql.url}" />
<property name="username" value="${cms.mysql.username}" />
<property name="password" value="${cms.mysql.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="5" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="5" />
<property name="maxIdle" value="20" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="180"/>
<property name="validationQuery" value="select 1" />
</bean>
<!-- publish configuration -->
<bean id="editionBaseDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${edition.mysql.driver}" />
<property name="url" value="${edition.mysql.url}" />
<property name="username" value="${edition.mysql.username}" />
<property name="password" value="${edition.mysql.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="5" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="5" />
<property name="maxIdle" value="20" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="180"/>
<property name="validationQuery" value="select 1" />
</bean>
3、上面的数据源倒是配置起来了,但是怎么样才能实现一个sessionFactory来管理两个源呢,肯定是需要一个动态的代理类,写一个DynamicDataSource类继承 AbstractRoutingDataSource ,并实现 determineCurrentLookupKey方法即可,AbstractRoutingDataSource是spring里的一个实现类,有兴趣的朋友可以研究一下他的源码,在此,不做过多介绍。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态分派数据源
* @ClassName: DynamicDataSource
* @author dove
* @date 2017年3月21日
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
利用ThreadLocal解决线程安全问题
package com.visionvera.common;
public class CustomerContextHolder {
//用ThreadLocal来设置当前线程使用哪个dataSource
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();
}
}
4、动态类编写完毕,就要用起来,实现一个sessionFactory管理多个数据源
<!--统一的dataSource-->
<bean id="dynamicDataSource" class="com.visionvera.common.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<!--通过不同的key决定用哪个dataSource-->
<entry key="cmsBaseDataSource" value-ref="cmsBaseDataSource"></entry>
<entry key="editionBaseDataSource" value-ref="editionBaseDataSource" ></entry>
</map>
</property>
<!--设置默认的dataSource-->
<property name="defaultTargetDataSource" ref="cmsBaseDataSource"></property>
</bean>
<!-- define the SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<property name="typeAliasesPackage" value="com.visionvera.cms.bean,com.visionvera.cms.vo,com.visionvera.edition.bean,com.visionvera.edition.vo"/>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/visionvera/*/dao/mapper/*.xml"/>
</bean>
5、以上代码完成后,基本可以在每次调用service之前,通过手动切换数据源,即执行CustomerContextHolder.setCustomerType("cmsDataSource"),实现数据源的切换了,但是这样的话,完全达不到我们想要的动态切换数据源的需求。
那么我通过网上查找,发现有的朋友是通过写自定义注解配合拦截器来实现动态数据源切换的,但我个人感觉,如果在前期一直使用一个数据源的项目,后期突然要加入新的数据源的情况来说,不太适合,因为,这样的话,需要在每一个dao中添加注解,这样,之前的项目代码也需要修改,这也不是很好。而我的实现方法是,利用sping aop定义切面,在切面中实现数据源的切换,请看下面的代码:
编写切面代码:
package com.visionvera.common.aspect;
import java.util.Map;
import java.util.Map.Entry;
import org.aspectj.lang.JoinPoint;
import com.visionvera.common.CustomerContextHolder;
/**
* 动态切换数据源切面
* @ClassName: DataSourceAspect
* @author chenting
* @date 2017年3月22日
*
*/
public class DataSourceAspect{
private String defaultDataSource;
private Map<String, Object> targetDataSources;
public void doBefore(JoinPoint joinPoint) {
boolean isSetDataSource = false;
String targetName = joinPoint.getTarget().getClass().getName();
for(Entry<String, Object> entry : targetDataSources.entrySet()) {
if(targetName.contains(entry.getKey())){
String value = entry.getValue().toString();
CustomerContextHolder.setCustomerType(value);
isSetDataSource = true;
break;
}
}
if(!isSetDataSource) {
CustomerContextHolder.setCustomerType(defaultDataSource);
}
}
public void doAfterReturning(JoinPoint joinPoint) {
CustomerContextHolder.clearCustomerType();
}
public Map<String, Object> getTargetDataSources() {
return targetDataSources;
}
public void setTargetDataSources(Map<String, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
public String getDefaultDataSource() {
return defaultDataSource;
}
public void setDefaultDataSource(String defaultDataSource) {
this.defaultDataSource = defaultDataSource;
}
}
在配置文件中利用spring aop进行管理
<!-- 配置数据源切换切面 -->
<bean id="dataSourceChangeAspect" class="com.visionvera.common.aspect.DataSourceAspect">
<property name="defaultDataSource" value="cmsBaseDataSource"></property>
<property name="targetDataSources">
<map key-type="java.lang.String">
<!--通过不同的key决定用哪个dataSource-->
<entry key="com.visionvera.cms" value="cmsBaseDataSource"></entry>
<entry key="com.visionvera.edition" value="editionBaseDataSource" ></entry>
</map>
</property>
</bean>
上面map中配置的方式,主要是我仿照上面DynamicDataSource的模式来写的,DynamicDataSource是继续子父类的,而我这个是自己写的,当然想实现的功能是类似的。这样配置的目的是想实现,同一个包下的dao接口,使用同一个数据源,这样肯定也有局限性,在后期如果遇到的话,会进行优化。
<aop:config>
<aop:pointcut id="serviceAop"
expression="execution(* com.visionvera.*.service..*.*(..)))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceAop" order="2"/>
<!-- 配置数据源的动态切换 -->
<aop:aspect ref="dataSourceChangeAspect" order="1">
<aop:before method="doBefore" pointcut-ref="serviceAop"/>
<aop:after-returning method="doAfterReturning" pointcut-ref="serviceAop"/>
</aop:aspect>
</aop:config>
上面之所以跟事物控制放一块,是因为切换数据源跟事务的切入点相同,故此写在一起,当然完全可以分开写。
分开写的模式为:
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceChangeAspect">
<aop:pointcut id="daoAop" expression="execution(* com.visionvera.*.service..*.*(..)))" />
<aop:before method="doBefore" pointcut-ref="daoAop"/>
<aop:after-returning method="doAfterReturning" pointcut-ref="daoAop"/>
</aop:aspect>
</aop:config>
至此,所有的配置均已完成。特别需要注意的一点是,order这一项,配置了执行的优先级,一定要在事务开启前,将数据源切换完毕。
但是还有一个比较大的问题没有解决,假如有两个数据源,此时需要同时添加一条数据,此时如果出现插入异常的话,事务是没法保证两个数据源都能回滚的,这个等待大神给指点迷津,谢谢。。
分享到:
相关推荐
- **动态数据源切换逻辑**:核心逻辑是在AOP切面的Advice中实现。当一个需要切换数据源的方法被调用时,通过反射或其他机制获取该方法上标注的数据源信息,然后根据这些信息从数据源池中获取对应的数据源,并将其...
- AOP切面的Java类,实现了方法执行前后的拦截和数据源切换。 - 数据源相关的Java配置类,用于配置Spring的DataSource和MybatisPlus。 - Mapper接口和对应的XML文件,定义数据库操作。 - 业务逻辑层的Java类,其中的...
在Spring Boot中,AOP(面向切面编程)和多数据源的整合是常见的应用场景,尤其是在大型企业级项目中,为了实现数据的隔离或者优化数据库访问,常常需要配置多个数据源。本文将深入探讨如何使用Spring Boot的AOP注解...
3. **AOP实现动态数据源切换** 使用AOP(面向切面编程)注解,我们可以实现方法级别的数据源切换。创建一个`@Aspect`注解的类,定义一个环绕通知来切换数据源: ```java @Aspect @Component public class ...
本示例主要讲解如何使用Spring Boot结合MyBatis实现多数据源切换,并确保AOP事务管理仍然有效。 首先,我们需要配置多数据源。在Spring Boot中,可以使用`DataSource`接口的实现类,如`HikariCP`或`Druid`,创建两...
- 使用JdbcTemplate进行数据源切换,可以在调用方法时指定使用的DataSource,或者通过线程绑定的数据源自动选择。 - 在实际应用中,创建JdbcTemplate实例时,需要传入对应的DataSource,以便进行数据库操作。 3. ...
在SpringBoot项目中,整合Mybatis-Plus并实现多数据源的动态切换,同时支持分页查询是一项常见的需求。以下将详细阐述这个过程中的关键步骤和技术要点。 首先,我们需要引入必要的Maven依赖。这里提到了四个关键...
总结来说,SpringBoot实现多数据源和动态切换的关键在于正确配置多个数据源,创建自定义的AOP切面和ThreadLocal上下文。在实际项目中,还需要考虑事务管理、异常处理等复杂情况,确保数据的一致性和安全性。通过理解...
在 `thc-datasources` 压缩包中,可能包含了更详细的配置示例、数据源切换注解的实现以及相关的辅助工具类,如 `DynamicDataSourceContextHolder`。读者可以结合这些资源,进一步理解和实践多数据源动态切换的实现。
"Spring动态切换多数据源Demo"是一个示例项目,它展示了如何在Spring中实现灵活的数据源切换。 首先,我们需要了解Spring中的数据源。在Spring中,数据源(DataSource)是Java中定义的javax.sql.DataSource接口的...
创建一个自定义的数据源切换注解,比如`@SwitchDataSource`,并在需要切换数据源的方法上使用。通过AspectJ的切面处理,我们可以在方法执行前后动态改变ThreadLocal中的数据源引用。 3. **Spring Cloud Config ...
通过以上步骤,我们已经成功地在SpringBoot应用中实现了基于AOP的多数据源切换。在业务代码中,只需在需要切换数据源的方法上添加`@SwitchDataSource`注解,并指定相应的数据源类型,AOP切面就会自动处理数据源的...
使用aop进行多数据源切换 springMVC+spring+mybatis增删改查的使用。dk8+tomcat8+mysql+Eclipse+maven。spring+spring mvc+mybatis+bootstrap+jquery
综上所述,"spring+druid+AtomikosDataSource"的组合为开发者提供了一套强大的工具,用于实现多数据源切换和分布式事务控制。在实际项目中,通过合理的配置和编码,可以构建出高效、健壮的分布式系统。在`mult-table...
在多数据源切换中,我们可以定义一个数据源服务接口,该接口的实现类将负责实际的数据源切换逻辑。 接下来,我们将重点关注Oracle数据库,它是世界上最广泛使用的商业关系型数据库之一,以其稳定性和高性能而著称。...
基于Spring的 AbstractRoutingDataSource 进行简单的封装,方便进行数据源的切换,目前主要用于主从数据库的读写切换上。切换spring数据源的工具,使用aop注解方式进行快速切换,减少编码的入侵
这可以通过Spring的`AbstractRoutingDataSource`实现,这个类可以根据一定的规则动态选择合适的数据源。 2. **定义切面**:创建一个Spring AOP切面,这个切面将包含一个或多个通知(advice),用于拦截特定的数据库...
以下将详细介绍如何实现Spring的动态数据源切换以及相关知识点。 1. **数据源配置** - `jdbc.properties`: 这个文件通常包含了数据库连接的相关配置,如URL、用户名、密码、驱动类名等。Spring通过`Properties`类...
然后,定义一个数据源切换的抽象类或接口,用于封装切换逻辑: ```java public interface DataSourceSwitcher { DataSource switchDataSource(DataSourceType type); } ``` 接着,实现这个接口,基于Spring的`...
本篇主要介绍的是基于AOP的动态数据源切换方案,这种方式更加灵活且易于维护。 **2. 技术栈** - **Spring Boot**: 作为基础框架,提供了一系列的启动器简化开发过程。 - **MyBatis Plus**: ORM框架,简化了数据...