分享到

简介

使用数据库管理应用程序数据是数据持久化最常见的选择之一。数据库允许快速存储和检索信息,提供数据完整性保证,并提供超越单个应用程序实例生命周期的持久性。有无数种数据库类型可用,以满足您的项目需求和偏好。

然而,直接从应用程序操作数据库并非总是易事。数据结构表示方式的差异常常带来挑战。表达不同实体之间关系的微妙之处也很困难。为了解决这个问题,已经创建了许多不同的工具来充当核心应用程序和数据层之间的接口。

在本指南中,我们将探讨三种常见方法之间的一些差异:原生 SQL、查询构建器和 ORM(对象关系映射器)。我们将比较每种方法的优缺点,最后提供一个常用术语词汇表,帮助您熟悉一些关键概念。

作为简化总结,以下是每种方法的优缺点概述

方法数据库/编程侧重手动管理抽象级别复杂程度
原生 SQL面向数据库
查询构建器混合
ORM面向编程

使用原生 SQL 或其他数据库原生查询语言管理数据

有些应用程序通过使用数据库引擎支持的原生语言编写和执行查询来直接与数据库交互。通常,只需一个数据库驱动程序即可连接、认证和与数据库实例通信。

开发人员可以通过连接发送用数据库原生语言编写的查询。作为回报,数据库也将以其原生格式提供查询结果。对于许多关系型数据库来说,首选的查询语言是 SQL。

大多数关系型数据库以及一些非关系型数据库都支持结构化查询语言,也称为 SQL,用于构建和执行强大的查询。SQL 自 20 世纪 70 年代以来一直用于数据管理,因此它得到了很好的支持并在一定程度上实现了标准化。

原生查询的优势

使用 SQL 或其他数据库原生语言有一些明显的优势。

一个优点是开发人员显式地编写和管理数据库查询并处理结果。虽然这可能需要大量额外的工作,但它意味着在数据库存储内容、如何表示数据以及以后如何提供数据方面几乎没有意外。缺乏抽象意味着更少的“活动部件”可以导致不确定性。

一个例子是性能。虽然复杂的抽象层通过翻译编程语句生成 SQL 查询,但生成的 SQL 可能效率很低。不必要的子句、过于宽泛的查询以及其他失误可能导致数据库操作缓慢,并且可能脆弱且难以调试。通过原生编写 SQL,您可以运用所有领域知识和常识来避免许多类查询问题。

使用数据库原生查询的另一个原因是灵活性。没有哪种抽象可能像原生数据库查询语言那样灵活。更高级别的抽象试图弥合两种不同范式之间的鸿沟,这可能会限制它们可以表达的操作类型。然而,在编写原生 SQL 时,您可以利用数据库引擎的所有功能并表达更复杂的查询。

原生查询的缺点

虽然原生查询有一些明确的优点,但它并非没有问题。

当使用纯 SQL 从应用程序与数据库交互时,您必须了解底层数据结构才能编写有效的查询。您完全负责在应用程序使用的数据类型和结构与数据库系统中可用的构造之间进行转换。

使用原生 SQL 时要记住的另一件事是,输入安全性完全由您自己管理。如果您存储外部用户提供的数据,这一点尤其重要,因为特殊构造的输入可能会导致数据库暴露您不想允许的信息。

这种类型的攻击被称为SQL 注入,只要用户输入可能影响数据库状态,它就是一个潜在问题。更高级别的抽象工具通常会自动清理用户输入,帮助您避免此类问题。

使用原生查询语言几乎总是意味着使用常规字符串来组合查询。在您必须转义输入并将字符串连接在一起才能创建有效查询的情况下,这可能是一个痛苦的过程。您的数据库操作可能会被包装在许多层字符串操作中,这很可能意外地损坏数据。

原生查询总结

虽然我们在本节主要讨论了 SQL,但这里的大多数信息同样适用于任何原生数据库查询语言。总而言之,原生 SQL 或直接使用任何等效的查询语言使您最接近数据库用于存储和管理数据的抽象,但迫使您手动完成所有繁重的数据管理工作。

使用查询构建器管理数据

与使用 SQL 等数据库原生查询语言的替代方法是使用称为查询构建器的工具或库来与数据库通信。

什么是 SQL 查询构建器?

SQL 查询构建器在原生数据库查询语言之上添加了一层抽象。它们通过规范化查询模式并提供方法或函数来实现这一点,这些方法或函数添加输入净化并自动转义项目,以便更容易地集成到应用程序中。

