通过liquibase实现一个简单的数据库适配器,自动适配60+数据库

news/2025/11/18 21:10:06/文章来源:https://www.cnblogs.com/ludangxin/p/19238946

1. 简介

最近我打算开发一个数据库适配器,核心目的是实现数据库表结构语句的动态执行,比如常见的创建/删除操作——涉及表、视图、索引、序列等各类数据库对象。当然,这个组件的理想状态是能自动适配主流数据库,用户无需纠结不同数据库的语法差异,只用专注于对应操作的数据结构定义就行。

之前写过一篇关于liquibase集成使用的文章,其在脚本支持和主流数据库适配(目前已支持超过60种数据库)方面有着不错的表现并且还能自动比较、追踪数据库的结构变化,用它来做核心组件,无疑能省下大量开发工作量,所以决定采用liquibase作为实现该功能的核心组件。

我们平时就常把liquibase用作项目的ddl版本管理工具,大多配置为项目启动阶段自动执行,把DDL相关信息持久化到数据库中。既然启动时能执行,那项目运行过程中想必也能通过编程方式触发执行——思路明确,话不多说,开整!

2. 环境信息

liquibase: 4.33.0
java: 8

3. Maven

<properties><logback.version>1.3.15</logback.version><lombok.version>1.18.38</lombok.version><junit.version>5.10.0</junit.version>
</properties><dependencies><dependency><groupId>org.liquibase</groupId><artifactId>liquibase-core</artifactId><version>4.33.0</version></dependency><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>3.4.5</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.4.0</version><scope>provided</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.38</version><scope>provided</scope></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version><optional>true</optional></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.9</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>${junit.version}</version><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>${junit.version}</version><scope>test</scope></dependency></dependencies>

4. Quick Start

liquibase数据结构分析

参考一下liquibase xml 描述信息如下

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.6.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd"><changeSet author="ludangxin" id="1662615627445-2"><createTable tableName="role" remarks="角色信息表"><column name="name" remarks="角色名称" type="VARCHAR(255)"/><column name="role_key" remarks="角色key" type="VARCHAR(255)"/></createTable><dropTable tableName="order"/></changeSet><changeSet author="ludangxin" id="1662615627445-3"><createTable tableName="user" remarks="用户信息表"><column name="id" type="INT" remarks="主键"><constraints nullable="false" primaryKey="true"/></column><column name="username" type="VARCHAR(255)" remarks="用户名称"/><column name="password" type="VARCHAR(255)" remarks="密码"/><column name="age" type="INT" remarks="性别"/><column name="sex" type="VARCHAR(255)" remarks="性别"/><column name="role" type="VARCHAR(255)" remarks="角色"/><column name="create_time" type="DATETIME" defaultValueComputed="NOW()" remarks="创建时间"/></createTable></changeSet>
</databaseChangeLog>

可以从上述的xml中看到 最外层到里依次是DatabaseChangeLog → ChangeSet → Change(createTable、dropTable....) , 且都是一对多的关系 基本的结构理清楚之后, 我们使用这些对象实现一个创建表的功能

@Test
public void test() throws SQLException, LiquibaseException {DatabaseChangeLog changeLog = new DatabaseChangeLog();changeLog.setLogicalFilePath("testPath");final CreateTableChange createTableChange = buildCreateTableChange();ChangeSet changeSet = new ChangeSet("123", "sysadmin", false, false, "system", null, null, changeLog);changeSet.addChange(createTableChange);changeLog.addChangeSet(changeSet);final Database database = buildDatabase("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true", "root", "123456");Liquibase liquibase = new Liquibase(changeLog, new ClassLoaderResourceAccessor(), database);liquibase.update(new Contexts(), new LabelExpression());
}public static CreateTableChange buildCreateTableChange() {CreateTableChange createTableChange = new CreateTableChange();createTableChange.setSchemaName("test");createTableChange.setTableName("test_table");createTableChange.setRemarks("测试表");final ColumnConfig columnConfig = new ColumnConfig();columnConfig.setName("name");columnConfig.setType("varchar(255)");columnConfig.setRemarks("姓名");createTableChange.addColumn(columnConfig);return createTableChange;
}public static Database buildDatabase(String url, String username, String password) throws DatabaseException, SQLException {Connection connection = DriverManager.getConnection(url, username, password);return DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
}

