18 | 生产环境多数据源的处理方法有哪些

news2024/11/28 6:50:10

工作中我们时常会遇到跨数据库操作的情况,这时候就需要配置多数据源,那么如何配置呢?常用的方式及其背后的原理支撑是什么呢?我们下面来了解一下。

首先看看两种常见的配置方式,分别为通过多个 @Configuration 文件、利用 AbstractRoutingDataSource 配置多数据源。

第一种方式:多个数据源的 @Configuration 的配置方法

这种方式的主要思路是,不同 Package 下面的实体和 Repository 采用不同的 Datasource。所以我们改造一下我们的 example 目录结构,来看看不同 Repositories 的数据源是怎么处理的。

第一步:规划 Entity 和 Repository 的目录结构,为了方便配置多数据源。

将 User 和 UserAddress、UserRepository 和 UserAddressRepository 移动到 db1 里面;将 UserInfo 和 UserInfoRepository 移动到 db2 里面。如下图所示:

Drawing 0.png

我们把实体和 Repository 分别放到了 db1 和 db2 两个目录里面,这时我们假设数据源 1 是 MySQL,User 表和 UserAddress 在数据源 1 里面,那么我们需要配置一个 DataSource1 的 Configuration 类,并且在里面配置 DataSource、TransactionManager 和 EntityManager。

第二步:配置 DataSource1Config 类。

目录结构调整完之后,接下来我们开始配置数据源,完整代码如下:

复制代码

@Configuration
@EnableTransactionManagement//开启事务
//利用EnableJpaRepositories配置哪些包下面的Repositories,采用哪个EntityManagerFactory和哪个trannsactionManager
@EnableJpaRepositories(
      basePackages = {"com.example.jpa.example1.db1"},//数据源1的repository的包路径
      entityManagerFactoryRef = "db1EntityManagerFactory",//改变数据源1的EntityManagerFactory的默认值,改为db1EntityManagerFactory
      transactionManagerRef = "db1TransactionManager"//改变数据源1的transactionManager的默认值,改为db1TransactionManager
      )
public class DataSource1Config {
   /**
    * 指定数据源1的dataSource配置
    * @return
    */
   @Primary
   @Bean(name = "db1DataSourceProperties")
   @ConfigurationProperties("spring.datasource1") //数据源1的db配置前缀采用spring.datasource1
   public DataSourceProperties dataSourceProperties() {
      return new DataSourceProperties();
   }
   /**
    * 可以选择不同的数据源,这里我用HikariDataSource举例,创建数据源1
    * @param db1DataSourceProperties
    * @return
    */
   @Primary
   @Bean(name = "db1DataSource")
   @ConfigurationProperties(prefix = "spring.datasource.hikari.db1") //配置数据源1所用的hikari配置key的前缀
   public HikariDataSource dataSource(@Qualifier("db1DataSourceProperties") DataSourceProperties db1DataSourceProperties) {
      HikariDataSource dataSource = db1DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
      if (StringUtils.hasText(db1DataSourceProperties.getName())) {
         dataSource.setPoolName(db1DataSourceProperties.getName());
      }
      return dataSource;
   }
   /**
    * 配置数据源1的entityManagerFactory命名为db1EntityManagerFactory,用来对实体进行一些操作
    * @param builder
    * @param db1DataSource entityManager依赖db1DataSource
    * @return
    */
   @Primary
   @Bean(name = "db1EntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("db1DataSource") DataSource db1DataSource) {
      return builder.dataSource(db2DataSource)
.packages("com.example.jpa.example1.db1") //数据1的实体所在的路径
.persistenceUnit("db1")// persistenceUnit的名字采用db1
.build();
   }
   /**
    * 配置数据源1的事务管理者,命名为db1TransactionManager依赖db1EntityManagerFactory
    * @param db1EntityManagerFactory 
    * @return
    */
   @Primary
   @Bean(name = "db1TransactionManager")
   public PlatformTransactionManager transactionManager(@Qualifier("db1EntityManagerFactory") EntityManagerFactory db1EntityManagerFactory) {
      return new JpaTransactionManager(db1EntityManagerFactory);
   }
}

到这里,数据源 1 我们就配置完了,下面再配置数据源 2。

第三步:配置 DataSource2Config类,加载数据源 2。

复制代码

@Configuration
@EnableTransactionManagement//开启事务
//利用EnableJpaRepositories,配置哪些包下面的Repositories,采用哪个EntityManagerFactory和哪个trannsactionManager
@EnableJpaRepositories(
        basePackages = {"com.example.jpa.example1.db2"},//数据源2的repository的包路径
        entityManagerFactoryRef = "db2EntityManagerFactory",//改变数据源2的EntityManagerFactory的默认值,改为db2EntityManagerFactory
        transactionManagerRef = "db2TransactionManager"//改变数据源2的transactionManager的默认值,改为db2TransactionManager
)
public class DataSource2Config {
    /**
     * 指定数据源2的dataSource配置
     *
     * @return
     */
    @Bean(name = "db2DataSourceProperties")
    @ConfigurationProperties("spring.datasource2") //数据源2的db配置前缀采用spring.datasource2
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }
    /**
     * 可以选择不同的数据源,这里我用HikariDataSource举例,创建数据源2
     *
     * @param db2DataSourceProperties
     * @return
     */
    @Bean(name = "db2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.hikari.db2") //配置数据源2的hikari配置key的前缀
    public HikariDataSource dataSource(@Qualifier("db2DataSourceProperties") DataSourceProperties db2DataSourceProperties) {
        HikariDataSource dataSource = db2DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        if (StringUtils.hasText(db2DataSourceProperties.getName())) {
            dataSource.setPoolName(db2DataSourceProperties.getName());
        }
        return dataSource;
    }
    /**
     * 配置数据源2的entityManagerFactory命名为db2EntityManagerFactory,用来对实体进行一些操作
     *
     * @param builder
     * @param db2DataSource entityManager依赖db2DataSource
     * @return
     */
    @Bean(name = "db2EntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("db2DataSource") DataSource db2DataSource) {
        return builder.dataSource(db2DataSource)
            .packages("com.example.jpa.example1.db2") //数据2的实体所在的路径
            .persistenceUnit("db2")// persistenceUnit的名字采用db2
            .build();
    }
    /**
     * 配置数据源2的事务管理者,命名为db2TransactionManager依赖db2EntityManagerFactory
     *
     * @param db2EntityManagerFactory
     * @return
     */
    @Bean(name = "db2TransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("db2EntityManagerFactory") EntityManagerFactory db2EntityManagerFactory) {
        return new JpaTransactionManager(db2EntityManagerFactory);
    }
}

这一步你需要注意,DataSource1Config 和 DataSource2Config 不同的是,1 里面每个 @Bean 都 @Primary,而 2 里面不是的。

第四步:通过 application.properties 配置两个数据源的值,代码如下:

复制代码

###########datasource1 采用Mysql数据库
spring.datasource1.url=jdbc:mysql://localhost:3306/test2?logger=Slf4JLogger&profileSQL=true
spring.datasource1.username=root
spring.datasource1.password=root
##数据源1的连接池的名字
spring.datasource.hikari.db1.pool-name=jpa-hikari-pool-db1
##最长生命周期15分钟够了
spring.datasource.hikari.db1.maxLifetime=900000
spring.datasource.hikari.db1.maximumPoolSize=8
###########datasource2 采用h2内存数据库
spring.datasource2.url=jdbc:h2:~/test
spring.datasource2.username=sa
spring.datasource2.password=sa
##数据源2的连接池的名字
spring.datasource.hikari.db2.pool-name=jpa-hikari-pool-db2
##最长生命周期15分钟够了
spring.datasource.hikari.db2.maxLifetime=500000
##最大连接池大小和数据源1区分开,我们配置成6个
spring.datasource.hikari.db2.maximumPoolSize=6

第五步:我们写个 Controller 测试一下。

复制代码

@RestController
public class UserController {
   @Autowired
   private UserRepository userRepository;
   @Autowired
   private UserInfoRepository userInfoRepository;
   //操作user的Repository
   @PostMapping("/user")
   public User saveUser(@RequestBody User user) {
      return userRepository.save(user);
   }
   //操作userInfo的Repository
  @PostMapping("/user/info")
  public UserInfo saveUserInfo(@RequestBody UserInfo userInfo) {
     return userInfoRepository.save(userInfo);
  }
}

第六步:直接启动我们的项目,测试一下。

请看这一步的启动日志:

Drawing 1.png
Drawing 2.png

可以看到启动的是两个数据源,其对应的连接池的监控也是不一样的:数据源 1 有 8 个,数据源 2 有 6 个。

Drawing 3.png

如果我们分别请求 Controller 写的两个方法的时候,也会分别插入到不同的数据源里面去。

