《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)

ops/2024/12/28 0:48:07/

文章目录

  • 第七章 SQL执行器的定义和实现
    • 背景
      • 技术背景
        • 模板模式
        • 特点
        • 结构
        • 示例代码(Java)
      • 业务背景
    • 目标
    • 设计
    • 实现
      • 工程代码
      • 类图
      • 实现步骤
        • 1.执行器的定义和实现
          • 1-1. Executor定义执行器接口
          • 1-2.BaseExecutor执行器抽象基类
          • 1-3. SimpleExecutor 简单执行器实现
        • 2.语句处理器
          • 2-1.StatementHandler定义语句处理接口
          • 2-2. BaseStatementHandler 语句处理器抽象基类
          • 2-3. SimpleStatementHandler 简单语句处理器类
          • 2-4. PrepareStatementHandler 预处理语句处理器类
        • 3.执行器创建和使用
          • 3-1. 开启执行器
          • 3-2. 使用执行器
    • 测试
      • 事先准备
      • 测试用例
        • 单元测试
      • 测试结果
    • 总结


第七章 SQL执行器的定义和实现

<a class=mybatis" />

背景

技术背景

模板模式

模板模式(Template Method Pattern)是行为型设计模式之一,它在父类中定义了一个算法的框架,允许子类在不改变算法结构的情况下重写算法的某些特定步骤。这种模式主要用来解决代码复用的问题,它通过把一些相同步骤提取到父类中,而将不同的步骤延迟到子类中实现,从而避免代码重复。

特点
  1. 算法框架:在父类中定义算法的骨架,这些算法通常包含一系列的步骤。
  2. 扩展性:子类可以重新定义算法的某些步骤,而不影响算法的结构。
  3. 代码复用:通过提取公共代码到父类,减少代码重复。
  4. 控制反转:父类控制算法的流程,子类提供具体的实现。
结构

模板模式通常包含以下角色:

  • 抽象类(Abstract Class):定义模板方法和算法框架,其中模板方法是一个调用一系列抽象操作的方法。
  • 具体类(Concrete Class):实现抽象类中的抽象方法,这些方法可以是具体的方法,也可以是其他抽象方法。
  • 钩子方法(Hook Method):在抽象类中定义,可以被子类重写,但不带有抽象方法的声明。
示例代码(Java)
java">// 抽象类
abstract class Game {// 模板方法final void play() {initialize();startPlay();endPlay();}// 抽象方法,由子类实现abstract void initialize();abstract void startPlay();abstract void endPlay();
}// 具体类
class Cricket extends Game {@Overridevoid initialize() {System.out.println("Cricket game initialized.");}@Overridevoid startPlay() {System.out.println("Cricket game started.");}@Overridevoid endPlay() {System.out.println("Cricket game finished.");}
}// 具体类
class Football extends Game {@Overridevoid initialize() {System.out.println("Football game initialized.");}@Overridevoid startPlay() {System.out.println("Football game started.");}@Overridevoid endPlay() {System.out.println("Football game finished.");}
}// 客户端代码
public class TemplatePatternDemo {public static void main(String[] args) {Game game = new Cricket();game.play();game = new Football();game.play();}
}

在这个例子中,Game 类定义了游戏的算法框架,而 CricketFootball 类分别实现了具体的游戏初始化、开始和结束的方法。这样,不同的游戏可以共享相同的游戏流程,同时保持各自的特性。

业务背景

在第6章节我们实现了连接池/非连接池的数据源,可以在调用执行SQL的时候,通过我们实现的池化技术完成数据库的操作。

那么关于池化数据源的调用、执行和结果封装,目前只是在 DefaultSqlSession 中进行发起 ,代码层面写死了流程,这种方式不适用于后续扩展使用,因为SqlSession 中新增定义的方法都要处理对池化数据源的调用逻辑。

java"> @Overridepublic <T> T selectOne(String statement, Object parameter) {try {// 映射语句MappedStatement mappedStatement = configuration.getMappedStatement(statement);// 环境Environment environment = configuration.getEnvironment();// 连接Connection connection = environment.getDataSource().getConnection();BoundSql boundSql = mappedStatement.getBoundSql();PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());preparedStatement.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));// 执行查询ResultSet resultSet = preparedStatement.executeQuery();// 结果封装List<T> objectList = resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));return objectList.get(0);} catch (Exception e) {e.printStackTrace();return null;}}
  • 解耦 DefaultSqlSession#selectOne 方法中关于对数据源的调用、执行和结果封装,提供新的功能模块替代这部分硬编码的逻辑处理。
  • 只有提供了单独的执行方法入口,我们才能更好的扩展和应对这部分内容里的需求变化,包括了各类入参、结果封装、执行器类型、批处理等,来满足不同样式的用户需求,也就是配置到 Mapper.xml 中的具体信息。

