java多数据源几种实现方式以及demo

提示:多数据源实现方式、多数据源的使用场景。AbstractRoutingDataSource、DynamicDataSource框架、mybatisplus的Intercepter插件、java中多数据源的几种实现方式、mybatisPlus的插件实现多数据源

文章目录

  • 前言
  • 一、多数据源的几种实现方式
  • 二、使用场景
  • 三、核心原理
    • 1、原理
    • 2、实现步骤
  • 四、代码实现
    • 1.基础实现
      • 1.1、pom依赖
      • 1.2、配置文件
      • 1.3、配置类1: DataSourceConfig
      • 1.4、配置类2: DynamicDataSource
      • 1.5、controller
      • 1.6、mapper
    • 2.代码优化
      • 2.1、注解wr
      • 2.2、 @WR("W")
      • 2.3、aop
    • 3、mybatisPlus的插件实现多数据源
      • 3.1、MyMybatisInterceptor
      • 3.2、DataSourceConfig
      • 3.3、DynamicDataSource
  • 总结

前言

最近工作中有一张表,实际数据量超过1亿了,导致一条普通的insert语句也耗时15秒,因此需要分表。在使用shardingSphere分表时,需要切换多数据源,因此特意调研了一下多数据源的几种实现方式。再次记录一下,感兴趣的同学可以下载代码,这样看起来更加清晰。gitee代码


一、多数据源的几种实现方式

java中实现多数据源,比较常见的方式有3种:

  1. abstractRootingDataSource
  2. mybatisplus的Intercepter插件
  3. DynamicDataSource 框架

其实最底层的核心原理,就是abstractRootingDataSource,剩下的两种,肯定也是以第一种为基础的,只不过封装了一层而已。

二、使用场景

一般来说,多数据源有以下两种使用场景:

  • 业务复杂(数据量大)。数据分布在不同的数据库中,数据库拆了, 应用没拆。 一个公司多个子项目,各用各的数据库,涉及数据共享…
  • 读写分离。为了解决数据库的读性能瓶颈(读比写性能更高, 写锁会影响读阻塞,从而影响读的性能)。

三、核心原理

1、原理

最核心的类就是AbstractRootingDataSource,因此我们着重介绍一下。
这个抽象类中,有3个比较重要的成员变量:

在这里插入图片描述

  1. 1、此时,我们仍然返回的是dynamicDatasource,只是,我们继承了AbstruceRootingDataSource,然后getConnection方法变成了由AbstruceRootingDataSource提供的connection了
  2. 这个getConnection方法内部,是: determineTargetDataSource().getConnection();
  3. 而2中底层是调用的模版方法,去获取最终的connection。因为是map中的get方法获取的,所以get的这个key是关键,
    lookupKey =
    determineCurrentLookupKey();resolvedDataSource.get(lookupKey);
  4. 而这个key呢,就需要程序员自己在这个接口中去实现 determineCurrentLookupKey
    方法了。(返回的是一个key值,我们自定义的key)

2、实现步骤

实现多数据源大概需要3部,(AbstractRoutingDataSource)
1.继承 abstractRootingDataSource
2.返回当前数据源标识 重写 determineCurrentLookupKey 方法
3.获取全部的数据源map super.setTargetDataSources(targetDataSources);

四、代码实现

1.基础实现

1.1、pom依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.2</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
        <version>2.6.2</version>
    </dependency>        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.6.2</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.1</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
        <version>8.0.27</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.6.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>
    <!--Druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>2.6.2</version>
    </dependency>

</dependencies>

1.2、配置文件

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    datasource1:
      url: jdbc:mysql://localhost:3306/mytest?serverTimezone=UTC&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
    datasource2:
      url: jdbc:mysql://localhost:3306/mytest2?serverTimezone=UTC&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 9001
mybatis:
  mapper-locations: classpath:mapper/**/*.xml

1.3、配置类1: DataSourceConfig

package zheng.config;

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;

/**
 * @author: ztl
 * @date: 2024/02/06 22:59
 * @desc:
 */

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    public DataSource dataSource1() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource2() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }
}

1.4、配置类2: DynamicDataSource