通过上面的六个步骤你应该知道了如何配置多数据源,那么它的原理基础是什么呢?我们看一下

Datasource 与 TransactionManager、EntityManagerFactory 的关系和职责分别是怎么样的。

Datasource 与 TransactionManager、EntityManagerFactory 的关系分析

我们通过一个类的关系图来分析一下:

Drawing 4.png

其中,

  1. HikariDataSource 负责实现 DataSource,交给 EntityManager 和 TransactionManager 使用;
  2. EntityManager 是利用 Datasouce 来操作数据库,而其实现类是 SessionImpl;
  3. EntityManagerFactory 是用来管理和生成 EntityManager 的,而 EntityManagerFactory 的实现类是 LocalContainerEntityManagerFactoryBean,通过实现 FactoryBean 接口实现,利用了 FactoryBean 的 Spring 中的 bean 管理机制,所以需要我们在 Datasource1Config 里面配置 LocalContainerEntityManagerFactoryBean 的 bean 的注入方式;
  4. JpaTransactionManager 是用来管理事务的,实现了 TransactionManager 并且通过 EntityFactory 和 Datasource 进行 db 操作,所以我们要在 DataSourceConfig 里面告诉 JpaTransactionManager 用的 TransactionManager 是 db1EntityManagerFactory。

上一讲我们介绍了 Datasource 的默认加载和配置方式,那么默认情况下 Datasource 的 EntityManagerFactory 和 TransactionManager 是怎么加载和配置的呢?

默认的 JpaBaseConfiguration 的加载方式分析

上一讲我只简单说明了 DataSource 的配置,其实我们还可以通过 HibernateJpaConfiguration,找到父类 JpaBaseConfiguration 类,如图所示:

Drawing 5.png

接着打开 JpaBaseConfiguration 就可以看到多数据源的参考原型,如下图所示:

Drawing 6.png

通过上面的代码,可以看到在单个数据源情况下的 EntityManagerFactory 和 TransactionManager 的加载方法,并且我们在多数据源的配置里面还加载了一个类:EntityManagerFactoryBuilder entityManagerFactoryBuilder,也正是从上面的方法加载进去的,看第 120 行代码就知道了。

那么除了上述的配置多数据源的方式,还有没有其他方法了呢?我们接着看一下。

第二种方式:利用 AbstractRoutingDataSource 配置多数据源

我们都知道 DataSource 的本质是获得数据库连接,而 AbstractRoutingDataSource 帮我们实现了动态获得数据源的可能性。下面还是通过一个例子看一下它是怎么使用的。

第一步:定一个数据源的枚举类,用来标示数据源有哪些。

复制代码

/**
 * 定义一个数据源的枚举类
 */
public enum RoutingDataSourceEnum {
   DB1, //实际工作中枚举的语义可以更加明确一点;
   DB2;
   public static RoutingDataSourceEnum findbyCode(String dbRouting) {
      for (RoutingDataSourceEnum e : values()) {
         if (e.name().equals(dbRouting)) {
            return e;
         }
      }
      return db1;//没找到的情况下,默认返回数据源1
   }
}

第二步:新增 DataSourceRoutingHolder,用来存储当前线程需要采用的数据源。

复制代码

/**
 * 利用ThreadLocal来存储,当前的线程使用的数据
 */
public class DataSourceRoutingHolder {
   private static ThreadLocal<RoutingDataSourceEnum> threadLocal = new ThreadLocal<>();
   public static void setBranchContext(RoutingDataSourceEnum dataSourceEnum) {
      threadLocal.set(dataSourceEnum);
   }
   public static RoutingDataSourceEnum getBranchContext() {
      return threadLocal.get();
   }
   public static void clearBranchContext() {
      threadLocal.remove();
   }
}

第三步:配置 RoutingDataSourceConfig,用来指定哪些 Entity 和 Repository 采用动态数据源。