目标

基于当前框架实现,解耦 DefaultSqlSession#selectOne 方法中关于对数据源的调用执行和结果封装,提供SQL执行器功能模块替代这部分硬编码的逻辑处理。

设计

ORM 框架渐进式的开发过程上,逐步实现的执行动作:解析配置代理对象映射方法等,直至我们前面章节对数据源的包装和使用,只不过我们把数据源的操作硬编码到了 DefaultSqlSession 的执行方法上了。

为了解耦这块的功能处理,需要引入SQL执行器的服务功能,将SQL执行器随着 DefaultSqlSession 创建时传入,之后具体的方法调用就可以通过执行器来处理。
整体设计如图 :

image-20241225102235660

  • 提取执行器的接口,定义执行方法、事务获取和相应提交、回滚、关闭的方法定义;
  • 执行器是一种标准的执行过程,由抽象类进行实现,可以对过程内容进行模板模式的过程包装。在包装过程中定义抽象类,由具体的子类来实现。这一部分在下文的代码中会体现到 SimpleExecutor 简单执行器实现中;
  • 对 SQL 的处理,使用 JDBC 执行 SQL 的时候,分为了简单处理和预处理,预处理中包括准备语句、参数化传递、执行查询,以及最后的结果封装和返回。所以我们这里也需要把 JDBC 这部分的步骤,分为结构化的类过程来实现,便于功能的拓展。具体代码主要体现在语句处理器 StatementHandler 的接口实现中。

实现

工程代码

image-20241225102550691

类图

image-20241225110638345
  • Executor 接口定义为执行器入口,确定事务操作和 SQL 执行的统一标准。用BaseExecutor抽象类处理统一共用的事务和执行SQL的标准流程,并定义执行 SQL 的抽象接口#doQuery(),后续由子类实现。
  • SimpleExecutor 简单SQL 执行器实现类中,处理 doQuery 方法的具体操作过程。这个过程中则会引入 SQL 语句处理器的创建,创建过程仍由 configuration 配置项提供。你会发现很多这样的生成处理,都来自于配置项
  • 执行器开发完成后,由DefaultSqlSessionFactory 开启 openSession 的时候,随着构造函数参数传递给 DefaultSqlSession 中,这样在执行 DefaultSqlSession#selectOne 的时候就可以调用执行器进行处理了。也就由此完成解耦操作了。

实现步骤

1.执行器的定义和实现

执行器分为接口、抽象类、简单执行器实现类三部分,通常在框架的源码中对于一些标准流程的处理,都会有抽象类的存在。它负责提供共性功能逻辑,以及对接口方法的执行过程进行定义和处理,并提供抽象接口交由子类实现。这种设计模式也被定义为模板模式。

1-1. Executor定义执行器接口
  • 在执行器中定义的接口包括事务相关的处理方法和执行SQL查询的操作,随着后续功能的迭代还会继续补充其他的方法。
java">public interface Executor {ResultHandler NO_RESULT_HANDLER = null;<E> List<E> query(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql);Transaction getTransaction();void commit(boolean required) throws Exception;void rollback(boolean required) throws Exception;void close(boolean forceRollback);}
1-2.BaseExecutor执行器抽象基类
  • 封装了执行器的全部接口,这样具体的子类继承抽象类后,就可以直接使用这些共性的方法。在 query 查询方法中,封装一些必要的流程处理,如果检测关闭等,在 Mybatis 源码中还有一些缓存的操作,这里暂时剔除掉,以核心流程为主。读者伙伴在学习的过程中可以与源码进行对照学习。
java">public abstract class BaseExecutor implements Executor {Logger logger = LoggerFactory.getLogger(BaseExecutor.class);protected Configuration configuration;protected Transaction transaction;protected Executor wrapper;private boolean closed;protected BaseExecutor(Configuration configuration, Transaction transaction) {this.configuration = configuration;this.transaction = transaction;this.wrapper = this;}// 连接关闭处理统一逻辑@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {if (closed) {throw new RuntimeException("Executor was closed.");}return doQuery(ms, parameter, resultHandler, boundSql);}protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql);@Overridepublic Transaction getTransaction() {if (closed) {throw new RuntimeException("Executor was closed.");}return transaction;}@Overridepublic void commit(boolean required) throws Exception {if (closed) {throw new RuntimeException("Cannot commit, transaction is already closed");}if (required) {transaction.commit();}}@Overridepublic void rollback(boolean required) throws SQLException {if (closed) {throw new RuntimeException("Cannot rollback, transaction is already closed");}if (required) {transaction.rollback();}}@Overridepublic void close(boolean forceRollback) {try {try {rollback(forceRollback);} finally {transaction.close();}} catch (SQLException e) {logger.warn("Unexpected exception on closing transaction.  Cause: " + e);} finally {transaction = null;closed = true;}}
}
1-3. SimpleExecutor 简单执行器实现
  • 简单执行器 SimpleExecutor 继承抽象基类,实现抽象方法 doQuery,在这个方法中包装数据源的获取、语句处理器的创建,以及对 Statement 的实例化和相关参数设置。最后执行 SQL 的处理和结果的返回操作。
