SpringBoot框架:通过AOP和自定义注解完成druid连接池的动态数据源切换(三)

引入依赖

引入数据库连接池的依赖——druid和面向切面编程的依赖——aop,如下所示:

复制代码


com.alibaba
druid-spring-boot-starter
1.1.21



org.springframework.boot
spring-boot-starter-aop

复制代码
二、创建数据库

1、主数据库

使用前文中已经创建的名为spring_boot_demo的数据库。

spring_boot_demo中t_user数据如下:

2、辅数据库

数据库名为other_data,库中建立数据表t_user,表结构与spring_boot_demo中的t_user一致。

实际项目中,大多是跨数据库的数据源切换,常用在同公司的多个不同系统中共用一个用户数据库,或者二次开发项目在原有数据库基础上做拓展,保留原有的数据连接。

这里为了方便操作,就都在mysql下部署数据库并且使表结构一致,方便形成数据对比。

other_data中插入数据如下:

三、修改数据库连接配置信息

在application.yml中,修改数据库连接配置如下:

复制代码
spring:
application:
name: spring-boot-demo
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
primary:
url: jdbc:mysql://localhost:3306/spring_boot_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
second:
url: jdbc:mysql://localhost:3306/other_data?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
复制代码

四、编写代码

结构如下:

1、枚举类DataSourceName:

该类用来存放数据源的名称,定义两个数据源名称分别为PRIMARY和SECOND。

复制代码
package com.example.demo.enums;

/**

  • DataSource的name常量

  • 便于切换

  • @author 我命倾尘
    */
    public enum DataSourceName {

    /**

    • 主数据源 spring_boot_demo
      */
      PRIMARY(“PRIMARY”),

    /**

    • 副数据源other_data
      */
      SECOND(“SECOND”);

    private String dataSourceName;
    private DataSourceName(String dataSourceName){
    this.dataSourceName=dataSourceName;
    }
    DataSourceName(){

    }
    public String getDataSourceName(){
    return this.dataSourceName;
    }
    }
    复制代码
      2、配置类DynamicDataSourceConfig:

通过@ConfigurationProperties读取配置文件中的数据源配置信息,并通过DruidDataSourceBuilder.create().build()创建数据连接,将多个数据源放入map,注入到IoC中:

复制代码
package com.example.demo.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.example.demo.bean.DynamicDataSource;
import com.example.demo.enums.DataSourceName;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**

  • @author 我命倾尘
    /
    @Configuration
    public class DynamicDataSourceConfig {
    /
    *

    • 创建DataSource Bean,将数据源配置从配置文件中读出
      */

    @Bean
    @ConfigurationProperties(“spring.datasource.druid.primary”)
    public DataSource oneDataSource(){
    return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(“spring.datasource.druid.second”)
    public DataSource twoDataSource(){
    return DruidDataSourceBuilder.create().build();
    }

    /**

    • 将数据源放入到 这个map中,注入到IoC
      */
      @Bean
      @Primary
      public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource){
      Map<Object,Object> targetDataSources=new HashMap<>(2);
      targetDataSources.put(DataSourceName.PRIMARY.getDataSourceName(),oneDataSource);
      targetDataSources.put(DataSourceName.SECOND.getDataSourceName(),twoDataSource);
      return new DynamicDataSource(oneDataSource,targetDataSources);
      }
      }
      复制代码
        3、动态数据源DynamicDataSource:

通过继承AbstractRoutingDataSource类,在构造函数中调用父类的方法,将配置类中放入map的数据源集合定为备选数据源,将传来的oneDataSource作为默认数据源:

复制代码
package com.example.demo.bean;

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

import javax.sql.DataSource;
import java.util.Map;

/**

  • @author 我命倾尘
    /
    public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal contextHolder=new ThreadLocal<>();
    /
    *

    • 配置DataSource
    • 设置defaultTargetDataSource为主数据库
      */
      public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSources){
      super.setDefaultTargetDataSource(defaultTargetDataSource);
      super.setTargetDataSources(targetDataSources);
      super.afterPropertiesSet();
      }
      public static String getDataSource(){
      return contextHolder.get();
      }
      public static void setDataSource(String dataSource){
      contextHolder.set(dataSource);
      }
      public static void clearDataSource(){
      contextHolder.remove();
      }

    @Override
    protected Object determineCurrentLookupKey() {
    return getDataSource();
    }
    }
    复制代码
      setTargetDataSources设置备选的数据源集合,

setDefaultTargetDataSource设置默认数据源,

determineCurrentLookupKey决定当前数据源的对应的key。

4、自定义注释类DataSource:

复制代码
package com.example.demo.annotation;

import com.example.demo.enums.DataSourceName;

import java.lang.annotation.*;

/**

  • @author 我命倾尘
    */
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
    DataSourceName value() default DataSourceName.PRIMARY;
    }
    复制代码
      @Documented指定被标注的注解会包含在javadoc中,

@Target指定注释可能出现在Java程序中的语法位置(ElementType.METHOD则说明注解可能出现在方法上),

@Retention指定注释的保留时间(RetentionPolicy.RUNTIME则是在java文件编译成class类时也依旧保存该注释)。

5、切面类DataSourceAspect:

