salaun/laravel-complex-upsert

一个无需依赖主键即可高效更新数据库的工具。

5.1.2 2024-10-02 13:56 UTC

README

典型用例是更新数据库,使用基于文本的文件数据树(json 或 XML)。
在这种情况下,对象可能没有主键,因此我们通过属性组合匹配数据以唯一定位它们。

主要原则是

  • 在造成永久性损害之前失败。如果可能,删除损坏部分并更新其余部分。由 Laravel 验证辅助程序和引用集合提供支持。
  • 多线程得益于 Laravel 队列系统。
  • 允许与数据块进行部分集成 哈希
  • 保留相同的记录及其 ID。不删除并从头开始创建,归功于 SQL upsert 查询
  • 重用 模型关系网络 以确保项目一致性和维护,避免代码重复。
  • 通过分组引用获取(引用集合)和按模型类型(upsert 服务)进行 upsert 以尽可能减少数据库查询。
  • 可定制、可读性强且易于维护。目的是引导您进行结构化和组织化的数据库更新过程,同时使设置此包实现的工作状态变得简单。

安装

您可以通过 composer 安装此包

composer require salaun/laravel-complex-upsert

您可以使用以下命令发布配置文件

php artisan vendor:publish --provider="Salaun\ComplexUpsert\ComplexUpsertServiceProvider" --tag="laravel-complex-upsert-config"

这是已发布配置文件的内容

return [
	'chunk_size' =>  env('COMPLEX_UPSERT_CHUNK_SIZE', 300),
];

Lexic

  • 关系:子节点对父节点的关系。
  • 引用:数据库中已存在的记录。数据树的最后节点。
  • Upsert(up[date or in]sert):在 SQL 上下文中表示插入记录的操作,如果它已存在,则更新它。在此包中是集成和更新的同义词。
  • 反向关系:为了处理递归 upsert,父节点需要先 upsert 以获取其 ID 并将其设置为子节点的外键。这意味着我们需要在设置外键时知道父节点与其子节点之间的关系。因此,我们设置关系名称 parent_relation::child_relation,以避免与父节点本地的 Laravel 关系混淆。

要求

数据库

此包旨在与 PostgreSQL 数据库一起使用(感谢 Thomas Pettry 包),但也应与 MySQL 数据库一起工作。

关系

在处理过程中使用的关系应从两侧定义,并且模型必须 使用 相关特质。

外键关系

嵌套模型必须定义所需的 DELETE ON CASCADE

使用方法

建议您遵循此包提供的流程,也可以不使用作业“框架”调用服务方法。

此包中包含的测试也是实现示例。

警告

根据 Laravel 文档 Laravel documentation,您应该注意您的迁移

All databases systems except SQL Server require the columns in the second argument provided to the upsert method to have a "primary" or "unique" index.

因此,您应该参考创建索引部分

$table->unique(['account_id', 'created_at'], 'upsert_key');

复合唯一键注意事项

当复合唯一键包含可空列且项目值为NULL时,upsert操作将导致重复记录。这是由于SQL标准不识别此复合键为唯一键。

UNIQUE索引允许包含NULL的列有多个NULL值。(MySQL文档

在PostgreSQL中,可以使用UNIQUE INDEX WITH NULL DISTINCT来避免此问题。

只有引用才能有从左到右的关系

考虑以下情况products <= product_label => labels => entities。目前不支持此情况。标签必须设置为引用,但实体无法自动更新。

软件包技术细节

辅助工具

此软件包包含名为服务的辅助工具,它们负责处理

  • upsert服务:负责将数据(如果需要,以递归方式)插入。
  • upsert引用服务:负责从数据库检索已存在的数据,以便完成要插入的模型。这些数据可用于将传入数据与要upsert的记录匹配。
  • WhereInMulti()查询构建器宏。基于多列/值执行whereIn查询。
    SELECT * FROM `users` WHERE (`firstname`, `lastname`) IN (('micheal', 'bay'), ('steven', 'spielberg'));
    

模型配置

要upsert的模型需要配置,以便服务可以使用它们。这是通过使用特性完成的。一些配置是静态参数,需要设置在要upsert的模型上

  • canFail (bool):定义此记录是否可以安全地从数据集中删除。
  • upsertId (array):用于数据库匹配的哪些列。
  • upsertColumns (array):在基表中已有数据记录的情况下,应更新哪些列。3个选项:"[]" 什么都不更新,"null" 更新所有内容,"['firstname', 'lastname']" 更新firstname和lastname列。
  • rules (array):用于创建Laravel模型的Laravel验证辅助工具时使用的数组。

引用集合

引用集合受laravel-media-library的启发,它根据声明性函数定义引用行为。如果upsert模型有引用,则必须声明集合才能检索和解析它。此类集合包含

  • relation (string):用于在upsert模型上设置外键的关系。
  • referenceId (array):用于获取(和更新)引用的哪些列。
  • query (Query Builder):访问数据库内容的查询。
  • syncColumns (array):如果需要,在获取其ID之前更新哪些引用值。

工作流程

除了使upsert更容易之外,还有一个关于处理应如何进行的实现。基于Laravel队列和批处理系统,我们引入了一系列步骤,以确保将正确数据放在正确位置,同时不风险破坏数据库内容的一致性。

分发器

分发器的角色是将upsert的片段分发给处理器。可以是下载并提取id块的ZIP文件,以便处理器作业的大小大致相同。对于给定的父项,它将用要异步执行的处理器作业填充其批处理。还有一个选项,在所有批处理作业执行完毕后执行操作(例如,删除新数据中不存在的记录)。

处理器

处理器将积极执行upsert。通过一系列步骤进行,它会过滤掉不可用或不相关的数据,并将其余部分格式化以完成upsert状态。当关系被静默遗忘时,会触发日志,而统计允许我们跟踪失败记录的位置和数量。致命失败由队列系统处理,并根据配置进行记录和重试。

存在系统步骤

  • beforeUpsert:在执行 confirmedUpsert() 步骤之前准备数据。
  • afterUpsert:一旦我们确认模型已成功插入,即保存数据供 confirmedUpsert() 步骤使用。

用户步骤

  • getData:从您选择的源获取原始数据。无论它是API、XML或JSON文件还是数据库导出。
  • instanciateData:创建与原始数据结构匹配的基本PHP对象。仅执行数据类型检查。
  • confirmedUpsert:在此处决定数据是否值得更新,或者是否可以忽略。
  • transformData:允许我们将 instanciateData 的基本PHP对象转换为laravel Model。可以进行文本格式化、结构更改等操作。还可以利用 Laravel 验证助手 makeValid() 来确保数据可以无问题地存入数据库。
  • resolveReferences(使用 UpsertReferenceService):如果数据库中缺少某些引用,则提供更新数据库中已有引用的选项,获取所需内容并对外键模型应用所需更改。
  • upsertModels(使用 UpsertService):如果有缺失(甚至在新数据中缺少某些关系并且您希望匹配时删除),则更新或插入,以更新数据库。如果设置了“反向关系”,则将以递归方式执行upsert。

可以添加任意数量的自定义步骤。任何未通过步骤的元素必须设置为null并从数组中过滤。可以将关系模型的模型类型设置为"canFail()",这将忽略它而不会取消其父元素的upsert。

优化

  • 明智地排序唯一索引中的列 来源

测试

composer p

变更日志

请参阅 变更日志 了解最近更改的详细信息。

致谢

许可证

MIT许可证(MIT)。请参阅 许可证文件 了解更多信息。