测试执行结果如下

不仅创建了目标表, 还生成了liquibase元信息存储和悲观锁表(防止并发执行导致表锁)

image-20251118101223546

刚刚尝试创建了一张新表, 那除了创建表, 它还能干嘛呢? 看一下liquibase源码

image-20251118101947363

可以看到, 对库/表的操作基本上都覆盖了, 那其实就好办了 对这些change对象做简单的封装, 方便客户端调用即可

5. 封装工具类

封装思路, 将这些change对应的功能封装一个builder类 用于方便build, 比如

这里只展示核心类, 其余代码请参考最后的源码

5.1 CreateTableChangeBuilder

创建表change builder

import com.ldx.meta.db.engine.entity.Column;
import com.ldx.meta.db.engine.entity.Table;
import liquibase.change.ColumnConfig;
import liquibase.change.ConstraintsConfig;
import liquibase.change.core.CreateTableChange;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.apache.commons.collections4.CollectionUtils;import java.util.List;/*** 建表 change builder** @author ludangxin* @since  2025/11/6*/
@Data
@Accessors(fluent = true)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CreateTableChangeBuilder {private String schemaName;private Table table;private boolean ifNotExists = true;public static CreateTableChangeBuilder getInstance() {return new CreateTableChangeBuilder();}public CreateTableChange build() {final String tableName = table.getName();final String tableComment = table.getComment();CreateTableChange createTableChange = new CreateTableChange();createTableChange.setSchemaName(schemaName);createTableChange.setTableName(tableName);createTableChange.setIfNotExists(ifNotExists);createTableChange.setRemarks(tableComment);final List<Column> columns = table.getColumns();if (CollectionUtils.isNotEmpty(columns)) {for (Column column : columns) {final ColumnConfig columnConfig = buildColumnConfig(column);createTableChange.addColumn(columnConfig);}}return createTableChange;}private static ColumnConfig buildColumnConfig(Column column) {ColumnConfig columnConfig = new ColumnConfig();columnConfig.setName(column.getName());columnConfig.setType(column.getType());columnConfig.setRemarks(column.getComment());columnConfig.setDefaultValue(column.getDefaultValue());final ConstraintsConfig constraintsConfig = new ConstraintsConfig();constraintsConfig.setNullable((!column.isNotNull()));constraintsConfig.setPrimaryKey(column.isPrimaryKey());columnConfig.setConstraints(constraintsConfig);return columnConfig;}
}

5.2 ChangeSetBuilder

change 集合 builder: 用于快速收集change

import com.ldx.meta.db.engine.configuration.MetaDbProperties;
import liquibase.change.Change;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** change set** @author ludangxin* @since 2025/11/6*/
@Setter
@Accessors(fluent = true)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ChangeSetBuilder {private String id;private String comments;private String author;private boolean alwaysRun = false;private boolean runOnChange = false;private String filePath = "none";private String contextFilter;private String dbmsList;private DatabaseChangeLog changeLog;private final List<Change> changes = new ArrayList<>();private MetaDbProperties properties = MetaDbProperties.DEFAULT();public static ChangeSetBuilder getInstance() {return new ChangeSetBuilder();}public ChangeSetBuilder change(Change... change) {changes.addAll(new ArrayList<>(Arrays.asList(change)));return this;}public ChangeSet build() {if (changeLog == null) {changeLog = ChangeLogBuilder.getInstance().build();}if (properties != null) {if (id == null || id.isEmpty()) {id = properties.getIdProvider().getNextId();}if (author == null || author.isEmpty()) {author = properties.getAuthor();}}changeLog.setLogicalFilePath(filePath);ChangeSet changeSet = new ChangeSet(id, author, alwaysRun, runOnChange, filePath, contextFilter, dbmsList, changeLog);changeSet.setComments(comments);changeSet.setLogicalFilePath(filePath);for (Change change : changes) {changeSet.addChange(change);}return changeSet;}
}