复制代码
package com.example.demo.aspect;

import com.example.demo.annotation.DataSource;
import com.example.demo.bean.DynamicDataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**

  • @author 我命倾尘
    */
    @Aspect
    @Component
    public class DataSourceAspect implements Ordered {
    private Logger log= LoggerFactory.getLogger(DataSourceAspect.class);

    /**

    • 切点:所有配置DataSource注解的方法
      */
      @Pointcut("@annotation(com.example.demo.annotation.DataSource)")
      public void dataSourcePointCut(){

    }

    @Around(value = “dataSourcePointCut()”)
    public Object around(ProceedingJoinPoint point) throws Throwable{
    Object result;
    MethodSignature signature=(MethodSignature)point.getSignature();
    Method method=signature.getMethod();
    DataSource ds=method.getAnnotation(DataSource.class);
    /**
    * 判断DataSource的值
    * 获取当前方法应用的数据源
    */
    DynamicDataSource.setDataSource(ds.value().getDataSourceName());
    try{
    result=point.proceed();
    }finally {
    DynamicDataSource.clearDataSource();
    }
    return result;
    }

    @Override
    public int getOrder() {
    return 1;
    }
    }
    复制代码
      Spring框架有很多相同接口的实现类,提供了Ordered接口来处理相同接口实现类之间的优先级问题。

通过环绕切面,对方法上的注释进行了检验,如果获取到有DataSource注释,则会进行数据源的切换,否则按默认数据源进行处理。

6、引入配置类:

既然手动配置了动态切换数据连接池,就要在入口类中排除自动引入,并引入数据源的配置类,以及开启AOP:

复制代码
package com.example.demo;

import com.example.demo.config.DynamicDataSourceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

@MapperScan(“com.example.demo.mapper”)
@Import({DynamicDataSourceConfig.class})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
复制代码
  通过@Import引入配置类,在@SpringBootApplication后进行自动引入的排除。

@EnableAspectJAutoProxy用来开启AOP。

五、简单测试

1、不使用注解:

UserController中的方法如下:

@RequestMapping("/user/age")
  public int getAgeOfUser(){
return userService.getAgeByUsername(“springbootdemo”);
  }
  所得到的结果如下:

这个结果是从主数据源spring_boot_demo数据库的表中得到的数据。

2、在方法前添加注解@DataSource(DataSourceName.SECOND):

UserController中的方法如下:

@RequestMapping("/user/age")
  @DataSource(DataSourceName.SECOND)
  public int getAgeOfUser(){
return userService.getAgeByUsername(“springbootdemo”);
  }
  结果如下:
22

深圳网站优化www.zg886.cn

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP(面向切面编程)是Spring框架中的一个组件,它允许您以一种声明性的方式来处理横切关注点(如事务管理,日志记录等)。 通过使用AOP,可以将这些关注点从应用程序的主体中分离出来,从而实现代码的复用和灵活性。 在使用Spring框架中实现多数据源切换时,可以使用自定义注解的形式来实现。首先,首先在应用程序的主体中定义两个数据源。 然后,可以定义一个自定义注解,用于标识哪些方法应该使用哪个数据源。例如,使用“@Primary”注解标记主要数据源,使用“@Secondary”注解标记辅助数据源。 然后,在Spring配置中定义一个AOP切面,该切面使用上述自定义注解切换数据源。下面是这种方法的一个示例: ```java @Aspect @Component public class DataSourceAspect { @Around("@annotation(Primary)") public Object primaryDataSource(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 切换到主要数据源 DynamicDataSourceContextHolder.setDataSource(DynamicDataSourceContextHolder.DATA_SOURCE_PRIMARY); try { return proceedingJoinPoint.proceed(); } finally { // 切换回默认数据源 DynamicDataSourceContextHolder.clearDataSource(); } } @Around("@annotation(Secondary)") public Object secondaryDataSource(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 切换到辅助数据源 DynamicDataSourceContextHolder.setDataSource(DynamicDataSourceContextHolder.DATA_SOURCE_SECONDARY); try { return proceedingJoinPoint.proceed(); } finally { // 切换回默认数据源 DynamicDataSourceContextHolder.clearDataSource(); } } } ``` 在上面的代码中,我们可以看到“@Around”注解被用于定义一个环绕通知,该通知基于使用“@Primary”或“@Secondary”注解的方法进行拦截。 在方法执行之前,我们使用“DynamicDataSourceContextHolder”来将数据源设置为主要或辅助数据源。 在方法执行完成之后,我们将数据源切换回默认数据源。 最后,我们可以将“@Primary”和“@Secondary”注解带到相应的方法上,以切换不同的数据源,例如: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Primary public User getUserById(long id) { return userDao.getUserById(id); } @Override @Secondary public List<User> getAllUsers() { return userDao.getAllUsers(); } } ``` 在上面的代码中,我们可以看到“@Primary”注解被用于getUserById()方法,表示这个方法应该从主要数据源中读取数据。相反,getAllUsers()方法被标记为“@Secondary”注解,表示这个方法应该从辅助数据源中读取数据。 通过这种方式,我们可以很容易地切换应用程序中的不同数据源,并且代码的重复率很低。这种方法适用于需要在应用程序的不同部分使用不同数据源的多租户应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值