相关解释:

  1. getConnection 是核心的,返回哪个数据库的链接的。
  2. afterPropertiesSet 是初始化的操作
  3. 实现了datasource接口这个肯定好理解,我们要返回一个动态数据源,也是个数据源嘛
  4. 实现了InitializingBean,是因为我们想要初始化set一些值,用到了afterPropertiesSet方法。当然,你也可以在构造方法中初始化操作,但是构造方法如果有多个的话,你难道要在每一个构造方法中都执行一个这个初始化的动作嘛?如果有10个构造,写10遍嘛?那100个构造呢,所以,spring为了避免这个局面,就用了afterPropertiesSet方法。
package zheng.config;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;


/**
 * @author: ztl
 * @date: 2024/02/08 22:29
 * @desc:
 */

@Component
@Primary // 将动态数据源作为核心返回。
         // (datasource1、datasource2、DynamicDataSource都会被spring扫描出来,如果只返回datasource1、2就没法动态切换了)
public class DynamicDataSource implements DataSource, InitializingBean {

    // 当前使用的数据源
    public static ThreadLocal<String > name = new ThreadLocal<>();

    // 写
    @Autowired
    DataSource dataSource1;
    // 读
    @Autowired
    DataSource dataSource2;

    @Override
    public Connection getConnection() throws SQLException {
        if (name.get().equals("W")){
            return dataSource1.getConnection();
        }else {
            return dataSource2.getConnection();
        }
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // todo: 这个是初始化的操作:(默认数据库是W库)
        name.set("W");
    }
}

1.5、controller

package zheng.controller;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import zheng.config.DynamicDataSource;
import zheng.entity.Frend;
import zheng.service.FrendService;

import java.util.List;

/**
 * @author: ztl
 * @date: 2024/02/06 23:06
 * @desc:
 */

@RestController
@RequestMapping("frend")
@Slf4j
public class FrendController {

    @Autowired
    private FrendService frendService;

    @GetMapping(value = "select")
    public List<Frend> select(){
        log.info("select start ...");
        // 读的操作,我们用读库
        DynamicDataSource.name.set("R");
        return frendService.list();
    }


    @GetMapping(value = "insert")
    public void in(){
        log.info("in start ...");
        // 写的操作,我们用写库
        DynamicDataSource.name.set("W");
        Frend frend = new Frend();
        frend.setName("ztl");
        frendService.save(frend);
    }
}

1.6、mapper

package zheng.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import zheng.entity.Frend;

import java.util.List;

public interface FrendMapper {

    @Select("SELECT * FROM Frend")
    List<Frend> list();

    @Insert("INSERT INTO  frend(`name`) VALUES (#{name})")
    void save(Frend frend);
}

2.代码优化

在上面的代码上,加一个注解,然后直接在注解上指定具体的数据源,比如说1就是a数据源,2就是b数据源。

针对于在controller层写数据源源的,代码侵入量大,不方便。
我们有两种解决方案:
1、aop。 更加适用于 大数据量,业务复杂的场景。(有多个不同的库,不同业务导致的)
2、mybatis插件。 更加适用于,读写分离的操作。 因为mybatis的插件可以很方便的知道我们现在是查询操作还是增删改操作。(只适用于mybaits持久层框架,如果是hibernate就不行了)
当然,你也可以判断sql,如果sql中包含某个表,用a库,不包含某个表,用b库。不过像一个表还行,几十张表,通过表名去判断查不同的库的话,太费劲了

除了aop以外,我们还有另一种实现方式,就是mybatisPlus的插件。因为通过插件,我们可以知道这个sql是查询、还是insert,像那种读写分离的数据源,是非常的适合的。

2.1、注解wr

就是,切换数据源时的注解,真实开发中,一般一个service只代表一个类的增删改查,所以可以直接把这个注解写在service上,而不是metnhod上

package zheng.annotation;

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

/**
 * @author: ztl
 * @date: 2024/02/21 22:34
 * @desc:
 */

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

    String value() default "W";
}

2.2、 @WR(“W”)

service,带了 @WR注解了

package zheng.service.impl;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import zheng.annotation.WR;
import zheng.entity.Frend;
import zheng.mapper.FrendMapper;
import zheng.service.FrendService;

import java.util.List;

/**
 * @author: ztl
 * @date: 2022/12/27 11:18
 * @desc:
 */

@Service
public class FrendImplService implements FrendService {

    @Autowired
    FrendMapper frendMapper;


    @Override
    @WR("R")
    public List<Frend> list() {
        return frendMapper.list();
    }

    @Override
    @WR("W")
    public void save(Frend frend) {
        //如果你想获取当前类的代理类(比如你是@Transaction,然后当前类自己调用自己类下的方法,
        // 是不会生效的,因为是代理类,你可以先获取到代理类,然后用代理类去执行自己类的方法,)。你可以:
//        FrendService o = (FrendService)AopContext.currentProxy();
//        System.out.println(o);
//        o.save(frend);
        frendMapper.save(frend);
    }
}

2.3、aop

package zheng.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import zheng.annotation.WR;
import zheng.config.DynamicDataSource;

/**
 * @author: ztl
 * @date: 2024/02/21 22:30
 * @desc:
 */

@Component
@Aspect
public class DynamicDataSourceAspect {

    /**
     * within 和 execute类似。within能制定到包,execute能制定到类中的方法。
     * 如果不指定within的话,spring会把全部的bean都扫描一下,我们目前只需要扫描service,
     * 因为我们在service上加的注解,扫描其他bean没意义,白白浪费性能而已,
     * 所以指定了service,并且带这个wr注解的话,就set一下多数据源的数据库的链接,
     * @param point
     * @param wr
     */
    @Before("within(zheng.service.impl.*) && @annotation(wr)")
    public void before(JoinPoint point, WR wr){
        String name = wr.value();
        DynamicDataSource.name.set(name);

        System.out.println("==============before:"+name);
    }
}

3、mybatisPlus的插件实现多数据源

DynamicDadaSourcePlugin

  • @Intercepts 是固定的写法
  • @Signature 是说你要给mybatis的哪个对象做代理。(插件其实是通过动态代理,在执行具体操作的时候进行增强)
  • Executor mysql数据库底层是通过这个executor来执行数据库操作。
  • method = “update” 其中增删改,都会调用这个update接口
  • method = “update” 代表着查。这样的话,增删改查,就都包含了。
  • invocation.getArgs();
    拿到当前方法的全部参数。(update的时候,就是update的参数,select的时候,就是select的参数)
  • MappedStatement 封装了具体的sql

mybatis源码中,MybatisAutoConfiguration mybatis的自动配置类,会自动的将interceptors的数组给注入进来,所以我们只需要定义这个对象就行
因为执行增删改查的时候不是都要通过这个executor嘛,那我们对这个对象进行一个加强的操作,来达到我们切换数据源的目的。
spring中的bean只有一个无参构造函数的时候呢,spring就会自动调用这个无参构造函数,并把所有的参数都进行自动注入,所以我们要将interceptor自动注入,只需要创建一个这个类型的bean对象即可。(mybatis的自动配置类就会自动帮我们注入进来)

3.1、MyMybatisInterceptor

package com.zheng.plugin;

import com.zheng.config.DynamicDataSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

/**
 * @author: ztl
 * @date: 2024/03/02 22:32
 * @desc:
 */

@Intercepts({
            @Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class}),
            @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class,
                    RowBounds.class, ResultHandler.class})
        })
public class DynamicDadaSourcePlugin implements Interceptor {

    /**
     * 具体的方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 拿到当前方法的全部参数。(update的时候,就是update的参数,select的时候,就是select的参数)
        Object[] objects = invocation.getArgs();
        // MappedStatement 封装了具体的sql、当前的操作类型(查询、update之类的)
        MappedStatement ms = (MappedStatement)objects[0];
        // 读方法
        if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)){
            System.out.println("111111+度方法");
            DynamicDataSource.name.set("R");
        }else {
            System.out.println("22222+写方法");
            DynamicDataSource.name.set("W");
        }
        // invocation.proceed() 这个是具体的调用
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor){
            return Plugin.wrap(target,this);
        }else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

3.2、DataSourceConfig

package com.zheng.config;

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;

/**
 * @author: ztl
 * @date: 2024/02/06 22:59
 * @desc:
 */

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    public DataSource dataSource1() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource2() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }
}

3.3、DynamicDataSource

package com.zheng.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

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


/**
 * @author: ztl
 * @date: 2024/02/08 22:29
 * @desc:
 */

@Component
@Primary // 将动态数据源作为核心返回。
         // (datasource1、datasource2、DynamicDataSource都会被spring扫描出来,如果只返回datasource1、2就没法动态切换了)