java">public class SimpleExecutor extends BaseExecutor {public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}@Overrideprotected <E> List<E> doQuery(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {//处理查询逻辑.try {//获取配置信息Configuration configuration = ms.getConfiguration();//获取StatementHandlerStatementHandler handler = configuration.newStatementHandler(this, ms, parameter, resultHandler, boundSql);//获取连接Connection connection = transaction.getConnection();Statement stmt = handler.prepare(connection);handler.parameterize(stmt);return handler.query(stmt, resultHandler);} catch (SQLException e) {e.printStackTrace();return null;}}
}
2.语句处理器

语句处理器是 SQL 执行器中依赖的部分,SQL 执行器封装事务、连接和检测环境等,而语句处理器则是准备语句、参数化传递、执行SQL、封装结果的处理

2-1.StatementHandler定义语句处理接口
  • 语句处理器的核心: 准备语句参数化传递参数执行查询的操作,这里对应的 Mybatis 源码中还包括了 update、批处理、获取参数处理器等。
java">public interface StatementHandler {/*** 准备语句*/Statement prepare(Connection connection) throws SQLException;/*** 参数化*/void parameterize(Statement statement) throws SQLException;/*** 执行查询*/<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
}
2-2. BaseStatementHandler 语句处理器抽象基类
  • 语句处理器基类中,封装参数信息、结果信息。不过这里我们暂时还不会做过多的参数处理,比如jdbc的字段类型转换等。这部分内容随着我们整个执行器的结构建设完毕后,再进行迭代开发。
  • 对 BaseStatementHandler#prepare 方法的处理,包括定义实例化抽象方法,这个方法交由各个具体的实现子类进行处理。
java">public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;protected final Executor executor;protected final MappedStatement mapperStatement;protected final Object parameter;protected final ResultSetHandler resultSetHandler;protected BoundSql boundSql;protected BaseStatementHandler(Executor executor, MappedStatement mapperStatement, Object parameter,ResultHandler resultHandler,BoundSql boundSql) {this.executor = executor;this.configuration = mapperStatement.getConfiguration();this.mapperStatement = mapperStatement;this.parameter = parameter;this.boundSql = boundSql;this.resultSetHandler = configuration.newResultSetHandler(executor, mapperStatement, boundSql);}@Overridepublic Statement prepare(Connection connection) throws SQLException {Statement statement = null;try {statement = instantiateStatement(connection);statement.setQueryTimeout(350);statement.setFetchSize(10000);return statement;} catch (Exception e) {throw new RuntimeException("Error preparing statement.  Cause: " + e, e);}}protected abstract Statement instantiateStatement(Connection connection) throws SQLException;}
2-3. SimpleStatementHandler 简单语句处理器类
  • SimpleStatementHandler 简单语句处理器,只是对 SQL 的最基本执行,没有参数的设置.
java">public class SimpleStatementHandler extends BaseStatementHandler {public SimpleStatementHandler(Executor executor, MappedStatement mapperStatement, Object parameter,ResultHandler resultHandler,BoundSql boundSql) {super(executor, mapperStatement, parameter, resultHandler, boundSql);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {return connection.createStatement();}@Overridepublic void parameterize(Statement statement) throws SQLException {// N/A}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {String sql = boundSql.getSql();statement.execute(sql);return resultSetHandler.handleResultSets(statement);}
}
2-4. PrepareStatementHandler 预处理语句处理器类
  • 预处理语句处理器中包括 instantiateStatement 预处理 SQL、parameterize 设置参数,以及 query 查询的执行的操作。
  • 这里需要注意,当前 parameterize 设置参数中还是写死的,后续这部分再进行完善。
  • query 方法则是执行查询和对结果的封装,结果的封装目前也是比较简单的处理,只是把我们前面章节中对象的内容摘取出来进行封装,这部分暂时没有改变。都放在后续进行完善处理。
