Spring Boot优雅实现注解切换多数据源

2023-03-15T11:36:00

请帮我完善这篇文档,并且里面的代码添加注释

实现注解切换多数据源需要以下步骤:

1.首先添加了AOP、MyBatis、Druid和MySQL的依赖。
        <!-- Aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
2.配置两个数据源,一个是master,一个是slave,同时设置了Druid连接池的参数,以及MyBatis的相关配置。
spring:
  datasource:
    druid:
      defaultAutoCommit: true
      initialSize: 5
      maxActive: 20
      maxWait: 60000
      minIdle: 5
      testOnBorrow: false
      testOnReturn: false
      testWhileIdle: true
      timeBetweenEvictionRunsMillis: 60000
      validationQuery: SELECT 1 FROM DUAL
      filters: stat,wall,log4j2
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    master:
      url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
    slave:
      url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.entity
  configuration:
    map-underscore-to-camel-case: true
3.创建一个枚举类,用于区分不同的数据源。
/**
 * <p>
 * 枚举类,用于区分不同的数据源
 * </p>
 *
 * @author snogzixian
 * @Url 宋子宪博客 www.songzixian.com
 * @since 2023-03-15
 */
public enum DataSourceType {
    /**
     * 主数据源
     */
    MASTER("master"),
    /**
     * 从数据源
     */
    SLAVE("slave");

    private final String value;

    DataSourceType(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}
4.创建一个注解类,用于标识需要使用哪个数据源。
import com.songzixian.enums.DataSourceType;

import java.lang.annotation.*;

/**
 * <p>
 * 注解类,用于标识需要使用哪个数据源
 * </p>
 *
 * @author snogzixian
 * @Url 宋子宪博客 www.songzixian.com
 * @since 2023-03-15
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}
5.创建一个AOP切面类,用于在方法执行前将数据源切换到指定的数据源上,方法执行后再切回原来的数据源。
import com.songzixian.annotation.DataSource;
import com.songzixian.enums.DataSourceType;
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.springframework.stereotype.Component;

/**
 * <p>
 * 数据源切面类,用于在方法执行前将数据源切换到指定的数据源上,方法执行后再切回原来的数据源
 * </p>
 *
 * @author snogzixian
 * @Url 宋子宪博客 www.songzixian.com
 * @since 2023-03-15
 */
@Aspect
@Component
public class DataSourceAspect {

    @Around("@annotation(com.songzixian.annotation.DataSource) || @within(com.songzixian.annotation.DataSource)")
    public Object switchDataSource(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource dataSource = signature.getMethod().getAnnotation(DataSource.class);
        if (dataSource == null) {
            Class<?> declaringType = signature.getDeclaringType();
            dataSource = declaringType.getAnnotation(DataSource.class);
        }
        if (dataSource == null) {
            DynamicDataSource.setDataSource(DataSourceType.MASTER.getValue());
        } else {
            DynamicDataSource.setDataSource(dataSource.value().getValue());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
        }
    }
}
6.创建一个配置类,用于配置多数据源和动态数据源。配置多数据源后,将其放入动态数据源中,通过设置默认数据源和目标数据源来实现动态切换数据源的功能。
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * <p>
 * 数据源配置类,用于配置多数据源和动态数据源
 * </p>
 *
 * @author snogzixian
 * @Url 宋子宪博客 www.songzixian.com
 * @since 2023-03-15
 */
@Configuration
public class DataSourceConfig {

    /**
     * 配置多数据源
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

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

    /**
     * 配置动态数据源
     */
    @Bean
    public DynamicDataSource dynamicDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("slave", slave1DataSource());

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource());

        return dataSource;
    }
}
7.创建一个动态数据源类,继承了AbstractRoutingDataSource类,并实现了determineCurrentLookupKey()方法,用于获取当前的数据源。同时,这里还提供了setDataSource()clearDataSource()方法,用于设置和清除当前数据源。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * <p>
 * 动态数据源类,继承了AbstractRoutingDataSource类,并实现了determineCurrentLookupKey()方法,用于获取当前的数据源
 * </p>
 *
 * @author snogzixian
 * @Url 宋子宪博客 www.songzixian.com
 * @since 2023-03-15
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> dataSourceHolder = new InheritableThreadLocal<>();

    /**
     * 设置数据源
     */
    public static void setDataSource(String dataSource) {
        dataSourceHolder.set(dataSource);
    }

    /**
     * 清除数据源
     */
    public static void clearDataSource() {
        dataSourceHolder.remove();
    }

    /**
     * 获取当前数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceHolder.get();
    }
}
8.创建一个MyBatis配置类,用于配置SqlSessionFactorySqlSessionTemplate。配置SqlSessionFactory时,需要注入DynamicDataSource类,同时设置mapper文件的位置。配置SqlSessionTemplate时,需要注入SqlSessionFactory
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

/**
 * <p>
 * MyBatis配置类,用于配置SqlSessionFactory和SqlSessionTemplate
 * </p>
 *
 * @author snogzixian
 * @Url 宋子宪博客 www.songzixian.com
 * @since 2023-03-15
 */
@Configuration
public class MyBatisConfig {

    @Autowired
    private DataSourceConfig dataSourceConfig;

    /**
     * 配置MyBatis
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceConfig.dynamicDataSource());

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));

        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }
}
9.最后直接在显示类中使用注解方式即可自由切换不同数据源

注解使用示例:@DataSource(枚举值),不填写注解则表示使用MASTER

@Service
public class BlogUserServiceImpl implements BlogUserService{

    @Resource
    private BlogUserMapper blogUserMapper;

    /**
     * 数据源一用法 不用注解默认MASTER,可以省略
     * @return
     */
    //@DataSource(DataSourceType.MASTER)
    @Override
    public List<BlogUser> getAll(){
       return blogUserMapper.selectAll();
    }

    /**
     * 数据源二用法
     * @return
     */
    @DataSource(DataSourceType.SLAVE)
    @Override
    public List<BlogUser> getUserSlave(){
        return blogUserMapper.selectAll();
    }

}

10.启动Spring Boot项目.然后进行调用2个不同数据源的接口就可以测试了,本篇文字由博主编写和测试,完全能用

当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »