SpringBoot+自定义注解实现多数据源配置

94 篇文章 3 订阅
29 篇文章 1 订阅
😊 @ 作者: 一恍过去
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: SpringBoot+自定义注解实现多数据源配置
⏱️ @ 创作时间: 2022年06月15日

在这里插入图片描述

前言

多数据源是一种常见的技术需求,它允许应用程序连接和操作多个不同的数据库或数据源。

通过自定义注解实现不同数据源直接的切换,不使用注解时默认使用主库数据源,在需要使用指定数据源的在调用Mapper的方法上加上自定义注解即可;

1、创建数据库(表)

分别在mysql中创建两个库:systemlog,并且创建对应的表(根据项目实际情况的定),如下:

在这里插入图片描述

2、创建自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    /**
     * 数据源名称
     * @return
     */
    String name();
}

3、配置yaml

spring:
  application:
    name: quartz
  datasource:
    # 数据源一
    system:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3307/system?useUnicode=true&useSSL=false&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
      username: root
      password: lhzlx
    # 数据源二
    log:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3307/log?useUnicode=true&useSSL=false&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
      username: root
      password: lhzlx

    # hikari连接池
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      #最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
      maximum-pool-size: 10
      #最小空闲连接,默认值 10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
      minimum-idle: 2
      #连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
      connection-timeout: 60000
      #空闲连接超时时间,默认值600000ms(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;
      #不等于0且小于10秒,会被重置为10秒。
      #只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放(自动释放过期链接)
      idle-timeout: 600000
      #连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
      max-lifetime: 640000
      #连接测试查询
      connection-test-query: SELECT 1


#mapper 别名扫描
mybatis:
  mapper-locations: classpath*:mappers/*.xml
  type-aliases-package: com.lhz.demo.model.entity
  #数据库类型
  configuration.database-id: mysql
  #自动驼峰转换
  configuration.map-underscore-to-camel-case: true

注意: 在使用hikari作为连接池时,如果配置多数据源需要将url修改为jdbc-url,使用druid作为连接池则不需要修改;否则会出现jdbcUrl is required with driverClassName错误;

4、DataSourceHolder

public class DataSourceHolder {
    /**
     * 使用ThreadLocal储存上下游的数据源名称
     */
    private static final ThreadLocal<String> sourceName = new ThreadLocal<>();

    /**
     * 得到当前的数据库连接
     * @return
     */
    public static String  getDataSource() {
        return sourceName.get();
    }

    /**
     * 设置当前的数据库连接
     * @param name
     */
    public static void setDataSource(String name) {
        sourceName.set(name);
    }

    /**
     * 清除当前的数据库连接
     */
    public static void clearDataSource() {
        sourceName.remove();
    }

}

4、创建RoutingDataSource

public class RoutingDataSource extends AbstractRoutingDataSource {


    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }

}

5、数据源名称枚举定义

public enum DataSourceEnum {
    /**
     * 数据源名称,对应yaml中的数据源名称
     */
    SYSTEM("system","系统默认数据源"),
    LOG("log","日志数据源");

    DataSourceEnum(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    /**
     * 数据源名称
     */
    private final String name;

    /**
     * 描述
     */
    private final String desc;

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }
}

6、创建DataSourceAspect

@Aspect
@Component
public class DataSourceAspect {
    @Pointcut("@annotation(com.lhz.demo.annotation.DataSource)")
    public void dataSource() {}