复制代码

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
      //数据源的repository的包路径,这里我们覆盖db1和db2的包路径
      basePackages = {"com.example.jpa.example1"},
      entityManagerFactoryRef = "routingEntityManagerFactory",
      transactionManagerRef = "routingTransactionManager"
)
public class RoutingDataSourceConfig {
   @Autowired
   @Qualifier("db1DataSource")
   private DataSource db1DataSource;
   @Autowired
   @Qualifier("db2DataSource")
   private DataSource db2DataSource;
   /**
    * 创建RoutingDataSource,引用我们之前配置的db1DataSource和db2DataSource
    *
    * @return
    */
   @Bean(name = "routingDataSource")
   public DataSource dataSource() {
      Map<Object, Object> dataSourceMap = Maps.newHashMap();
      dataSourceMap.put(RoutingDataSourceEnum.DB1, db1DataSource);
      dataSourceMap.put(RoutingDataSourceEnum.DB2, db2DataSource);
      RoutingDataSource routingDataSource = new RoutingDataSource();
      //设置RoutingDataSource的默认数据源
      routingDataSource.setDefaultTargetDataSource(db1DataSource);
      //设置RoutingDataSource的数据源列表
      routingDataSource.setTargetDataSources(dataSourceMap);
      return routingDataSource;
   }
   /**
    * 类似db1和db2的配置,唯一不同的是,这里采用routingDataSource
    * @param builder
    * @param routingDataSource entityManager依赖routingDataSource
    * @return
    */
   @Bean(name = "routingEntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("routingDataSource") DataSource routingDataSource) {
      return builder.dataSource(routingDataSource).packages("com.example.jpa.example1") //数据routing的实体所在的路径,这里我们覆盖db1和db2的路径
            .persistenceUnit("db-routing")// persistenceUnit的名字采用db-routing
            .build();
   }
   /**
    * 配置数据的事务管理者,命名为routingTransactionManager依赖routtingEntityManagerFactory
    *
    * @param routingEntityManagerFactory
    * @return
    */
   @Bean(name = "routingTransactionManager")
   public PlatformTransactionManager transactionManager(@Qualifier("routingEntityManagerFactory") EntityManagerFactory routingEntityManagerFactory) {
      return new JpaTransactionManager(routingEntityManagerFactory);
   }
}

路由数据源配置与 DataSource1Config 和 DataSource2Config 有相互覆盖关系,这里我们直接覆盖 db1 和 db2 的包路径,以便于我们的动态数据源生效。

第四步:写一个 MVC 拦截器,用来指定请求分别采用什么数据源。

新建一个类 DataSourceInterceptor,用来在请求前后指定数据源,请看代码:

复制代码

/**
 * 动态路由的实现逻辑,我们通过请求里面的db-routing,来指定此请求采用什么数据源
 */
@Component
public class DataSourceInterceptor extends HandlerInterceptorAdapter {
   /**
    * 请求处理之前更改线程里面的数据源
    */
   @Override
   public boolean preHandle(HttpServletRequest request,
                      HttpServletResponse response, Object handler) throws Exception {
      String dbRouting = request.getHeader("db-routing");
      DataSourceRoutingHolder.setBranchContext(RoutingDataSourceEnum.findByCode(dbRouting));
      return super.preHandle(request, response, handler);
   }
   /**
    * 请求结束之后清理线程里面的数据源
    */
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      super.afterCompletion(request, response, handler, ex);
      DataSourceRoutingHolder.clearBranchContext();
   }
}

同时我们需要在实现 WebMvcConfigurer 的配置里面,把我们自定义拦截器 dataSourceInterceptor 加载进去,代码如下:

复制代码

/**
 * 实现WebMvcConfigurer
 */
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
   @Autowired
   private DataSourceInterceptor dataSourceInterceptor;
   //添加自定义拦截器
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
      WebMvcConfigurer.super.addInterceptors(registry);
   }
...//其他不变的代码省略}

此处我们采用的是 MVC 的拦截器机制动态改变的数据配置,你也可以使用自己的 AOP 任意的拦截器,如事务拦截器、Service 的拦截器等,都可以实现。需要注意的是,要在开启事务之前配置完毕。

第五步:启动测试。

我们在 Http 请求头里面加上 db-routing:DB2,那么本次请求就会采用数据源 2 进行处理,请求代码如下:

复制代码

POST /user/info HTTP/1.1
Host: 127.0.0.1:8089
Content-Type: application/json
db-routing: DB2
Cache-Control: no-cache
Postman-Token: 56d8dc02-7f3e-7b95-7ff1-572a4bb7d102
{"ages":10}

通过上面五个步骤,我们可以利用 AbstractRoutingDataSource 实现动态数据源,实际工作中可能会比我讲述的要复杂,有的需要考虑多线程、线程安全等问题,你要多加注意。
在实际应用场景中,对于多数据源的问题,我还有一些思考,下面分享给你。

微服务下多数据源的思考:还需要这样用吗?

通过上面的两种方式,我们分别可以实现同一个 application 应用的多数据源配置,那么有什么注意事项呢?我简单总结如下几点建议。

多数据源实战注意事项
  1. 此种方式利用了当前线程事务不变的原理,所以要注意异步线程的处理方式;
  2. 此种方式利用了 DataSource 的原理,动态地返回不同的 db 连接,一般需要在开启事务之前使用,需要注意事务的生命周期;
  3. 比较适合读写操作分开的业务场景;
  4. 多数据的情况下,避免一个事务里面采用不同的数据源,这样会有意想不到的情况发生,比如死锁现象;
  5. 学会通过日志检查我们开启请求的方法和开启的数据源是否正确,可以通过 Debug 断点来观察数据源是否选择的正确,如下图所示:

Drawing 7.png

微服务下的实战建议

在实际工作中,为了便捷省事,更多开发者喜欢配置多个数据源,但是我强烈建议不要在对用户直接提供的 API 服务上面配置多数据源,否则将出现令人措手不及的 Bug。

如果你是做后台管理界面,供公司内部员工使用的,那么这种 API 可以为了方便而使用多数据源。

微服务的大环境下,服务越小,内聚越高,低耦合服务越健壮,所以一般跨库之间一定是是通过 REST 的 API 协议,进行内部服务之间的调用,这是最稳妥的方式,原因有如下几点:

  1. REST 的 API 协议更容易监控,更容易实现事务的原子性;
  2. db 之间解耦,使业务领域代码职责更清晰,更容易各自处理各种问题;
  3. 只读和读写的 API 更容易分离和管理。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

绘制多个子图fig.add_subplot函数

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 绘制多个子图 fig.add_subplot函数 下列代码创建的子图网格大小是&#xff1f; import matplotlib.pyplot as plt fig plt.figure() ax fig.add_subplot(121) ax.plot([1, 2, 3, 4, 5], [1…

做情绪识别,有必要用LLM吗?

卷友们好&#xff0c;我是尚霖。 情绪识别在各种对话场景中具有广泛的应用价值。例如&#xff0c;在社交媒体中&#xff0c;可以通过对评论进行情感分析来了解用户的情绪态度&#xff1b;在人工客服中&#xff0c;可以对客户的情绪进行分析&#xff0c;以更好地满足其需求。 此…

堆与堆排序

一.什么是堆&#xff1f; 1.堆是完全二叉树&#xff0c;除了树的最后一层结点不需要是满的&#xff0c;其它的每一层从左到右都是满的&#xff0c;如果最后一层结点不是满的&#xff0c;那么要求左满右不满。 2.堆分为两类&#xff0c;大根堆和小根堆。 大根堆每个结点都大于…

C++位图,布隆过滤器

本期我们来学习位图&#xff0c;布隆过滤器等相关知识&#xff0c;以及模拟实现&#xff0c;需求前置知识 C-哈希Hash-CSDN博客 C-封装unordered_KLZUQ的博客-CSDN博客 目录 位图 布隆过滤器 海量数据面试题 全部代码 位图 我们先来看一道面试题 给 40 亿个不重复的无符号…

scratch时间游戏 2023年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试三级真题和答案解析

目录 scratch时间游戏 一、题目要求 1、准备工作 2、功能实现 二、案例分析

EtherCAT报文-FPWR(配置地址写)抓包分析

0.工具准备 1.EtherCAT主站 2.EtherCAT从站&#xff08;本文使用步进电机驱动器&#xff09; 3.Wireshark1.EtherCAT报文帧结构 EtherCAT使用标准的IEEE802.3 Ethernet帧结构&#xff0c;帧类型为0x88A4。EtherCAT数据包括2个字节的数据头和44-1498字节的数据。数据区由一个或…

芯片学习记录TLP184

TLP184 芯片介绍 TLP184是一款光耦隔离器&#xff0c;它的主要特点包括&#xff1a;高电压耐受能力、高传输速度、高共模隔离能力、低功耗等。它可以用于工业自动化、通信设备、家用电器等领域的电气隔离应用。由一个光电晶体管组成&#xff0c;光学耦合到两个红外发射二极管…

[初始java]——规范你的命名规则,变量的使用和注意事项,隐式转化和强制转化

目录 一、标识符是么 二、命名规则 三、变量 1.定义变量的完整格式&#xff1a; 2.变量的分类 3.变量在内存中的位置 4.注意事项&#xff1a; 四、隐式转化和强制转化 五、表达式和语句的概念 一、标识符是么? 就是给类、变量、方法起名字的&#xff0c;用于标识它们。…