java">public class PreparedStatementHandler extends BaseStatementHandler {public PreparedStatementHandler(Executor executor, MappedStatement mapperStatement, Object parameter,ResultHandler resultHandler,BoundSql boundSql) {super(executor, mapperStatement, parameter, resultHandler, boundSql);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();return connection.prepareStatement(sql);}@Overridepublic void parameterize(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.<E>handleResultSets(ps);}
}
3.执行器创建和使用

执行器开发完成以后,则需要在串联到 DefaultSqlSession 中进行使用。串联过程:在 创建 DefaultSqlSession 的时候,构建出执行器,并作为参数传递到defaultSqlSession。这块涉及到 DefaultSqlSessionFactory#openSession 的处理。

3-1. 开启执行器
  • openSession 中开启事务tx, 用于执行器的创建,具体实现可以看 configuration.newExecutor 代码,这部分没有太多复杂的逻辑。读者可以参考源码进行学习。
  • 在执行器创建完毕后,以参数方式传递给 DefaultSqlSession,这样就把整个过程串联起来了。
java">public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();TransactionFactory transactionFactory = environment.getTransactionFactory();tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(),TransactionIsolationLevel.READ_COMMITTED,false);// 创建执行器final Executor executor = configuration.newExecutor(tx);// 创建DefaultSqlSessionreturn new DefaultSqlSession(configuration, executor);} catch (Exception e) {try {assert tx != null;tx.close();} catch (SQLException ignore) {}throw new RuntimeException("Error opening session.  Cause: " + e);}}
}
3-2. 使用执行器
  • 完成上面执行器的所有实现后,就可以调用执行器进行功能解耦了。在 DefaultSqlSession#selectOne 中获取 MappedStatement 映射语句类后,则传递给执行器进行处理,那么现在这个类经过设计思想的解耦后,就变得更加干净整洁了,更易于维护和扩展了。
java">public class DefaultSqlSession implements SqlSession {/*** 配置项.*/private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}public Configuration getConfiguration() {return configuration;}/*** 根据给定的执行SQL获取一条记录的封装对象.** @param statement* @param <T>* @return*/@Overridepublic <T> T selectOne(String statement) {return this.selectOne(statement, null);}@Overridepublic <T> T selectOne(String statement, Object parameter) {//映射语句MappedStatement mappedStatement = configuration.getMappedStatement(statement);//使用执行器.List<T> list = executor.query(mappedStatement, parameter, Executor.NO_RESULT_HANDLER, mappedStatement.getBoundSql());return list.get(0);}@Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}}

测试

事先准备

创建库表

sql">-- 建表
CREATE TABLE `my_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',`user_id` varchar(9) DEFAULT NULL COMMENT '用户ID',`user_head` varchar(16) DEFAULT NULL COMMENT '用户头像',`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',`user_name` varchar(64) DEFAULT NULL COMMENT '用户名',`user_password` varchar(64) DEFAULT NULL COMMENT '用户密码',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;-- 插入数据
INSERT INTO my_user (user_id, user_head, create_time, update_time, user_name, user_password) VALUES('1', '头像', '2024-12-13 18:00:12', '2024-12-13 18:00:12', '小苏', 's123asd');

定义一个数据库接口 IUserDao

IUserDao

java">public interface IUserDao {String queryUserInfoById(String uid);}

配置数据源

  • 通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
  • 这里DataSource 配置的是 DRUID,因为我们实现的是这个数据源的处理方式。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="UNPOOLED">  #无池化时配置成这个类型值<dataSource type="POOLED">  #池化时配置城这个类型值<property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml"/></mappers>
</configuration>

定义对应的mapper xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.suwg.mybatis.test.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.suwg.mybatis.test.po.User">SELECT id, user_id, user_head, create_timeFROM userwhere id = #{id}</select></mapper>

测试用例

  • 单元测试,传递一个 1L 的 long 类型参数,进行方法的调用处理。通过单元测试验证执行器的处理过程,读者在学习的过程中可以进行断点测试,学习每个过程的处理内容。
  • mybatis-config-datasource.xml 中 dataSource 数据源类型的调整 dataSource type="DRUID/POOLED/UNPOOLED",按需配置验证.
单元测试
java">public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);// 测试SqlSessionFactory@Testpublic void testSqlSessionFactory() throws IOException {// 1.从xml文件读取mybatis配置项, 从SqlSessionFactory获取SqlSession.Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession = sqlSessionFactory.openSession();// 2.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 4.测试验证User user = userDao.queryUserInfoById(1L);logger.info("测试结果:{}", JSON.toJSONString(user));}}