使用 SQL 查询构建器时,数据库层支持的结构和操作仍然相当容易识别。这允许您以编程方式处理数据,同时仍保持与数据相对接近。

通常,查询构建器提供一个使用方法或函数向查询添加条件的接口。通过将方法链接在一起,开发人员可以从这些单独的“子句”中组合完整的数据库查询。

SQL 查询构建器的优点

因为查询构建器使用与应用程序其余部分相同的结构(方法或函数),所以开发人员通常发现它们比以字符串形式编写的原生数据库查询更容易长期管理。区分操作符和数据很简单,并且很容易将查询分解为处理查询特定部分的逻辑块。

对于一些开发人员来说,使用 SQL 查询构建器的另一个优点是它并不总是隐藏底层的查询语言。尽管操作可能使用方法而不是字符串,但它可能相当透明,这使得熟悉数据库的人更容易理解操作将做什么。这在使用更高级别的抽象时并非总是如此。

SQL 查询构建器通常还支持多个数据后端,例如,抽象了各种关系型数据库中的一些细微差异。这使您可以使用相同的工具来处理使用不同数据库的项目。它甚至可能使迁移到新数据库变得稍微容易。

SQL 查询构建器的缺点

SQL 查询构建器也存在与原生查询语言相同的缺点。

一个常见的批评是,SQL 查询构建器仍然要求您理解并考虑数据库的结构和功能。对于某些开发人员来说,这不是一个足够有用的抽象。这意味着除了查询构建器本身的特定语法和功能之外,您还必须对 SQL 有相当好的掌握。

此外,SQL 查询构建器仍然要求您定义检索到的数据如何与应用程序数据相关联。您的内存对象和数据库中的对象之间没有自动同步。

虽然查询构建器通常模拟它们旨在使用的查询语言,但额外的抽象层可能意味着有时某些操作无法通过提供的方法实现。通常,存在一种“原生”模式,可以直接向后端发送查询,绕过查询构建器的典型接口,但这只是规避了问题,而不是解决了问题。

SQL 查询构建器总结

总的来说,SQL 查询构建器提供了一层薄薄的抽象,专门解决了直接使用数据库原生语言的一些主要痛点。SQL 查询构建器几乎可以作为查询的模板系统,允许开发人员在直接使用数据库和添加额外抽象层之间取得平衡。

使用 ORM 管理数据

在抽象层级上更进一步的是 ORM。ORM 通常旨在实现更完整的抽象,希望能更流畅地与应用程序数据集成。

什么是 ORM?

对象关系映射器,或 ORM,是专门用于在关系型数据库中的数据表示与面向对象编程 (OOP) 中使用的内存表示之间进行转换的软件。ORM 为数据库中的数据提供面向对象的接口,试图使用熟悉的编程概念并减少必要的样板代码以加快开发速度。

一般来说,ORM 充当抽象层,旨在帮助开发人员在不大幅改变面向对象范式的情况下处理数据库。通过减少适应数据库存储格式细节的心理负担,这可能会有所帮助。

特别是,面向对象编程中的对象倾向于在其中编码大量状态,并且可以通过继承和其他 OOP 概念与其他对象建立复杂的关系。将此信息可靠地映射到面向表的关系范式通常并非易事,并且可能需要对两个系统都有很好的理解。ORM 试图通过自动化其中一些映射并提供数据的表达性接口来减轻这一负担。

ORM 的挑战是否特定于面向对象编程和关系型数据库?

根据定义,ORM 专门设计用于在面向对象的应用程序语言和关系型数据库之间提供接口。然而,尝试在编程语言中使用的数据结构抽象和数据库存储使用的数据结构抽象之间进行映射和转换是一个更普遍的问题,当抽象不干净对齐时,这个问题就可能存在。

根据编程范式(面向对象、函数式、过程式等)和数据库类型(关系型、文档型、键值型等),不同程度的抽象可能有用。通常,应用程序中数据结构的复杂性决定了与数据存储交互的容易程度。

面向对象编程倾向于产生大量具有重要状态和关系需要考虑的结构。其他一些编程范式更明确地说明状态的存储位置以及如何管理。例如,纯函数式语言不允许可变状态,因此状态通常是函数或对象的输入,这些函数或对象输出新状态。这种数据与操作的清晰分离,以及状态生命周期的明确性,可以帮助简化与数据库的交互。

无论哪种方式,通过在两种不同表示之间进行映射的软件与数据库交互的选项通常是可用的。因此,虽然 ORM 描述了具有独特挑战的这些特定子集,但应用程序内存和持久存储之间的映射通常需要考虑,无论细节如何。