内网、外网、宽带、带宽、流量、网速之间的区别与联系

一.带宽与宽带的区别是什么&#xff1f; 带宽是量词&#xff0c;指的是网速的大小&#xff0c;比如1Mbps的意思是一兆比特每秒&#xff0c;这个数值就是指带宽。 宽带是名词&#xff0c;说明网络的传输速率速很高 。宽带的标准各不相同&#xff0c;最初认为128kbps以上带宽的就…

从0开始学Java:Java基础语法

文章目录 1. 注释2. 关键字&#xff08;*Keyword*&#xff09;3. 标识符( Identifier)4. 常量&#xff08;*Constant*&#xff09;5. 输出语句6. 变量&#xff08;*Variable*&#xff09;7. 计算机如何存储数据7.1 进制7.2 计算机存储单位7.3 二进制数据存储 8. 数据类型8.1 数…

一行 Python 代码搞定训练分类或回归模型

引言 自动机器学习(Auto-ML)是指自动化数据科学模型开发流水线的组件。AutoML 减少了数据科学家的工作量&#xff0c;并加快了工作流程。AutoML 可用于自动化各种流水线组件&#xff0c;包括数据理解&#xff0c;EDA&#xff0c;数据处理&#xff0c;模型训练&#xff0c;超参数…

C++11(lambda表达式)

目录 一、lambda表达式的引入 二、语法格式 三、捕捉方式 四、lambda表达式的底层 1、仿函数的调用 2、lambda的调用 ​编辑 一、lambda表达式的引入 在之前&#xff0c;我们调用函数的方式有&#xff1a;通过函数指针调用&#xff0c;仿函数也能像函数一样调用。而在C…

pgsl基于docker的安装

1. 有可用的docker环境 &#xff0c;如果还没有安装docker&#xff0c;则请先安装docker 2. 创建pg数据库的挂载目录 mkdir postgres 3. 下载pg包 docker pull postgres 这个命令下载的是最新的pg包&#xff0c;如果要指定版本的话&#xff0c;则可以通过在后面拼接 :versio…

12 | JPA 的审计功能解决了哪些问题

Auditing 指的是什么&#xff1f; Auditing 是帮我们做审计用的&#xff0c;当我们操作一条记录的时候&#xff0c;需要知道这是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候&#xff0c;甚至需要修改记录……这些都是 Spring Data JPA 里面的 Auditing…

【LeetCode刷题(数据结构)】:给定一个链表 返回链表开始入环的第一个节点 如果链表无环 则返回 NULL

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos…

【Eclipse】查看版本号

1.在Eclipse的启动页面会出现版本号 2. Eclipse的关于里面 Help - About Eclipse IDE 如下图所示&#xff0c;就为其版本 3.通过查看readme_eclipse.html文件

华为浏览器风险提示 - 解决方案

问题 使用华为手机自带的华为浏览器时&#xff0c;可能会遇到网页提示风险提示且无法打开的情况&#xff0c;如下图。这是因为华为浏览器开启了安全浏览功能&#xff0c;下文介绍解决方案。 解决方案 取消华为浏览器设置中的安全浏览功能即可&#xff0c;操作步骤如下。打开…

EtherCAT报文-FPRD(配置地址读)抓包分析

0.工具准备 1.EtherCAT主站 2.EtherCAT从站&#xff08;本文使用步进电机驱动器&#xff09; 3.Wireshark1.EtherCAT报文帧结构 EtherCAT使用标准的IEEE802.3 Ethernet帧结构&#xff0c;帧类型为0x88A4。EtherCAT数据包括2个字节的数据头和44-1498字节的数据。数据区由一个或…

数据结构-表、树、图

一、表 1.1、散列表 也叫哈希表&#xff0c;把数据分散在列表中&#xff0c;依赖于数组下标访问的特性&#xff0c;数组的一种拓展。 散列思想&#xff1a; 即映射思想&#xff0c;用键值对来保存信息&#xff0c;键&#xff08;key&#xff09;和值&#xff08;value&a…

E117-经典赛题-主机发现与信息收集

任务实施: E117-经典赛题-主机发现与信息收集 任务环境说明&#xff1a; 服务器场景&#xff1a;p9_bt5-1&#xff08;用户名&#xff1a;root&#xff1b;密码&#xff1a;toor&#xff09; 服务器场景操作系统&#xff1a;Back Track five kali Linux 192.168.32.1…