springboot3实现aop多数据源结合mybatis-plus-boot-starter

方式一

其实网上有包如下:

复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
    <version>4.3.0</version>
</dependency>

applicaiton.yml配置如下:

复制代码
spring:
  datasource:
    dynamic:
      primary: ds1
      strict: false
      # 自定义负载均衡策略。slave组下有;多个个数据源,当用户使用 slave 切换数据源时会使用负载均衡算法。系统自带了两个负载均衡算法 LoadBalanceDynamicDataSourceStrategy 轮询,是默认的。 RandomDynamicDataSourceStrategy 随机的。
      strategy: com.baomidou.dynamic.datasource.strategy.LoadBalanceDynamicDataSourceStrategy
      datasource:
        ds1:
          url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalIn
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456
        ds2:
          url: jdbc:mysql://127.0.0.1:3306/demo1?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456

使用的时候就简单了,直接在service中的方法上使用

复制代码
@DS("ds2")
public void saveTest() {
   this.save(TestEntity.builder()
           .name("动态数据源测试"+ ThreadLocalRandom.current().nextInt(1000))
           .build());
}

方式二:自定义aop实现

我这里使用的springboot 3.2.4

复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

application.yml,内容如下:

复制代码
spring:
  datasource:
#    driver-class-name: com.mysql.cj.jdbc.Driver
#    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=
#    username: root
#    password: 123456
#    type: com.alibaba.druid.pool.DruidDataSource
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=
      username: root
      password: 123456
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/demo1?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=
      username: root
      password: 123456
      
# mybatis
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: org.demo.entity
  #typeEnumsPackage: org.springblade.dashboard.entity.enums
  global-config:
    #打开在线执行sql语句
    enable-sql-runner: true
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增", 1:"不操作", 2:"用户输入ID",3:"数字型snowflake", 4:"全局唯一ID UUID", 5:"字符串型snowflake";
      id-type: assign_id
      #字段策略
      insert-strategy: not_null
      update-strategy: not_null
      where-strategy: not_null
      #驼峰下划线转换
      table-underline: true
      # 逻辑删除配置
      # 逻辑删除全局值(1表示已删除,这也是Mybatis Plus的默认配置)
      logic-delete-value: 1
      # 逻辑未删除全局值(0表示未删除,这也是Mybatis Plus的默认配置)
      logic-not-delete-value: 0

导包

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.16</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>

先创建一个自定义数据源注解DataSource.java,内容如下:

复制代码
package org.demo.RW;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

继续创建数据源类型DataSourceType.java,内容如下:

复制代码
package org.demo.RW;

public enum DataSourceType {
    MASTER, SLAVE
}

接下来重点,springbootJDBC提供了一个抽象类 AbstractRoutingDataSource.java 用于数据源管理,只需要实现这个类,创建类DynamicDataSource.java ,内容如下:

复制代码
package org.demo.RW;


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

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<DataSourceType> CONTEXT_HOLDER = new ThreadLocal<>();

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSourceType();
    }

    public static void setDataSourceType(DataSourceType dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

继续创建配置文件DataSourceConfig.java ,内容如下:

复制代码
package org.demo.RW;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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;

@Configuration
@MapperScan(basePackages = "org.demo.mapper")
public class DataSourceConfig {

    @Bean("masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource")DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.afterPropertiesSet();

        return dynamicDataSource;
    }

    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
        /*SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        return sessionFactory.getObject();*/

        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        sessionFactory.setDataSource(dynamicDataSource);// 这里是重点,DynamicDataSource是我们创建的数据源管理类,否则不生效

//        // 设置mapper.xml位置
//        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//        sessionFactory.setMapperLocations(
//                resolver.getResources("classpath*:mapper/**/*.xml"));
//        sessionFactory.setTypeAliasesPackage("org.demo.entity");
//        sessionFactory.afterPropertiesSet();
        return sessionFactory.getObject();
    }

	/*  
	// 启动报错JdbcTemplate。时再打开注释,不报错不需要打开注释
	@Bean
	public JdbcTemplate jdbcTemplate(DynamicDataSource dynamicDataSource) {
		return new JdbcTemplate(dynamicDataSource);
	}

	// 事物管理
	@Bean
	public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
		return new DataSourceTransactionManager(dynamicDataSource);
	}*/
}

接下来创建DataSourceAspect.java 进行aop拦截,内容如下:

复制代码
package org.demo.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.demo.RW.DataSource;
import org.demo.RW.DynamicDataSource;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@EnableAspectJAutoProxy(proxyTargetClass = true)
@Aspect
@Component
@Order(1)
@Slf4j
public class DataSourceAspect {

    //任务层级目录下的service
    @Around("execution(* org.demo..service..*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        DataSource targetDataSource = method.getAnnotation(DataSource.class);
        if (targetDataSource != null) {
            DynamicDataSource.setDataSourceType(targetDataSource.value());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSourceType();
        }
    }
}

接下来创建测试ITestService.java 与实现TestServiceImpl.java ,内容如下:

复制代码
package org.demo.a.service;

import com.baomidou.mybatisplus.extension.service.IService;
import org.demo.entity.TestEntity;

import java.util.List;

public interface ITest1Service extends IService<TestEntity> {

    List<TestEntity> getList();

    void saveTest();
}
## 实现
package org.demo.a.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.demo.RW.DataSource;
import org.demo.RW.DataSourceType;
import org.demo.a.service.ITest1Service;
import org.demo.entity.TestEntity;
import org.demo.mapper.TestMapper;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

@Service
public class Test1ServiceImpl extends ServiceImpl<TestMapper, TestEntity> implements ITest1Service {

    @Override
//    @DataSource(DataSourceType.SLAVE)
    public List<TestEntity> getList() {
        return this.list();
    }

    @Override
    @DataSource(DataSourceType.SLAVE)
    public void saveTest() {
        this.save(TestEntity.builder()
                .name("动态数据源测试"+ ThreadLocalRandom.current().nextInt(1000))
                .build());
    }
}

然后在接口里调用,Ok了。

相关推荐
程序员泠零澪回家种桔子1 分钟前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构
CodeCaptain9 分钟前
nacos-2.3.2-OEM与nacos3.1.x的差异分析
java·经验分享·nacos·springcloud
Anastasiozzzz1 小时前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人1 小时前
通过脚本推送Docker镜像
java·docker·容器
韩立学长1 小时前
基于Springboot泉州旅游攻略平台d5h5zz02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·旅游
铁蛋AI编程实战1 小时前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
晚霞的不甘2 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
SunnyDays10112 小时前
使用 Java 冻结 Excel 行和列:完整指南
java·冻结excel行和列
摇滚侠2 小时前
在 SpringBoot 项目中,开发工具使用 IDEA,.idea 目录下的文件需要提交吗
java·spring boot·intellij-idea
云姜.2 小时前
java多态
java·开发语言·c++