public class DynamicDataSource extends AbstractRoutingDataSource {

    // 当前使用的数据源
    public static ThreadLocal<String > name = new ThreadLocal<>();

    // 写
    @Autowired
    DataSource dataSource1;
    // 读
    @Autowired
    DataSource dataSource2;


    // 返回当前数据源的标识(此处是R/W)
    @Override
    protected Object determineCurrentLookupKey() {
        return name.get();
    }

    @Override
    public void afterPropertiesSet() {
        // 拿到多数据源中,全部的数据源
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("W",dataSource1);
        targetDataSources.put("R",dataSource2);

        super.setTargetDataSources(targetDataSources);

        // 设置默认的数据源
        super.setDefaultTargetDataSource(dataSource1);

        // 这个父类的方法还是需要的,不然spring没法把connection对象传递下去,
        super.afterPropertiesSet();
    }
}


总结

多数据源,到这就分享完毕了。下次应该会给大家分享一下,shardingsphere的用法,以及在我们的项目中,所遇到的问题及解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/505583.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s1.28.8版本配置Alertmanager报警方式(邮件,企业微信)

文章目录 总结部署流程 Alertmanager 三大核心1. 分组告警2. 告警抑制3. 告警静默 报警过滤静默通知方案一&#xff1a;方案二&#xff1a; 抑制报警规则案例一 参考文档 自定义路由告警&#xff0c;分来自不同路由的告警&#xff0c;艾特不同的人员进行区分修改 alertmanager …

中缀转后缀表达式

思路分析 遇到数字&#xff0c;直接输出遇到符号 栈为空&#xff0c;符号直接入栈若为 ( &#xff0c;则直接入栈用当前符号和栈顶符号比较优先级 当前符号 > 栈顶符号&#xff0c;当前符号直接入栈&#xff0c;结束当前符号 < 栈顶符号,栈顶符号出栈并输出&#xff0c;…

verilog中的testbench语句——display,fopen,fread,fwrite——更新中

一、fopen bmp_file_read $fopen("../pic/picture.bmp","rb"); 要注意这类操作文件的函数&#xff0c;在vivado2018自带的仿真软件里&#xff0c;不综合直接仿真&#xff0c;它的当前文件夹如图所示。 一、fwrite $fwrite(bmp_file_write,"%c"…

【Effective Web】页面优化

页面优化 页面渲染流程 JavaScript 》 Style 》 Layout 》 Paint 》 Composite 首先js做了一些逻辑&#xff0c;触发了样式变化&#xff0c;style计算好这些变化后&#xff0c;把影响的dom元素进行重新布局&#xff08;layout&#xff09;,再画到画布中&#xff08;Paint&am…

【数据结构与算法】二叉树遍历、判断和 diff 算法

遍历 深度优先遍历 function Node(value) {this.value valuethis.left nullthis.right null }let a new Node(a) let b new Node(b) let c new Node(c) let d new Node(d) let e new Node(e) let f new Node(f) let g new Node(g) a.left c a.right b c.l…

如何提升公众号搜索量?分享内部运营的5步优化技术!

最近一直有自媒体同行朋友在写关于公众号的内容&#xff0c;很多都说公众号现在没得玩了。其实&#xff0c;在运营自媒体上面&#xff0c;思维不通&#xff0c;技术不到位&#xff0c;哪个平台都不适合你玩。 想要在自媒体上面运营变现&#xff0c;一定不要先点击广告变现&…

【二分查找】查找数列中数第一次出现的编号

一道巩固二分查找知识的题&#xff0c;非常简单&#xff0c;一起做一下吧 题目&#xff1a; 答案&#xff1a; #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N1000010;int n,m; int q[N];bool isBlue(int num…

7种2024年算法优化BP,实现回归,单/多变量输入,单/多步预测功能,机器学习预测全家桶再更新!...

截止到本期MATLAB机器学习预测全家桶&#xff0c;一共发了19篇关于机器学习预测代码的文章。算上这一篇&#xff0c;一共20篇&#xff01;参考文章如下&#xff1a; 1.五花八门的机器学习预测&#xff1f;一篇搞定不行吗&#xff1f; 2.机器学习预测全家桶&#xff0c;多步预测…

中文乱码 一文讲解 字符集和字符编码 不再困惑(有源码)

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 这可能是应用级程序员最困惑的…

