首页 > 其他分享 >SpringBoot利用自定义注解实现多数据源

SpringBoot利用自定义注解实现多数据源

时间:2023-05-21 15:46:14浏览次数:39  
标签:return String 自定义 数据源 DataSource Integer public SpringBoot

自定义多数据源

SpringBoot利用自定义注解实现多数据源,前置知识:注解、Aop、SpringBoot整合Mybaits

1、搭建工程

创建一个SpringBoot工程,并引入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--    解析多数据源注解    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.18</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、定义多数据源注解

/**
 * 1、定义多数据源注解
 * @author ss_419
 * TODO 这个注解将来可以加在service类上或者方法上,通过value属性来指定类或者方法应该使用那个数据源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
    String value() default DataSourceType.DEFAULT_DS_NAME;
}

3、创建一个多数据上下文对象

这个类用来存储当前线程所使用的数据源名称


/**
 * TODO 这个类用来存储当前线程所使用的数据源名称
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 09:21
 */
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }


}

4、配置aop

  • @annotation(org.pp.dd.annotation.DataSource) 如果有@DataSource注解就给拦截下来

  • @within(org.pp.dd.annotation.DataSource) 表示类上有@DataSource注解就将类中的方法给拦截下来

/**
 * TODO
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 09:42
 */
@Component
@Aspect
@Order(11)
public class DataSourceAspect {

    /**
     * 定义切点
     *
     * @annotation(org.pp.dd.annotation.DataSource) 如果有@DataSource注解就给拦截下来
     * @within(org.pp.dd.annotation.DataSource) 表示类上有@DataSource注解就将类中的方法给拦截下来
     */
    @Pointcut("@annotation(org.pp.dd.annotation.DataSource) || @within(org.pp.dd.annotation.DataSource)")
    public void pc() {

    }
    /**
     * 环绕通知
     *
     * @param pjp
     * @return
     */
    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) {
        // 获取方法上的有效注解
        DataSource dataSource = getDataSource(pjp);
        if (dataSource != null) {
            // 获取注解中数据源的名称
            String value = dataSource.value();
            DynamicDataSourceContextHolder.setDataSourceType(value);
        }
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }

    }

    private DataSource getDataSource(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 获取方法上的注解
        DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);

        if (annotation != null) {
            // 说明方法上有注解
            return annotation;
        }


        return (DataSource) AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

5、读取参数DruidProperties


/**
 * TODO 读取数据源
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:20
 */
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
    private String type;
    private String driverClassName;
    private Map<String, Map<String ,String>> ds;
    private Integer initialSize;
    private Integer minIdle;
    private Integer maxActive;
    private Integer maxWait;

    /**
     * 在这个方法中设置公共属性
     * @param dataSource
     * @return
     */
    public DataSource dataSource(DruidDataSource dataSource){
        dataSource.setInitialSize(initialSize);
        dataSource.setMinIdle(minIdle);
        dataSource.setMaxActive(maxActive);
        dataSource.setMaxWait(maxWait);
        return dataSource;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Map<String, Map<String, String>> getDs() {
        return ds;
    }

    public void setDs(Map<String, Map<String, String>> ds) {
        this.ds = ds;
    }

    public Integer getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }

    public Integer getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }

    public Integer getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }

    public Integer getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }
}

6、加载数据源LoadDataSource

/**
 * TODO 加载数据源
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:30
 */