Active Record 与 Data Mapper ORM

不同的 ORM 采用不同的策略来映射应用程序和数据库结构。两个主要类别是 *active record 模式* 和 *data mapper 模式*。

Active record 模式试图将数据库的数据封装在代码中对象的结构中。对象包含从数据库保存、更新或删除的方法,并且对对象的更改旨在轻松地反映在数据库中。通常,应用程序中的 active record 对象代表数据库中的一条记录。

Active record 实现允许您通过在代码中创建和连接类和实例来管理数据库。由于这些通常将类实例直接映射到数据库记录,因此如果您了解代码中使用的对象,就很容易概念化数据库中的内容。

不幸的是,这也可能带来一些主要缺点。应用程序往往与数据库紧密耦合,这在尝试迁移到新数据库或甚至测试代码时都可能导致问题。您的代码倾向于依赖数据库来填补从对象中卸载的空白。这两个领域之间的“神奇”转换也可能导致性能问题,因为系统试图无缝地将复杂对象映射到底层数据结构。

数据映射器模式是另一种常见的 ORM 模式。与 Active Record 模式一样,数据映射器试图充当代码和数据库之间的独立层,在两者之间进行协调。然而,它不是试图无缝地集成对象和数据库记录,而是专注于解耦和转换它们,同时让它们独立存在。这有助于将业务逻辑与处理映射、表示、序列化等数据库相关细节分离。

因此,开发人员不再让 ORM 系统自行找出如何在对象和数据库表之间进行映射,而是负责显式地在两者之间进行映射。这有助于避免紧密耦合和幕后操作,但代价是在确定适当的映射方面需要更多的工作。

ORM 的优点

ORM 因多种原因而广受欢迎。

它们有助于将底层数据领域抽象化,使其在应用程序上下文中易于理解。ORM 帮助您将数据存储视为当前工作的扩展,而不是一个独立的系统。这有助于开发人员更快地专注于核心业务逻辑,而不是陷入存储后端的细微差别。

这带来的另一个副作用是 ORM 消除了许多与数据库交互所需的样板代码。ORM 通常附带迁移工具,可帮助您根据代码中的更改管理数据库模式更改。如果 ORM 可以帮助管理数据库结构的更改,您就不必预先找出完美的数据库模式。您的应用程序和数据库更改通常是相同或密切相关的,这有助于跟踪您对代码进行更改时对数据库所做的更改。

ORM 的缺点

ORM 并非没有缺点。在许多情况下,这些缺点源于使 ORM 有用的相同决策。

ORM 的根本问题之一是试图隐藏数据库后端的细节。这种混淆在简单情况下或短期内使 ORM 的使用变得更容易,但随着复杂性的增长,往往会导致问题。

抽象永远不会 100% 完成,并且在不了解底层查询语言或数据库结构的情况下尝试使用 ORM 常常会导致有问题的假设。这会使调试和性能调优变得困难或不可能。

也许使用 ORM 最著名的问题是对象关系阻抗不匹配,这个术语用来描述在面向对象编程和关系型数据库使用的关系范式之间进行转换的困难。这两类技术使用的数据模型之间的不兼容性意味着,随着复杂性的每一次增加,都需要额外的、不完美的抽象。对象关系阻抗不匹配被称为计算机科学领域的越南战争(指越南战争),因为它倾向于随时间增加复杂性并导致成功或改变方向都变得困难或不可能的情况。

一般来说,ORM 往往比替代方案慢,特别是对于复杂查询。ORM 通常为相对简单的数据库操作生成复杂的查询,因为它们采用通用模式,这些模式必须足够灵活以处理其他情况。依赖 ORM 在所有情况下都能做正确的事,可能导致代价高昂的错误,而且这些错误很难在前期发现。

ORM 总结

ORM 是一种有用的抽象,可以大大简化数据库操作。它们可以帮助您快速设计和迭代,并弥合应用程序逻辑和数据库结构之间的概念差异。然而,这些优点中的许多都像一把双刃剑。它们可能会阻碍您理解数据库,并使调试、改变范式或提高性能变得具有挑战性。

词汇表

在处理数据库和应用程序之间接口的技术时,您可能会遇到一些不熟悉的术语。在本节中,我们将简要介绍一些您可能会遇到的最常见术语,其中一些在本文章前面已经介绍过,有些则没有。

  • 数据映射器(Data mapper): 数据映射器是一种设计模式或软件,用于将编程数据结构映射到存储在数据库中的数据结构。数据映射器试图在两个源之间同步更改,同时保持它们彼此独立。映射器本身负责维护一个可用的转换,使开发人员能够迭代应用程序数据结构,而无需担心数据库表示。
  • 数据库驱动程序(Database driver): 数据库驱动程序是一种旨在封装和启用应用程序与数据库之间连接的软件。数据库驱动程序抽象了如何建立和管理连接的底层细节,并为数据库系统提供统一的、程序化的接口。通常,数据库驱动程序是开发人员用于与数据库交互的最低抽象级别,更高级别的工具构建在驱动程序提供的功能之上。
  • 注入攻击(Injection attack): 注入攻击是一种攻击,其中恶意用户试图通过在面向用户的应用程序字段中精心构造的输入来执行不需要的数据库操作。通常,这用于检索不应访问的数据或删除或篡改数据库中的信息。
  • ORM: ORM,或对象关系映射器,是抽象层,用于在关系型数据库中使用的数据表示和面向对象编程中使用的内存表示之间进行转换。ORM 为数据库中的数据提供面向对象的接口,试图减少代码量并使用熟悉的原型来加速开发。
  • 对象关系阻抗不匹配(Object-relational impedance mismatch): 对象关系阻抗不匹配是指在面向对象的应用程序和关系型数据库之间进行转换的困难。由于数据结构差异显著,因此很难忠实地、高性能地将程序数据结构更改并转录为存储后端使用的格式。
  • 持久化框架(Persistence framework): 持久化框架是一种中间件抽象层,旨在弥合程序数据和数据库之间的差距。如果其采用的抽象将对象映射到关系实体,则持久化框架也可能是 ORM。
  • 查询构建器(Query builder): 查询构建器是一个抽象层,通过提供受控接口来帮助开发人员访问和控制数据库,该接口增加了可用性、安全性和灵活性功能。通常,查询构建器相对轻量级,侧重于简化数据访问和数据表示,并且不尝试将数据转换为特定的编程范式。
  • SQL: SQL,即结构化查询语言,是一种为管理关系型数据库管理系统而开发的领域特定语言。它可用于查询、定义和操作数据库中的数据及其组织结构。SQL 在关系型数据库中无处不在。

结论

在本文中,我们探讨了几种从应用程序与数据库交互的不同选项。我们研究了使用 SQL 等数据库原生查询语言、使用查询构建器安全地构建查询以及使用 ORM 提供更完整抽象的不同抽象级别和灵活性。

这些方法各有其用,某些方法可能比其他方法更适合特定类型的应用程序。重要的是要了解您的应用程序需求、组织内的数据库知识以及您选择实现的抽象(或缺乏抽象)的成本。总的来说,理解每种方法将使您最有机会选择适合您项目的选项。

常见问题

可视化 SQL 查询构建器是用于创建 SQL 查询的图形用户界面。

通常,查询构建器相对轻量级,侧重于简化数据访问和数据表示,并且不尝试将数据转换为特定的编程范式。

在线 SQL 查询构建器是一种基于云的工具,可帮助快速构建 SQL 查询,而无需了解如何编写 SQL。

通常,查询构建器相对轻量级,侧重于简化数据访问和数据表示,并且不尝试将数据转换为特定的编程范式。

Active Record 模式试图将数据库的数据封装在代码中对象的结构中。对象包含从数据库保存、更新或删除的方法,并且对对象的更改旨在轻松地反映在数据库中。

Active Record 实现允许您通过在代码中创建和连接类和实例来管理数据库。

数据映射器模式试图充当代码和数据库之间的独立层,在两者之间进行协调。

它专注于解耦和转换它们,同时让它们独立存在。这有助于将业务逻辑与处理映射、表示、序列化等数据库相关细节分离。

原生查询是由 SQL 语句组成的数据库查询,可以使用数据库客户端直接在数据库中执行。

关于作者
Daniel Norman

Daniel Norman

Daniel 是一名软件工程师,在现代网络和云环境方面拥有丰富的经验。他从事数据库开发已超过 15 年,对开源和现代开发工具充满热情。
Justin Ellingwood

Justin Ellingwood

Justin 自 2013 年以来一直撰写关于数据库、Linux、基础设施和开发工具的文章。他目前与妻子和两只兔子住在柏林。他通常不必用第三人称写作,这对所有相关方来说都是一种解脱。
© . This site is unofficial and not affiliated with Prisma Data, Inc.