    @Before("dataSource()")
    public void before(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        try {
            // 添加默认数据源
            String dataSource = DataSourceEnum.SYSTEM.getName();

            boolean present = method.isAnnotationPresent(DataSource.class);
            if(present){
                DataSource annotation = method.getAnnotation(DataSource.class);
                dataSource = annotation.name();
            }

            // 此处添加线程对应的数据源到上下文
            // 在AbstractRoutingDataSource子类中拿到数据源, 加载后进行配置
            DataSourceHolder.setDataSource(dataSource);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 清除数据源, 方法执行完成后, 清除数据源
     */
    @After("dataSource()")
    public void after(JoinPoint joinPoint) {
        DataSourceHolder.clearDataSource();
    }
}

在这里插入图片描述

7、数据源配置


@Configuration
public class DataSourceConfig {
    /**
     * yml中配置的数据源名称
     * @return
     */
    @Bean(name =  "system")
    @ConfigurationProperties(prefix = "spring.datasource.system")
    public DataSource system() {
        //database1数据源
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "log")
    @ConfigurationProperties(prefix = "spring.datasource.log")
    public DataSource log() {
        //database2数据源
        return DataSourceBuilder.create().build();
    }

    /**
     * 指定主数据源
     * @return
     */
    @Bean(name="routingDataSource")
    @Primary
    public DataSource dataSource() {
        RoutingDataSource routingDataSource = new RoutingDataSource();
        // 设置默认数据源
        routingDataSource.setDefaultTargetDataSource(system());
        // 将数据源写入ThreadLocal
        Map<Object, Object> dataSourceMap = new HashMap<>(4);
        dataSourceMap.put(DataSourceEnum.SYSTEM.getName(), system());
        dataSourceMap.put(DataSourceEnum.LOG.getName(), log());
        routingDataSource.setTargetDataSources(dataSourceMap);
        return routingDataSource;

    }
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

8、Mapper接口

com.lhz.demo.mapper目录下创建LogMapperSysMapper
LogMapper:

@Mapper
public interface LogMapper {
    List<TbLog> selectAll();
}

SysMapper:

@Mapper
public interface SysMapper {
    List<TbSystem> selectAll();
}

9、Mappeing(XML)

resources/mappers目录下创建LogMapper.xmlSystemMapper.xml文件

LogMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhz.demo.mapper.LogMapper">

    <select id="selectAll" resultType="com.lhz.demo.model.entity.TbLog">
        select * from tb_log
    </select>
</mapper>

SystemMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhz.demo.mapper.SysMapper">


    <select id="selectAll" resultType="com.lhz.demo.model.entity.TbSystem">
        select * from tb_system
    </select>
</mapper>

10、Service

@Service
public class TestService {

    @Resource
    private SysMapper sysMapper;

    @Resource
    private LogMapper logMapper;

    /**
     * 不指定数据源,模式使用system数据源
     * @return
     */
    public Object sys() {
        return sysMapper.selectAll();
    }


    /**
     * 指定数据源查询,该注解也可以放在Mapper接口上,表示整个Mapper接口都使用指定数据源
     * @return
     */
    @DataSource(name = "log")
    public Object log() {
        return logMapper.selectAll();
    }
}

11、Controller

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    @Resource
    private TestService testService;

    /**
     * 查询system库
     * @return
     */
    @GetMapping("/sys")
    public Object sys() {
        return testService.sys();
    }


    /**
     * 查询log库
     * @return
     */
    @GetMapping("/log")
    public Object log() {
        return testService.log();
    }
}

12、配置启动类

Application启动类需要屏蔽DataSource自动配置,修改如下:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication .class, args);
    }
}

13、目录结构

在这里插入图片描述

14、测试

启动项目分别访问`http://localhost:9090/test/sys`与`http://localhost:9090/test/log`均有数据输出,则表示配置成功;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,你的问题是关于如何在Spring Boot应用中实现多数据源动态切换,使用的技术包括Spring Boot、MyBatis、MySQL、Oracle、Druid数据源连接池、自定义注解和切面,并且配置文件使用application.yml格式。 首先,需要在pom.xml文件中添加相应的依赖: ```xml <!--Spring Boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--MyBatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!--MySQL--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!--Oracle--> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency> <!--Druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> ``` 接下来,需要在application.yml文件中配置数据源和MyBatis相关的属性,例如: ```yaml spring: datasource: druid: # 数据源1 db1: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root # 数据源2 db2: driver-class-name: oracle.jdbc.OracleDriver url: jdbc:oracle:thin:@localhost:1521:ORCL username: scott password: tiger # 默认数据源 url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity ``` 然后,需要定义一个自定义注解,用于标识哪些方法需要使用哪个数据源: ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String value() default "db1"; } ``` 在数据源切换的时候,我们需要获取注解上指定的数据源名称,因此需要定义一个切面: ```java @Aspect @Component public class DataSourceAspect { @Around("@annotation(ds)") public Object around(ProceedingJoinPoint point, DataSource ds) throws Throwable { String dataSourceName = ds.value(); DynamicDataSource.setDataSource(dataSourceName); try { return point.proceed(); } finally { DynamicDataSource.clearDataSource(); } } } ``` 最后,需要定义一个动态数据源,用于实现数据源的切换: ```java public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return dataSourceHolder.get(); } public static void setDataSource(String dataSourceName) { dataSourceHolder.set(dataSourceName); } public static void clearDataSource() { dataSourceHolder.remove(); } } ``` 至此,多数据源动态切换的配置就完成了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一恍过去

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值