@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {

    @Autowired
    DruidProperties druidProperties;

    public Map<String, DataSource> loadAllDataSource() {
        Map<String, DataSource> map = new HashMap<>();
        Map<String, Map<String, String>> ds = druidProperties.getDs();

        try {
            Set<String> keySet = ds.keySet();
            for (String key : keySet) {
                map.put(key, druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return map;
    }
}

7、定义数据源管理器

当系统需要调用数据源的时候,数据源以key-value存起来,当需要数据源时调用determineCurrentLookupKey()方法来获取数据源。
由于本人实力原因,解答不了大家这里的疑惑。大致功能 通过修改本地线程的值,来实现数据源的切换。

/**
 * TODO 设置数据源
 * 当系统需要调用数据源的时候,数据源以key-value存起来,当需要数据源时调用determineCurrentLookupKey()方法
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:47
 */
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {


    public DynamicDataSource(LoadDataSource loadDataSource) {
        //1、设置所有的数据源
        Map<String, DataSource> allDs = loadDataSource.loadAllDataSource();
        super.setTargetDataSources(new HashMap<>(allDs));
        //2、设置默认数据源
        super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DS_NAME));

        super.afterPropertiesSet();
    }

    /**
     * 这个方法用来返回数据源名称,当系统需要获取数据源的时候会自动调用该方法获取数据源名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

定一个用于存储数据库类型的接口,这个接口类似于枚举类:

/**
 * TODO
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:54
 */
public interface DataSourceType {
    String DEFAULT_DS_NAME = "master";
    String DS_SESSION_KEY = "ds_session_key";
}

8、测试

创建User实体:

/**
 * TODO
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 11:15
 */
public class User {
    private Integer id;
    private String username;
    private String password;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

创建UserService:

@Service
// 在类上加注解的效果,会使该类的所有方法都切入到新的数据源中
//@DataSource
public class UserService {

    @Autowired
    UserMapper userMapper;
    // 在方法上加注解的效果,只会让指定的方法切入到另一个数据源中
    //@DataSource("slave")
    public List<User> findUsers(){
        return userMapper.findAllUsers();
    }
}

创建UserMapper:

@Mapper
public interface UserMapper {


    @Select("SELECT * FROM user")
    List<User> findAllUsers();
}

测试类:

@SpringBootTest
class DynamicDatasourcesApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {

        List<User> users = userService.findUsers();
        users.stream()
                .forEach(user -> System.out.println(user));
    }

}

默认选择主库的数据源:
image

执行结果如下:
image

在Service上加上注解,指定数据源为从库:
image
执行结果如下:
image

标签:return,String,自定义,数据源,DataSource,Integer,public,SpringBoot
From: https://www.cnblogs.com/atwood-pan/p/17418673.html

相关文章

  • Wordpress自定义小工具(Widget)简单案例
    在主题对应目录创建文件如widgets.php<?php//继承了WP_Widget这个类来创建新的小工具(Widget):可在后台外观-小工具中添加此自定义小工具到页面具体位置classmy_widgetextendsWP_Widget{publicfunction__construct() { //$widget_ops可以给小工具进......
  • ExtJS 4中自定义Grid列标题的对齐方式
           从ExtJS2.0到目前的4.0,Grid的列标题对齐方式都是和数据的对齐方式一致的,这不太符合中国人的习惯。解决办法是,自己重写一下Ext.grid.column.Column对象中渲染列标题的对齐方式的代码。代码只有一句,在afterRender方法中,因而重写afterRender方法就行了,具体做法如下。......
  • java基于springboot+vue的土特产在线销售平台、特产在线销售商城,附源码+数据库+lw文档
    1、项目介绍考虑到实际生活中在藏区特产销售管理方面的需要以及对该系统认真的分析,将系统权限按管理员和用户这两类涉及用户划分。(1)管理员功能需求管理员登陆后,主要模块包括首页、个人中心、用户管理、特产信息管理、特产分类管理、特产分类管理、特产评分管理、系统管理、订单......
  • 配置wordpress:自定义简码(wordpress 6.2)
    一,添加php代码的例子:1,代码://说明:得到当前时间functionlhdGetNowTime(){ $now=date("Y-m-dH:i:s");return$now;}//注册成简码,名字myNowadd_shortcode('myNow','lhdGetNowTime');//说明:获取完整URLfunctioncurPageURL(){ $pageURL='h......
  • SpringBoot读取Yml配置文件工具类
    SpringBoot读取Yml配置文件工具类在某些特定的环境,需要在非SpringBean中读取Yml文件,可以使用以下方式读取:需要依赖<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>24.1-jre</v......
  • springboot的xml和java对象转换
    packagecom.zygh.tscmp.pojo;importcom.fasterxml.jackson.annotation.JsonFormat;importcom.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;importcom.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;importcom.faster......
  • springboot整合redis
     前言Redis是一款key-value存储结构的内存级NoSQL数据库支持多种数据存储格式支持持久化支持集群Redis下载(Windows版)https://github.com/tporadowski/redis/releasesRedis安装与启动(Windows版)Windows解压安装或一键式安装服务端启动命令redis-server.exe......
  • Jenkins 自动部署 SpringBoot
    Jenkins是流行的CI/DI工具。什么是CI/DI呢?CI/CD的核心概念可以总结为三点:持续集成持续交付持续部署简单来说就是将不同代码的分支合并到主分支,并自动进行打包,编译,测试,部署到生产环境的交付流程。 在这里用阿里云主机演示Jenkins自动部署SpringBoot项目。可以到阿里云官......
  • 数据列表管理-底部的自定义代码参考
    <!--做数据的流转状态操作001--><divclass="div_bottom_control_location_area"id="div_rejected_or_approved"><buttononclick="submit_or_reject_for_review(0)"type="button"class="am-btnam-btn-dangerbtn_......
  • 数据列表管理-顶部查询条件的自定义代码参考
     银行:<selectid="sl_bank"class="fsbpmserachcontrolsearch_select"myts="sl"></select>状态:<selectid="sl_status_filter"class="fsbpmserachcontrolsearch_select"myts="sl"><......