SSM下利用AOP注解的形式配置多数据源
SSM下利用AOP注解的形式配置多数据源
需求描述:
系统原先使用的SQL server数据库,druid数据源。目前又新增了postgresql数据库,需要实现在方法上加上一个注解就会将此方法执行postgresql的数据源。
实现思路:
简单的说就是:
Mybatis来调用数据源,AOP用来切入方法,注解来告诉AOP应该切换哪个。
实现过程:
首先在Mybatis.xml中添加一个数据源的bean,为了方便区分,下面用dataSource1/2代替数据源名字
spring-dao.xml配置文件
<!--原本的数据源 dataSource1为原本的数据源-->
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${mssql.driver}"/>
<!-- 基本属性 url、user、password -->
<property name="url" value="${mssql.url}"/>
<property name="username" value="${mssql.username}"/>
<property name="password" value="${mssql.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${mssql.initialSize}"/>
<property name="minIdle" value="${mssql.minIdle}"/>
<property name="maxActive" value="${mssql.maxActive}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- <property name="validationQuery" value="${jdbc.testSql}" /> -->
<!-- <property name="testWhileIdle" value="true" /> -->
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat"/>
</bean>
<!--配置多数据源 dataSource2为新增数据源-->
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${postgresql.driver}"/>
<property name="url" value="${postgresql.url}"/>
<property name="username" value="${postgresql.username}"/>
<property name="password" value="${postgresql.password}"/>
</bean>
<!--配置多数据源-->
<bean id="dataSource" class="com.***.***.***.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSource1" key="dataSource1"></entry>
<entry value-ref="dataSource2" key="dataSource2"></entry>
</map>
</property>
<!--此处为默认数据源,非常重要,如果不配置后续clear掉数据源后可能会报错-->
<property name="defaultTargetDataSource" ref="dataSource1"></property>
</bean>
<!--配置SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
<!--配置mybatis全局配置文件:mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--扫描entity包,使用别名,多个用;隔开-->
<!--<property name="typeAliasesPackage" value="com.***.***.***.entity"/>-->
<!--扫描sql配置文件:mapper需要的xml文件-->
<property name="mapperLocations" value="classpath:mapper/*/*.xml"/>
</bean>
DataSource注解接口类 @interface
/**
* 描述: 配置多数据源注解接口
* @Author: zhuyu
* @Date: Create in 19:01 2020/11/30
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
// 此处为注解的默认数据源,即当方法上加上接口的时候,不添加任何参数时的默认值
String value() default "dataSource1";
}
DataSourceAop功能实现类
/**
* @Description: 多数据源切换AOP接口
* @Author: zhuyu
* @Date: Create in 19:06 2020/11/30
*/
@Aspect
@Component
public class DataSourceAop {
private final static Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
@Pointcut("@annotation(com.***.***.***.datasource.DataSource)")
public void cut() {
}
/**
* Before方法,主要功能为判断方法是否合法,如果合法则切换数据源
* <p>
* 在配置多数据源中,ProceedingJoinPoint不支持@before和@after,仅支持@Around。
* 如果要使用@Before和@After,需要使用JoinPoint来代替ProceedingJoinPoint
*
* @param point
* @throws Throwable
*/
@Before(value = "cut()")
public void cutDataSource(JoinPoint point) throws Throwable {
try {
//获得访问的方法名
String methodName = point.getSignature().getName();
logger.info("方法:{}", methodName, "正在切换数据源");
//获取当前方法
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
// 读取该方法的注解,得到需要切换数据源的名称
String switchDataSourceName = method.getAnnotation(DataSource.class).value();
// 切换数据源
DataSourceContextHolder.setDbType(switchDataSourceName);
} catch (Exception e) {
DataSourceContextHolder.clearDbType();
logger.error("切换数据源发生异常,已切换回主数据源,异常信息为:{}", e.getMessage());
}
}
/**
* 方法调用完毕,切换回主数据源
*
* @param point 加入点
*/
@After(value = "cut()")
public void afterCut(JoinPoint point) {
try {
// 方法结束后清理数据源
DataSourceContextHolder.clearDbType();
} catch (Exception e) {
logger.error("@After清理数据源发生异常,异常信息为:{}", e.getMessage());
e.printStackTrace();
}
}
/**
* 无论如何都要执行
*
* @param point 加入点
* @throws Throwable
*/
@AfterReturning(value = "cut()")
public void afterReturning(JoinPoint point) {
try {
// 获取当前数据源
String dbType = DataSourceContextHolder.getDbType();
// 如果当前数据源不是主数据源,则清理数据源信息切换到主数据源
if (dbType != null && !dbType.equals("dataSource1")) DataSourceContextHolder.clearDbType();
} catch (Exception e) {
DataSourceContextHolder.clearDbType();
logger.error("@AfterReturning切换数据源发生异常,已切换回默认数据源.异常信息为:{}", e.getMessage());
}
}
/**
* AOP执行过程中抛出Exception之后被调用
*
* @param point
*/
@AfterThrowing(value = "cut()")
public void afterThrowing(JoinPoint point) {
try {
logger.info("AOP执行过程中发送异常");
DataSourceContextHolder.clearDbType();
} catch (Exception e) {
logger.error("@AfterThrowing执行过程中发送异常,异常信息为:{}", e.getMessage());
}
}
}
DataSourceContextHolder 切换数据源操作类
/**
* @Description: 切换数据源操作类
* @Author: zhuyu
* @Date: Create in 19:06 2020/11/30
*/
public class DataSourceContextHolder {
private final static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);
// 新建一个线程
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 切换数据源,set到线程中
*
* @param dbType 数据源名称
*/
public static void setDbType(String dbType) {
logger.info("已切换到数据源:{}", dbType);
contextHolder.set(dbType);
}
/**
* 获取当前线程数据源名称
*
* @return 当前数据源名称
*/
public static String getDbType() {
return (contextHolder.get());
}
/**
* 清理当前线程数据源信息,清理掉以后当前数据源信息为 null
* 清理掉数据源后mybatis会自动使用 dao.xml中的defaultTargetDataSource(默认数据源)作为当前数据源
*/
public static void clearDbType() {
contextHolder.remove();
logger.info("已清理数据源");
}
/**
* 设置当前线程数据源为默认数据源,一般情况下作用相当于clearDbType()
*/
public static void setDefault() {
contextHolder.set("dataSource1");
logger.info("已切换到默认数据源dataSource1");
}
}
DynamicDataSource.java
/**
* @Description: mybatis访问该类获取数据源信息
* @Author: zhuyu
* @Date: Create in 19:02 2020/11/30
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* mybatis来拿数据源信息,方法获取当前数据源信息返回给mybatis(我是这样理解的,如果有更好的理解请修改注释)
* @return 当前线程数据源信息
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDbType();
}
}
至此,多数据源以及配置完毕,但是需要注意,我xml中并未贴出aop和mybatis的其他配置,不要忘记了
测试一下
在方法上加入 @DataSource("dataSource2"),访问方法查看日志输出


需要注意的点
- 切换数据源的本质实际上是新建一个线程,然后对新建的线程中的数据源内容进行切换修改,方法执行完后,线程要记得remove掉。我的代码中将线程remove封装到了clearDbType中。如果后续多数据源在并发情况下导致数据源报错什么的,就自行想办法加Lock保证线程安全吧。一般流量下只要及时remove应该就没什么问题了。
- 为了保证数据源可以顺利clear,我代码中进行了多次clear操作,其实没太大作用。
- 如果数据源多的话,可以考虑在@Before里面加上方法名与被切换数据源的判断之类的来保证万无一失,我之前加上了,但是我们系统只有两个数据源,暂时用不到就删掉了。
结语
AOP切换数据源实现过程如上,如果有问题或疑问以及不足的地方,请在评论区指出,谢谢。
> Some things take time. Be patient.

浙公网安备 33010602011771号