测试结果

image-20241225114018020

image-20241225114138668

  • 从输出的结果来看,我们已经顺利完成,将DefaultSqlSession#selectOne中的调用,替换成了执行器来完成整个过程,通过解耦这部分的逻辑操作,后续我们扩展就更方便了,这块的功能测试通过后,后续就可以聚焦在新的功能扩展点开发了。

总结

  • 整个章节的实现都是在处理解耦这件事情,从DefaultSqlSession#selectOne 对数据源的处理解耦到执行器中进行操作。而执行器中又包括了对 JDBC 处理的拆解,链接、准备语句、封装参数、处理结果,所有的这些过程经过解耦后的类和方法,就都可以在以后的功能迭代中非常方便的完成扩展了。
  • 本章节也为我们后续扩展参数的处理、结果集的封装预留出了扩展点,以及对于不同的语句处理器选择的问题,都需要在后续进行完善和补充。目前我们串联出来的是最核心的骨架结构,随着后续的渐进式开发陆续迭代完善。
  • 对于源码的学习,读者要经历看、写、思考、应用等几个步骤的过程,才能更好的吸收这里面的思想,每一次的阅读源码,都能有新的认知和体会。不要只是Ctrl+C,Ctrl+V一遍就完事了,否则也就失去了跟着学习源码的意义。

参考书籍:《手写Mybatis渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/book-small-mybatis


http://www.ppmy.cn/ops/145520.html

相关文章

冰狐智能辅助使用插件化开发集成三方ocr

插件化开发&#xff08;Plugin Development&#xff09;是一种软件开发模式&#xff0c;它将一个应用程序的功能拆分为模块&#xff0c;并允许在运行时动态加载、卸载和扩展这些模块&#xff0c;以增强应用程序的功能。冰狐支持动态加载和卸载第三方插件模块&#xff0c;开发者…

Unity 战斗系统中角色UI血条设计

1:如何选取技术方案 Unity战斗系统中&#xff0c;每个角色经常会有血条与昵称。如何架构设计才高效&#xff0c;我们列举一些常用的做法: onGUI来做昵称与血条;3D世界中创建一个3D物体来做血条与昵称,然后让血条与昵称对着摄像机;基于UGUI/NGUI单独做血条与昵称的UI节点&#…

Chromium GN 目标指南 - view_example 表单示例 (八)

1. 引言 在前面的文章中&#xff0c;我们学习了如何创建计数器示例&#xff0c;了解了如何使用 Label 和 Button 控件进行交互以及更新 UI 状态。在本篇文章中&#xff0c;我们将创建一个更复杂的示例 —— 表单&#xff0c;以学习如何使用 Textfield、Combobox 和 Checkbox 等…

DocFlow票据AI自动化处理工具,提升企业票据数字化管理效能

随着全球化与信息化进程&#xff0c;企业的文件、信息、数据吞吐量不断增长&#xff0c;2020年以来&#xff0c;业务形势的变革再次加速了企业对先进的文档数字化管理解决方案需求。其中&#xff0c;票据处理始终面临着文件量大耗时、单据高度多样化、“淡旺季”周期波动性强、…

极狐GitLab 17.7正式发布,可从 GitLab 丝滑迁移至极狐GitLab【一】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

面试场景题系列:分布式系统中的唯一ID生成器

1.场景需求需求界定 •ID必须是唯一的。 •ID只包含数字。 •ID长为64位。 •ID按日期排序。 •可以每秒生成超过10&#xff0c;000个唯一ID。 2.高层级的设计 在分布式系统中&#xff0c;有多个方法可以用来生成唯一ID。我们考虑的方法有&#xff1a; •多主复制(Multi…

最新的强大的文生视频模型Pyramid Flow 论文阅读及复现

《PYRAMIDAL FLOW MATCHING FOR EFFICIENT VIDEO GENERATIVE MODELING》 论文地址&#xff1a;2410.05954https://arxiv.org/pdf/2410.05954 项目地址&#xff1a; jy0205/Pyramid-Flow&#xff1a; 用于高效视频生成建模的金字塔流匹配代码https://github.com/jy0205/Pyram…

linux服务器上CentOS的yum和Ubuntu包管理工具apt区别与使用实战

在 CentOS 7 上&#xff0c;系统默认使用 yum 作为包管理工具&#xff0c;而不是 apt。apt 是为 Debian 和 Ubuntu 系统设计的&#xff0c;不能在 CentOS 或其他基于 RHEL 的发行版上直接使用。 如果你希望继续使用 CentOS 7&#xff0c;并管理软件包&#xff0c;你应该使用 y…