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了。

相关推荐
ZouZou老师2 小时前
C++设计模式之适配器模式:以家具生产为例
java·设计模式·适配器模式
曼巴UE52 小时前
UE5 C++ 动态多播
java·开发语言
VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue音乐管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
程序员鱼皮3 小时前
刚刚,IDEA 免费版发布!终于不用破解了
java·程序员·jetbrains
Hui Baby3 小时前
Dubbo/springCloud同机房收敛
spring·spring cloud·dubbo
Hui Baby3 小时前
Nacos容灾俩种方案对比
java
曲莫终3 小时前
Java单元测试框架Junit5用法一览
java
成富4 小时前
Chat Agent UI,类似 ChatGPT 的聊天界面,Spring AI 应用的测试工具
java·人工智能·spring·ui·chatgpt
凌波粒4 小时前
Springboot基础教程(9)--Swagger2
java·spring boot·后端