SSTI 服务器端模板注入(Server-Side Template Injection)

1.Web_python_template_injection {{}}是变量包裹标识符&#xff0c;里面存放的是一个变量&#xff0c;当你输入 http://61.147.171.105:55121/{{8*8}} 执行成功&#xff0c;说明存在模版注入。接下来&#xff0c;开始想办法编代码拿到服务器的控制台权限 。 首先&#xff0c…

Redis 命令行客户端

目 录 redis 客户端介绍 redis 客户端介绍 redis 是一个 客户端-服务器 结构的程序&#xff01;&#xff01;&#xff08;类似于 MySQL&#xff09; 因此 redis 客户端和服务器 可以在同一个主机上&#xff0c;也可以在不同主机上. Redis 的客户端也有很多种形态&#xff1a;…

2024 批量下载吾爱破解公众号文章内容/话题/图片/封面/视频/音频,导出文章pdf合集,excel数据包含阅读数留言数粉丝数

前几天看到吾爱破解论坛公众号文章吾爱破解精华集2023&#xff0c;于是把吾爱破解论坛公众号2022-2023年所有公众号文章也下载做成合集分享给大家&#xff0c;网盘地址https://pan.quark.cn/s/9c1b60b822a7 下载的excel文章数据包含文章日期&#xff0c;文章标题&#xff0c;文…

基于springboot实现图书个性化推荐系统项目【项目源码+论文说明】

基于springboot实现图书个性化推荐系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个图书个性化推荐系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论…

risc-v向量扩展strlen方法学习

riscv向量文档中给出了strlen的实现&#xff0c; 大概是这么一个思路&#xff0c; 加载向量: 使用向量加载指令&#xff08;如 vload&#xff09;从内存中加载一个向量长度的字符。比较向量与零: 使用向量比较指令&#xff08;如 vmask 或 vcmpeq&#xff09;来检查向量中的每…

Js之运算符与表达式

运算符&#xff1a;也叫操作符&#xff0c;是一种符号。通过运算符可以对一个或多个值进行运算&#xff0c;并获取运算结果。 表达式&#xff1a;由数字、运算符、变量的组合&#xff08;组成的式子&#xff09;。 表达式最终都会有一个运算结果&#xff0c;我们将这个结果称…

【电路笔记】-快速了解数字逻辑门

快速了解数字逻辑门 文章目录 快速了解数字逻辑门1、概述2、集成电路的分类3、摩尔定律4、数字逻辑状态5、数字逻辑噪声6、简单的基本数字逻辑门7、基本 TTL 逻辑门8、发射极耦合数字逻辑门9、集成电路的“74”子族10、基本 CMOS 数字逻辑门数字逻辑门是一种电子电路,它根据其…

C++从入门到精通——引用()

C的引用 前言一、C引用概念二、引用特性交换指针引用 三、常引用保证值不变权限的方法权限的放大权限的缩小权限的平移类型转换临时变量 四、引用的使用场景1. 做参数2. 做返回值 五、传值、传引用效率比较值和引用的作为返回值类型的性能比较 六、引用和指针的区别引用和指针的…

动态规划-最长回文子串

动态规划-最长回文子串 原题描述解答中心移动思想代码实现复杂度分析时间复杂度空间复杂度 动态规划思想代码实现复杂度分析时间复杂度空间复杂度 突然觉得很有必要将学过的内容记录下来&#xff0c;这样后续在需要用到的时候就可以避免从头进行学习&#xff0c;而去看自己之前…

调试技巧安全预编译头文件(C++基础)

调试 调试可以选择条件调试和操作调试&#xff1a; 条件调试来选择条件进入断点设置&#xff0c;操作调试来使达到断点条件后完成某些操作&#xff08;一般是output窗口输出&#xff09;。 在这里就只输出了小于6的条件。 安全 降低崩溃、内存泄露、非法访问等问题。 应该转…

GetSystemTimes:获取CPU占用率(WIN API)

原文链接&#xff1a;https://blog.csdn.net/qq_28742901/article/details/104960653 GetSystemTimes函数&#xff1a; BOOL WINAPI GetSystemTimes(__out_opt LPFILETIME lpIdleTime, // 空闲时间__out_opt LPFILETIME lpKernelTime, // 内核进程占用时间__out_opt LPFILETI…