Spring AOP如何实现注解式的Mybatis多数据源切换详解

网友投稿 976 2023-03-14

Spring AOP如何实现注解式的Mybatis多数据源切换详解

Spring AOP如何实现注解式的Mybatis多数据源切换详解

一、为什么要使用多数据源切换?

多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个WEB项目,在使用Mybatis作为数据库链接和操作框架的情况下通常只需要构建一个系统库,在该系统库创建业务表来满足需求,当然也有分为测试库和正式库dev/prod,不过这俩库的切换是使用配置文件进行切分的,在项目启动时或者打成maven JAR包指定environment-dev.properties或者environment-prod.properties。

那么当程序运行过程中,比如一个controller中既需要查询数据库A,又需要查询数据库B,而且两者都希望用entity(Mybatis中用于与表结构保持一直的bean)来接收查询结果,即都希望走Mybatis的entity-mapper-mapper.xml这么一套框架。这个时候最原始的方法是在代码中手动链接数据库比如:

var conn:Connection = null

try {

Class.forName("com.mysql.jdbc.Driver")

conn = DriverManager.getConnection("url","username","password")

val statement = conn.createStatement()

val result = statement.executeQuery("select * from **** where **** ")

while(result.next()){

}

}

本文所采用的是修改dao层context配置文件添加基于Spring事务和AOP方式uroBreAsDe的注解式数据源切换。最终实现的效果如下:

@Transactional //该注解表明该Service类开启Spring事务,事务的意思是指具有原子性的一个操作集合(本人理解),该事务做什么事在dao层的配置文件里配置,后面会讲。

@Service //表明为Service类,使用Component也行,Spring在启动时会扫描该类将该类所需要的bean全部构建出来以供使用

@TargetDataSource(name = "dataSource1") //重点,自定义的AOP注解,指定该TestService1类下的所有public方法都使用数据源dataSource1

class TestService1{

public void queryAllUser(){

UserMapper userMapper = new UserMapper()

userMapper.queryAllUser();

System.out.println("使用数据源dataSource1查询用户信息")

}

}

@Transactional

@Service

@TargetDataSource(name = "dataSource2")

class TestService2{

public void queryAllBook(){

BookMapper bookMapper = new BookMapper()

bookMapper.queryAllBook();

System.out.println("使用数据源dataSource2查询书籍信息")

}

}

在每一个需要切换数据源的Service层使用TargetDataSource(name= “***”)即可指定当前线程的数据源,当然别忘记@Transactional事务的添加,该事务用于Mybatis查询数据时去获取当前线程的数据源为哪一个。如此在controller中正常调用Service中的方法就行了,如果需要查询两个数据库那么分别调用两个TestService中的方法即可。比如:

//本人目前使用scala语言作为开发语言,java没怎么写了,还是习惯Scala,以下程序还是使用Scala语言规范哈

class testController{

@AutoWired

TestService1 testService1;

@AutoWired

TestService2 testService2;

@RequestMapping(value = Array("/test"), produces = Array("application/json;charset=UTF-8"), method = Array(RequestMethod.GET))

def test(): Unit = {

val allUser = testService1.queryAllUser()

println("使用TestService1查询数据源1中的所有用户")

val allBook = testService2.queryAllBook("33287")

println("使用TestService2查询数据源2中的所有书籍信息")

}

}

二、如何实现

接下来就详细讲述如何在Spring MVC和Mybatis的单套数据源支持上扩展多数据源切换能力。以下为双数据源,三数据源的实现方式相同。

1.首先在配置文件中添加第二个数据源的链接信息。

environment-dev.properties

#数据源1的链接信息

db1.jdbc.username=xxx

db1.jdbc.password=xxxxx

db1.jdbc.driverClassName=com.mysql.jdbc.Driver

db1.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8

#新添加的数据源2的链接信息

db2.jdbc.username=xxx

db2.jdbc.password=xxxxx

db2.jdbc.driverClassName=com.mysql.jdbc.Driver

db2.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8

2.在dao层的context.xml配置文件中添加基于注解的事务管理以及AOP切面配置

(1)在配置文件中添加双数据源,如下:

(2)使用AbstractRoutingDataSource实现动态数据源选择

配置文件中添加

在dao层创建dataSourceManage包,在包中创建如下类DynamicDataSource,DataSourceHolder。

类一:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

return DataSourceHolder.getDataSoure();

}

}

类二:

public class DataSourceHolder {

//线程本地环境

private static final ThreadLocal dataSources = new ThreadLocal();

//设置数据源

public static void setDataSource(String customerType) {

dataSources.set(customerType);

}

//获取数据源

public static String getDataSoure() {

return (String) dataSources.get();

}

//清除数据源

public static void clearDataSource() {

dataSources.remove();

}

}

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。以上完成数据库操作之前的数据源选择,使用的是DataSourceHolder.getDataSoure();

(3)添加Spring事务,确定在业务代码中查询数据库时,由Spring事务去执行以上对数据源的选择,这样既不影响业务代码又能提供事务的性质保证。

在配置文件中添加

classpath:common/dao/mysql/mapper/*Mapper.xml

注意配置sqlSessionFactory中使用的数据源需要和事务配置中的保持一直。以及配置文件的顶层bean需要添加 xmlns:tx="http://springframework.org/schema/tx"和xsi:schemaLocation中添加http://springframework.org/schema/tx http://springframework.org/schema/tx/spring-tx.xsd

(4)配置AOP提供Service层注解式声明使用的数据源

首先在配置文件中添加AOP支持xmlns:aop="http://springframework.org/schema/aop",xsi:schemaLocation中添加http://springframework.org/schema/aop https://springframework.org/schema/aop/spring-aop.xsd

其中execution(* common.dao.mysql.service.*.*(..))为service下的所有类(指TestService1和TestService2)的所有public方法都加上切面代理即使用dataSourceExchange处理。

然后在dataSourceManage包下创建DataSourceExchange类实现AfterReturningAdvice,MethodBeforeAdvice两个aop通知

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

import org.springframework.aop.MethodBeforeAdvice;

public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {

@Override

public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {

DataSourceHolder.clearDataSource();

}

@Override

public void before(Method method, Object[] objects, Object o) throws Throwable {

//这里TargetDataSource是自定义注解,method为查询数据库的方法比如一中的queryAllUser(),Objects为传给该方法的参数数组,o为调用该方法的对象,比如val allUser =

//testService1.queryAllUser()中的testService1

if (method.isAnnotationPresent(TargetDataSource.class)) {

TargetDataSource dataSource = method.getAnnotation(TargetDataSource.class);

DataSourceHolder.setDataSource(dataSource.name());

} else {

if (o.getClass().isAnnotationPresent(TargetDataSource.class)) {

TargetDataSource dataSource = o.getClass().getAnnotation(TargetDataSource.class);

DataSourceHolder.setDataSource(dataSource.name());

}

}

}

}

然后在dataSourceManage包下创建TargetDataSource注解类

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface TargetDataSource {

String name() default "dataSource1";

}

以上配置完成之后即可达成一中的最终效果。

完整的dao配置文件内容如下

xmlns="http://springframework.org/schema/beans"

xmlns:context="http://springframework.org/schema/context"

xmlns:xsi="http://w3.org/2001/XMLSchema-instance"

xmlns:tx="http://springframework.org/schema/tx"

xmlns:aop="http://springframework.org/schema/aop"

xsi:schemaLocation="http://springframework.org/schema/beans http://springframework.org/schema/beans/spring-beans-4.2.xsd

http://springframework.org/schema/context http://springframework.org/schema/context/spring-context-4.2.xsd http://springframework.org/schema/aop

https://springframework.org/schema/aop/spring-aop.xsd http://springframework.org/schema/tx http://springframework.org/schema/tx/spring-tx.xsd ">

classpath:test/common/dao/mysql/mapper/*Mapper.xml

xmlns="http://springframework.org/schema/beans"

xmlns:context="http://springframework.org/schema/context"

xmlns:xsi="http://w3.org/2001/XMLSchema-instance"

xmlns:tx="http://springframework.org/schema/tx"

xmlns:aop="http://springframework.org/schema/aop"

xsi:schemaLocation="http://springframework.org/schema/beans http://springframework.org/schema/beans/spring-beans-4.2.xsd

http://springframework.org/schema/context http://springframework.org/schema/context/spring-context-4.2.xsd http://springframework.org/schema/aop

https://springframework.org/schema/aop/spring-aop.xsd http://springframework.org/schema/tx http://springframework.org/schema/tx/spring-tx.xsd ">

classpath:test/common/dao/mysql/mapper/*Mapper.xml

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:小程序插件怎么样删除掉(微信小程序插件在哪里找)
下一篇:如何通过XML方式配置并实现Mybatis
相关文章

 发表评论

暂时没有评论,来抢沙发吧~