5.3 ChangeLogBuilder

changeLog: 用于快速收集changeSet

import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** change log** @author ludangxin* @since  2025/11/6*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ChangeLogBuilder {public static ChangeLogBuilder getInstance() {return new ChangeLogBuilder();}private final List<ChangeSet> changeSets = new ArrayList<>();public ChangeLogBuilder changeSet(ChangeSet... changeSet) {changeSets.addAll(new ArrayList<>(Arrays.asList(changeSet)));return this;}public DatabaseChangeLog build() {DatabaseChangeLog changeLog = new DatabaseChangeLog();if (!changeSets.isEmpty()) {final ChangeSet anyChangeSet = changeSets.get(0);final String logicalFilePath = anyChangeSet.getLogicalFilePath();changeLog.setLogicalFilePath(logicalFilePath);}else {changeLog.setLogicalFilePath("default");}for (ChangeSet changeSet : changeSets) {changeLog.addChangeSet(changeSet);}return changeLog;}
}

5.4 MetaDbEngineService

适配器执行入口 支持快速execute & lambda调用

import com.ldx.meta.db.engine.configuration.MetaDbDataSource;
import com.ldx.meta.db.engine.configuration.MetaDbProperties;
import com.ldx.meta.db.engine.exception.MetaDbDataSourceException;
import com.ldx.meta.db.engine.exception.MetaDbEngineException;
import com.ldx.meta.db.engine.builder.ChangeLogBuilder;
import com.ldx.meta.db.engine.builder.ChangeSetBuilder;
import com.ldx.meta.db.engine.builder.SqlBuilder;
import liquibase.Contexts;
import liquibase.LabelExpression;
import liquibase.Liquibase;
import liquibase.change.Change;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;/*** 适配器入口** @author ludangxin* @since 2025/11/6*/
@Slf4j
public class MetaDbEngineService {private final MetaDbDataSource dataSource;private final MetaDbProperties metaDbProperties;public MetaDbEngineService(MetaDbDataSource dataSource) {this.dataSource = Objects.requireNonNull(dataSource, "dataSource can not be null");this.metaDbProperties = MetaDbProperties.DEFAULT();}public MetaDbEngineService(MetaDbDataSource dataSource, MetaDbProperties metaDbProperties) {this.dataSource = Objects.requireNonNull(dataSource, "dataSource can not be null");this.metaDbProperties = Objects.requireNonNull(metaDbProperties, "metaDbProperties can not be null");}public void execute(Change... changes) {this.execute(false, changes);}public void execute(boolean printSql, Change... changes) {if (changes == null || changes.length == 0) {log.warn("execute called with no changes -> no-op");return;}final ChangeSet changeSet = ChangeSetBuilder.getInstance().properties(metaDbProperties).change(changes).build();this.execute(printSql, changeSet);}public void execute(ChangeSet... changeSets) {this.execute(false, changeSets);}public void execute(boolean printSql, ChangeSet... changeSets) {if (changeSets == null || changeSets.length == 0) {log.warn("execute called with no changeSets -> no-op");return;}final DatabaseChangeLog changeLog = ChangeLogBuilder.getInstance().changeSet(changeSets).build();this.execute(printSql, changeLog);}public void execute(DatabaseChangeLog changeLog) {this.execute(false, changeLog);}public void execute(boolean printSql, DatabaseChangeLog changeLog) {if (changeLog == null || CollectionUtils.isEmpty(changeLog.getChangeSets())) {log.warn("execute called with empty changeLog -> no-op");return;}this.executeChangeLog(changeLog, printSql);}public List<String> getSqlList(Change... changes) {if (changes == null || changes.length == 0) {return Collections.emptyList();}final ChangeSet changeSet = ChangeSetBuilder.getInstance().properties(metaDbProperties).change(changes).build();return this.getSqlList(changeSet);}public List<String> getSqlList(ChangeSet... changeSets) {if (changeSets == null || changeSets.length == 0) {return Collections.emptyList();}final DatabaseChangeLog changeLog = ChangeLogBuilder.getInstance().changeSet(changeSets).build();return this.getSqlList(changeLog);}public List<String> getSqlList(DatabaseChangeLog changeLog) {if (changeLog == null) {return Collections.emptyList();}return this.generateSqlList(changeLog);}public String getSql(Change... changes) {List<String> list = getSqlList(changes);return list.isEmpty() ? "" : String.join(";\n", list) + ";";}public String getSql(ChangeSet... changeSets) {List<String> list = getSqlList(changeSets);return list.isEmpty() ? "" : String.join(";\n", list) + ";";}public String getSql(DatabaseChangeLog changeLog) {List<String> list = getSqlList(changeLog);return list.isEmpty() ? "" : String.join(";\n", list) + ";";}private void executeChangeLog(DatabaseChangeLog changeLog, boolean printSql) {try (final Database database = dataSource.openDatabase()) {if (printSql) {this.printSql(database, changeLog);}String url = safeGetDbUrl(database);Liquibase liquibase = new Liquibase(changeLog, new ClassLoaderResourceAccessor(), database);liquibase.update(new Contexts(), new LabelExpression());int executed = changeLog.getChangeSets() == null ? 0 : changeLog.getChangeSets().size();log.info("liquibase execute success, URL: {}, changeSets: {}", url, executed);}catch (LiquibaseException e) {throw new MetaDbEngineException("liquibase execute failed: " + e.getMessage(), e, safeGenerateAllSql(changeLog));}catch (RuntimeException e) {throw new MetaDbEngineException("meta-db-engine execute changelog failed: " + e.getMessage(), e, safeGenerateAllSql(changeLog));}}private void printSql(DatabaseChangeLog changeLog) {try (final Database database = dataSource.openDatabase()) {printSql(database, changeLog);}catch (DatabaseException e) {throw new MetaDbDataSourceException("print sql failed", e);}}public void printSql(Database database, DatabaseChangeLog changeLog) {final String dbUrl = safeGetDbUrl(database);generateSqlList(changeLog).forEach(sql -> log.info("execute sql url:{} statement: {}", dbUrl, sql));}private List<String> generateSqlList(DatabaseChangeLog changeLog) {try (final Database database = dataSource.openDatabase()) {if (changeLog == null || CollectionUtils.isEmpty(changeLog.getChangeSets())) {return Collections.emptyList();}return changeLog.getChangeSets().stream().filter(Objects::nonNull).map(ChangeSet::getChanges).filter(CollectionUtils::isNotEmpty).flatMap(Collection::stream).map(change -> SqlBuilder.getInstance().database(database).change(change).build()).collect(Collectors.toList());}catch (Exception e) {throw new MetaDbEngineException("generate sql failed", e, null);}}private List<String> safeGenerateAllSql(DatabaseChangeLog changeLog) {try {return this.generateSqlList(changeLog);}catch (Exception e) {return Collections.emptyList();}}private String safeGetDbUrl(Database db) {try {return db != null && db.getConnection() != null ? db.getConnection().getURL() : "unknown";}catch (Exception e) {return "unknown";}}public TransientEngine lambda() {return new TransientEngine(this);}@Setter@Accessors(fluent = true)public static final class TransientEngine {private final MetaDbEngineService engine;private boolean printSql = false;private DatabaseChangeLog changeLog = ChangeLogBuilder.getInstance().build();private TransientEngine(MetaDbEngineService service) {this.engine = service;}public TransientEngine change(Change... changes) {final ChangeSet changeSet = ChangeSetBuilder.getInstance().properties(engine.metaDbProperties).change(changes).build();return this.changeSet(changeSet);}public TransientEngine changeSet(ChangeSet... changeSets) {for (ChangeSet changeSet : changeSets) {changeLog.addChangeSet(changeSet);}return this;}public TransientEngine changeLog(DatabaseChangeLog changeLog) {Objects.requireNonNull(changeLog, "changeLog can not be null");this.changeLog = changeLog;return this;}public void execute() {engine.execute(printSql, changeLog);}public void printSql() {engine.printSql(changeLog);}public List<String> getSqlList() {return engine.getSqlList(changeLog);}}
}

6. 测试工具类

import com.ldx.meta.db.engine.builder.ChangeSetBuilder;
import com.ldx.meta.db.engine.builder.CreateTableIndexChangeBuilder;
import com.ldx.meta.db.engine.builder.DropTableIndexChangeBuilder;
import com.ldx.meta.db.engine.builder.DropViewChangeBuilder;
import com.ldx.meta.db.engine.configuration.MetaDbDataSource;
import com.ldx.meta.db.engine.configuration.MetaDbProperties;
import com.ldx.meta.db.engine.entity.Column;
import com.ldx.meta.db.engine.entity.Table;
import com.ldx.meta.db.engine.builder.CreateTableChangeBuilder;
import com.ldx.meta.db.engine.builder.CreateViewChangeBuilder;
import com.ldx.meta.db.engine.builder.DropTableChangeBuilder;
import com.ldx.meta.db.engine.builder.SqlBuilder;
import com.ldx.meta.db.engine.entity.TableIndex;
import com.ldx.meta.db.engine.enums.IdProviderType;
import liquibase.change.core.CreateIndexChange;
import liquibase.change.core.CreateTableChange;
import liquibase.change.core.CreateViewChange;
import liquibase.change.core.DropTableChange;
import liquibase.change.core.DropViewChange;
import liquibase.change.core.RawSQLChange;
import liquibase.changelog.ChangeSet;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;import java.util.Arrays;
import java.util.Collections;/*** test** @author ludangxin* @since 2025/11/6*/
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class MetaDbEnginesTest {private static MetaDbEngineService META_DB_ENGINE_SERVICE;@BeforeAllpublic static void init() {final MetaDbDataSource metaDbDataSource = MetaDbDataSource.fromJdbc("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true", "root", "123456");final MetaDbProperties metaDbProperties = new MetaDbProperties();metaDbProperties.setIdProvider(IdProviderType.TIMESTAMP);metaDbProperties.setAuthor("zhang-tie-niu");META_DB_ENGINE_SERVICE = new MetaDbEngineService(metaDbDataSource, metaDbProperties);}@Test@Order(1)public void given_table_info_when_create_table_then_complete() {final Column idColumn = Column.builder().name("id").type("varchar(32)").comment("主键").build();final Table table = Table.builder().name("test123").columns(Collections.singletonList(idColumn)).build();final CreateTableChange createTableChange = CreateTableChangeBuilder.getInstance().table(table).build();META_DB_ENGINE_SERVICE.execute(createTableChange);}@Test@Order(2)public void given_view_info_when_create_view_then_complete() {final CreateViewChange createView = CreateViewChangeBuilder.getInstance().viewName("v_test123").sql("select * from test123").replaceIfExists(true).build();META_DB_ENGINE_SERVICE.lambda().printSql(true).change(createView).execute();}@Test@Order(3)public void given_sql_info_when_update_table_then_complete() {final RawSQLChange build = SqlBuilder.build("insert into test123 (id) values (1)");META_DB_ENGINE_SERVICE.lambda().printSql(true).change(build).execute();}@Test@Order(4)public void given_view_name_when_drop_view_then_complete() {final DropViewChange dropView = DropViewChangeBuilder.getInstance().viewName("v_test123").build();META_DB_ENGINE_SERVICE.lambda().printSql(true).change(dropView).execute();}@Test@Order(5)public void given_table_name_when_drop_table_then_complete() {final DropTableChange dropTable = DropTableChangeBuilder.getInstance().tableName("test123").build();META_DB_ENGINE_SERVICE.lambda().printSql(true).change(dropTable).execute();}@Testpublic void given_ddl_info_when_execute_then_complete() {final Column idColumn = Column.builder().name("id").type("varchar(32)").primaryKey(true).comment("主键").build();final Column nameColumn = Column.builder().name("name").type("varchar(32)").comment("姓名").build();final Column idCardColumn = Column.builder().name("id_card").type("varchar(32)").comment("身份证").build();final Table table = Table.builder().name("test666").columns(Arrays.asList(idColumn, nameColumn, idCardColumn)).build();final CreateTableChange createTableChange = CreateTableChangeBuilder.getInstance().table(table).build();final TableIndex tableIdCardIndex = TableIndex.builder().indexName("id_card_unique_index").tableName("test666").columnNames(Collections.singletonList("id_card")).unique(true).build();final CreateIndexChange createIndexChange = CreateTableIndexChangeBuilder.getInstance().tableIndex(tableIdCardIndex).build();final ChangeSet changeSet = ChangeSetBuilder.getInstance().change(createTableChange).change(createIndexChange).build();META_DB_ENGINE_SERVICE.execute(changeSet);}}

测试执行结果如下

image-20251118181638811

7. 小结

本章节尝试使用liquibase作为数据库适配器底座, 通过简单封装其内置的DatabaseChangeLog → ChangeSet → Change(createTable、dropTable....)等对象, 实现方便快速的执行ddl操作, 最主要的是其默认就支持多达60+数据库的自动适配, 可以轻松的实现数据库兼容.

8. 源码

测试过程中的代码已全部上传至github, 欢迎点赞收藏 仓库地址: https://github.com/ludangxin/meta-db-engine

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

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

相关文章

题解:AT_abc428_g [ABC428G] Necklace

补一下。分享一种比较暴力的做法。 首先肯定是使用 Burnside 引理求解,不过题目并没有给定环的大小,但是由于大小为 \(n\) 的环至少要有 \(2^n\) 的美丽值,所以这个 \(n\) 只有 \(\log m\) 个。 所以可以快乐的枚举…

第十四天 mysql单表练习

一、mysql的安装 yum install myaql=安装客户端 yum install mysql-sever=安装客户端 service mysql status=查看状态 service mysql start=开启数据库 service mysql restart=重启数据库 service mysql stop=关闭数据…

人工智能之编程进阶 Python高级:第四章 数学类模块

人工智能之编程进阶 Python高级:第四章 数学类模块人工智能之编程进阶 Python高级 第四章 数学类模块@目录人工智能之编程进阶 Python高级前言一、re 模块:正则表达式(Regular Expressions)常用函数常用元字符二、…

Pandas GroupBy 的 10 个实用技巧

很多人把 groupby 理解成单纯的求和、计数这类操作,比如说算算总收入、数数用户量,然后就没了。实际上它的应用场景要广得多:计算组内特征、数据标准化、构造滚动指标、合并不同维度的统计结果,甚至处理一些复杂的…

lvs详细配置

lvs(linux virtual server) 1、概述 1、lvs简介 lvs 是一种基于linux操作系统的负载均衡解决方案,就是将请求分发到多个后端服务器上面,lvs常用于网站,数据库等需要高并发访问的场景 2、lvs架构ipvs :lvs的核心组件…

Lazarus使用cef打开文件和下载设置

Lazarus使用cef打开文件和下载设置//下载procedure TForm1.Chromium1BeforeDownload(Sender: TObject; const browser: ICefBrowser; const downloadItem: ICefDownloadItem; const suggestedName: ustring; const c…

题解:P14435 [JOISC 2013] 收拾吉祥物 / Mascots

\(\displaystyle{\large\textcolor{#00A0DE}{蓝是水的颜色}}\),所以我们来水一下这道蓝题的题解。 题目传送门 题意理解 我们首先给出一个 \(R\times C\) 的方格图,在其中任意 \(n\) 坐标中初始放着物品,我们依次选…

Solon AI 开发学习 - 1导引

Solon AI & MCP 是 Java 智能体开发框架,支持 Java 8-25,简化了与主流 AI 模型(如 OpenAI、DeepSeek)的集成。它提供统一接口抽象,支持多种 AI 模型(ChatModel、GenerateModel等)协作,并兼容 MCP 协议实现…

linux c 线程池

在 Linux 系统中,线程池(Thread Pool)通常用于管理并发执行的任务,以提高系统性能和资源利用率。Linux 系统本身没有内置的线程池机制,但可以通过使用系统工具、库或自定义脚本实现类似的功能。 下面我将分几个方…

linux c 文件是否存在

在 Linux 中,判断一个文件是否存在,可以使用多种命令,以下是几种常用的方法:方法 1:使用 test 或 [[ ]] 检查文件是否存在 if [ -f /path/to/file ]; thenecho + #引号 + 文件存在 + #引号 + elseecho + …

linux c 文件是否存在

在 Linux 中,判断一个文件是否存在,可以使用多种命令,以下是几种常用的方法:方法 1:使用 test 或 [[ ]] 检查文件是否存在 if [ -f /path/to/file ]; thenecho + #引号 + 文件存在 + #引号 + elseecho + …

2025 年 11 月滚珠丝杆厂家推荐排行榜,高负载滚珠丝杆,耐磨滚珠丝杆,检测仪器高速滚珠丝杆,螺母滚珠丝杆,医用自动化滚珠丝杆公司推荐

2025年11月滚珠丝杆厂家推荐排行榜:高负载与医用自动化应用优选指南 行业背景与发展趋势 滚珠丝杆作为精密传动领域的核心部件,其技术发展与制造业升级息息相关。随着工业4.0和智能制造的深入推进,高负载滚珠丝杆、…

Pjudge #21741. 【NOIP Round #5】青鱼和区间 题解

Description 鱼王青鱼的 AI 不愿意进行 996 的工作,于是要求青鱼先和它玩一个游戏。 AI 生成了 \(n\) 道题,编号为 \(1, 2, \dots, n\)。现在 AI 心里选择了其中一道题,但青鱼不知道这是哪一题。 青鱼可以向 AI 询问…

11月18日

今日进度 (1)Commit 记录 •陈鉴祥:完成 agent-svc 忙闲状态查询,全量测试通过 •何绍斌:优化支付表索引,完成数据一致性测试 •张廷智:编写数据格式化工具,完成报表页联调 •郑权:收集测试用例,组织团队交叉…

UE4/UE5反射系统动态注册机制解析 - 实践

UE4/UE5反射系统动态注册机制解析 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "M…

完全平方和的推广

平方和公式 \[(\sum_{i=1}^n A_i)^2 = \sum_{i=1}^n A_i^2 + 2\sum_{1 \leq i < j \leq n} A_i A_j \]核心是完全平方公式的推广,用“多项式展开逻辑”即可理解。 1. 从简单到复杂推导 (1)2个数的情况(完全平方…

三维偏序整体二分?

基于整体二分的CDQ分治实现? 众所周知的是三维偏序可以使用整体二分解决,但是今天我研究了一下,发现这个整体二分并不是一般意义下的整体二分,而是CDQ分治的一种类似整体二分的实现。 一般而言,整体二分指的是平行…

做题随笔:P3403

Solution 这里给出了一种不使用最短路的优化算法,可以优化到 \(O(x)\),而且出奇的直观、好写。同见于 oi-wiki。(其实相关部分是我写的) 题意 原题链接 给定 \(x,y,z,h\),求 \(ax+by+cz=h\) 的非负整数解 \((a,b,…

2025.11.18

今天上课,拿快递,吃饭,睡觉,买水果,回宿舍。

《从纪律委员到AI元人文开放者》

《从纪律委员到AI元人文开放者》 作者:岐金兰 日期:2025年11月18日 三十年前,我站在教室的讲台上,手里拿着纪律记录本。那时的我,是班级秩序的守护者,用稚嫩的肩膀承担着维持纪律的责任。谁能想到,三十年后,我…