ORM 与数据库迁移
简介
对象关系映射(ORM)和数据库迁移是 SQL 和关系型数据库领域中的基本概念。ORM 为开发人员提供了一种使用编程语言与数据库交互的便捷方式,屏蔽了 SQL 的复杂细节。从数据建模到 SQL 构建以及即席查询,不同的 ORM 提供了不同的功能、接口和控制级别,以适应各种使用场景和用户偏好。
相比之下,迁移是指随着时间的推移对数据库模式进行受控的更改或修改,它支撑着项目数据库在面对不断变化的需求或规范时的演进、灵活性和适应性。无论是创建表、添加索引、更改列类型还是其他任务,迁移都消除了手动更改数据库模式的必要性。
我们将对比主流的 ORM,重点关注它们的数据建模能力、SQL 构建、即席查询以及为开发人员提供的精确控制程度。随后,我们将深入探讨数据库迁移,进一步强调它们在 SQL 和关系型数据库背景下的功能和影响。从创建新数据库到跟踪更改并确保数据库完整性,不同的迁移策略提供了不同程度的控制和简便性,这对于数据库管理非常有价值。
定义
ORM
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于在关系型数据库和面向对象编程语言之间转换或映射数据。SQL 和关系型数据库与 ORM 配合得非常好,因为它们允许应用程序使用面向对象的技术来管理数据库。
在 SQL 和关系型数据库的背景下,这种映射的概念出现在尝试将关系型数据库概念转换为面向对象概念时。
其工作原理如下:
- 表变为类: 在面向对象框架中,类用于定义对象。在关系型数据库中,这些类通常直接与数据库表相关联。
- 行变为对象: 正如数据库表中的每一行代表一个单一项目一样,类的一个实例(或对象)代表应用程序中的一个单一项目。
- 列变为对象属性: 表中为每个项目存储的各个数据片段(即列),在应用程序中成为对象的属性。
ORM 通过引入一个可以使用流行的面向对象范式进行操作的“虚拟对象数据库”,解决了面向对象世界与关系型世界之间的不匹配问题。
使用 ORM 的一个主要优势是,它们使开发人员能够使用高级编程语言进行工作,从而简化了代码并降低了出错的可能性。这包括在底层数据库中创建、读取、更新和删除记录。如果数据库结构发生变化,ORM 还可以帮助封装数据库结构并将更改隔离在一个地方。
然而,ORM 可能会带来性能损失,并且可能不适合具有复杂查询或密集型数据库操作的应用程序,因为它们可能无法像手写 SQL 那样有效地优化 SQL 查询。
ORM 的示例包括 Java 中的 Hibernate、JavaScript 中的 Sequelize 和 Python 中的 SQLAlchemy。开发人员通常会选择适合其项目需求和首选编程语言的 ORM。
总之,在 SQL 和关系型数据库中使用 ORM 有助于简化数据管理,并创建更具可扩展性和可维护性的应用程序。但是,在选择时应审慎考虑应用程序的复杂性和性能要求。
迁移
数据库迁移是指对应用于数据库的更改或修改的管理。此过程主要处理数据库模式中的任何更改或更新。然而,它也涉及改变数据库状态和结构的各个方面,如添加、更改、删除列和表、索引、数据转换等。
之所以使用“迁移”一词,是因为这些更改或修改可以在不同的机器和系统之间移动,通常是从开发环境移动到生产环境。
迁移可能出于多种原因,例如增强数据库功能、更改数据库结构以匹配新的业务需求、解决错误或提高系统性能。
为了管理这些迁移,人们使用工具和软件来帮助将更改应用到数据库。它们还允许在出现错误或问题时进行回滚,使其成为现代数据库管理和软件开发生命周期中不可或缺的一部分。
数据库迁移的目标是在不阻碍或最小化应用程序停机时间的情况下,将数据库模式和数据从一个环境传输到另一个环境。
总的来说,数据库迁移对于在整个生命周期中维护数据库的完整性、性能和健康状况至关重要。
驱动程序与方言
- SQL 驱动程序:SQL 驱动程序充当软件应用程序与数据库之间的接口或桥梁。它们是使程序能够与数据库进行通信和交互的软件组件。SQL 驱动程序允许应用程序使用 SQL 命令与数据库建立连接、读取、写入、验证身份并执行事务。在实践中,各种类型的 SQL 驱动程序包括用于 Java 应用程序的 JDBC(Java 数据库连接)驱动程序、用于使用 ODBC API 的应用程序的 ODBC(开放数据库连接)驱动程序,以及用于 .NET 应用程序的 ADO.NET 驱动程序。 例如,如果您正在构建一个与 MySQL 数据库交互的 Java 应用程序,则需要一个专门为 MySQL 数据库实现 JDBC API 的 JDBC 驱动程序。该驱动程序了解如何将 Java 应用程序中的函数调用、例程和 SQL 命令转换为 MySQL 数据库能够有效理解和处理的格式。
- SQL 方言:SQL 方言是指特定数据库管理系统(DBMS)所使用的 SQL(结构化查询语言)的变体或版本。SQL 是一种用于管理和操作关系型数据库的标准化语言。虽然 ANSI(美国国家标准协会)为 SQL 提供了一个标准,确保了在不同系统间的一致使用,但每个 DBMS 通常会在基础 SQL 之上引入额外的专有功能或语法,从而导致了不同的 SQL 方言。 例如,MySQL、Oracle Database、PostgreSQL 和 SQL Server 都有其独特的 SQL 方言。虽然基础的 SQL 命令(如 `SELECT`、`UPDATE`、`DELETE`、`INSERT` 和 `WHERE`)在所有 SQL 方言中通常相同,但在高级功能(如错误处理、存储过程和字符串连接语法)方面存在差异。这意味着开发人员必须了解他们正在使用的特定 SQL 方言,尤其是在迁移或集成不同的数据库系统时。
示例
- ActiveRecord 将类连接到关系型数据库表,为应用程序建立了一个几乎零配置的持久层。
- Ecto 是一个用于数据映射和语言集成查询的工具包。
- SqlAlchemy 是 Python 的数据库工具包。
- Drizzle 是一个 TypeScript ORM,专为 SQL 数据库设计,并充分考虑了类型安全。
- Knex 是一个功能完备、支持多种方言(PostgreSQL、MySQL、CockroachDB、MSSQL、SQLite3、Oracle(包括 Oracle Wallet 身份验证))的 Javascript 查询构建器。
- Conman 依赖于动态变量来管理连接。动态变量允许连接在事务中根据上下文重新绑定。在处理多个数据库时,需要一个单独的变量来跟踪每个数据库连接。
- HoneySQL 是以 Clojure 数据结构形式存在的 SQL。以编程方式构建查询——即使在运行时——而无需拼接字符串。
分类
模型
ActiveRecord**、**Ecto**、**SqlAlchemy**、**Drizzle 依赖于静态模型的概念,因为模型在使用前必须先定义。
备注
- 简单的数据管理:对象关系映射(ORM)中的模型提供了一种简单有效的方法,可以使用面向对象的语法在数据库中创建、检索、更新和删除记录。
- 数据库无关:ORM 作为抽象层,允许开发人员以最少的代码更改在不同的数据库之间切换。
- 精简代码:模型有助于精简代码并减少开发人员需要编写的 SQL 量,从而产生更简洁、更易于理解的代码。
- 增强安全性:它们为开发人员提供了一种安全处理数据库操作的方法,防止 SQL 注入等攻击。
- 提高生产力:模型可以显著加快开发过程。这主要是因为开发人员可以更多地专注于应用程序的业务逻辑,而不是编写复杂的 SQL 查询。
- 封装业务逻辑:模型将相关方法保持在一起,比仅仅将所有代码保留在控制器中提供了更好的代码组织。
- 支持数据库关系:ORM 模型还可以通过一对一、一对多和多对多等关系简化对关联表的操作。
- 可维护性:使用模型,开发人员可以更改数据库模式,并方便地将更改迁移到现有数据库中,而无需更改代码库。
- 可扩展:通过自动数据库模式更新和其他功能,使用模型的 ORM 可以轻松扩展以满足不断增长的应用程序的需求。
- 与其他库集成:大多数 ORM 提供了轻松与其他现有库或工具集成的方法,从而为应用程序丰富了更复杂的功能。
即席查询
Knex 和 HoneySQL 都非常适合执行即席查询,因为它们允许构建任意 SQL 语句,而无需 模型 或在应用程序内处理 SQL 字符串。
备注
- 安全风险:使用随机输入数据插值 SQL 查询字符串可能导致 SQL 注入攻击。这可能允许攻击者操纵 SQL 查询,从而导致数据窃取、损坏或删除。
- 数据完整性:如果没有适当的验证,随机输入可能会损害数据库中存储数据的完整性。未经清理的输入可能包含不正确或误导性的数据。
- 性能:通过使用随机输入数据插值 SQL 查询字符串,预编译语句的性能优化将丢失。数据库引擎将每个带有不同数据的查询视为完全独立的查询,这降低了效率。
- 调试困难:如果 SQL 查询是由随机数据输入构建的,则很难进行调试。意外错误、不适当的数据类型或语法错误变得难以追踪。
- 维护:它增加了复杂性,使得长期阅读、理解、维护或修改 SQL 查询变得具有挑战性。
- 易错:在处理复杂输入数据时,查询字符串可能会变得过于复杂且更容易出错。可能会出现意外错误,例如编写格式错误的查询,这可能导致应用程序失败。
- 兼容性问题:如果数据库模式发生变化,应用程序将在所有依赖于旧模式的插值 SQL 字符串处中断。
控制
Conman 在我们所有的选项中提供了最终的控制权,因为我们可以直接利用 手写 的 SQL 查询和语句。
备注
- 效率和速度:手写的 SQL 查询可以优化数据库性能和速度。您可以针对特定的数据集微调脚本,从而减少检索数据所需的计算和资源。
- 灵活性:与许多查询构建器或 ORM 工具不同,手写 SQL 提供了无限的灵活性。您可以以多种不同的方式操作数据,从而实现其他工具可能不允许的复杂查询。
- 知识和理解:编写自己的 SQL 查询可以增强您对数据库及其操作的理解。当您与代码进行实际交互时,您将学习如何更有效地组织和管理数据。
- 自定义:自定义 SQL 查询可满足特定需求,实现复杂计算、嵌套查询、定制并发或针对详尽且广泛的数据集的专门数据处理。
- 终极控制:手写 SQL 查询使您可以完全控制数据库及其操作。您可以精确地决定如何获取、改进、删除或插入数据。
- 增强安全性:如果操作正确,手写查询可以更安全。您可以实施严格的检查和验证,以避免 SQL 注入和数据泄露。
- 跨平台兼容性:SQL 在 Oracle、MySQL、SQL Server 等不同数据库中几乎是统一认可的。因此,您的手写查询通常可以以最小的更改移植到不同的平台上。
- 经济高效:手写 SQL 查询的创建和执行无需昂贵的专用软件,这对开发人员来说非常经济高效。
- 维护:一旦您习惯了手写 SQL 查询,从长远来看,维护这些查询可能会变得更容易。您不必依赖在软件版本更新时会失败的自动化函数。
- 创造力的实验室:手写 SQL 查询可以将数据库组织者转变为创造力的实验室。精心制作一个复杂的 SQL 查询感觉就像拼图一样——这是一个有益的挑战,可以带来对数据及其潜在用途的更深刻理解。
请记住:与任何代码一样,手写 SQL 在清晰编写并添加良好注释时效果最好。清晰度和可维护性在 SQL 环境中与在任何其他编程语言中一样重要。
演示
services:
db:
container_name: ubntth_pg
image: postgres
restart: always
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: ubntth_db
ports:
- "5432:5432"
pgadmin:
container_name: ubntth_pgadmin4
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: root@admin.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"export DB_HOST=127.0.0.1
export DB_PORT=5432
export DB_USER=root
export DB_PASS=root
export DB_NAME=ubntth_db
export DB_URL=postgres://root:root@127.0.0.1:5432/ubntth_db{
"name": "orms_and_migrations",
"version": "1.0.0",
"description": "ORMs and Migrations",
"main": "run.mjs",
"scripts": {
"dev": "bun run.mjs",
"gen:migration": "drizzle-kit generate:pg --schema=./schema.mjs",
"run:migration": "bun ./src/schema.mjs",
"drizzle-studio": "bunx drizzle-kit studio",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"PostgresSQL",
"Javascript",
"SQL",
"Databases"
],
"author": "ChiefKemist",
"license": "ISC",
"dependencies": {
"drizzle-orm": "^0.28.6",
"elysia": "^0.6.19",
"pg": "^8.11.3"
},
"devDependencies": {
"@types/pg": "^8.10.2",
"drizzle-kit": "^0.19.13"
}
}/** @type { import("drizzle-kit").Config } */
export default {
schema: "./schema.mjs",
driver: 'pg',
dbCredentials: {
connectionString: Bun.env.DB_URL,
}
};import { drizzle } from "drizzle-orm/node-postgres";
import { Client } from "pg";
export const connect = () => {
const client = new Client({
connectionString: "postgres://user:password@host:port/db",
});
// or
const client = new Client({
host: Bun.env.DB_HOST,
port: Bun.env.DB_PORT,
user: Bun.env.DB_USER,
password: Bun.env.DB_PASS,
database: Bun.env.DB_NAME,
});
await client.connect();
const db = drizzle(client);
return db
};import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
export const users = pgTable('users', {
id: serial('id').primaryKey(),
fullName: varchar('full_name', { length: 256 }),
//phone: varchar('phone', { length: 256 }),
}, (users) => ({
nameIdx: index('name_idx').on(users.fullName),
}));
//export const accounts = pgTable('accounts', {
// id: serial('id').primaryKey(),
// bankName: varchar('bank_name', { length: 256 }),
// //accountNumber: varchar('account_number', { length: 256 }),
// userId: int('user_id').references(() => users.id),
//});
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
import { connect } from "./connection"l
//const sql = postgres("...", { max: 1 })
//const db = drizzle(sql);
const db = connect();
await migrate(db, { migrationsFolder: "drizzle" });结论
“Blub 悖论”概念由保罗·格雷厄姆(Paul Graham)在一篇讨论不同编程语言相对优点的文章中提出。它指的是一个理论上的程序员,由于无法理解更高级语言的卓越特性和能力,会将任何比其首选语言更先进的语言视为多余且过于复杂。这个概念的名字源于虚构的“Blub”编程语言,它处于语言熟练度的核心。使用“Blub”的人可能认为它具有足够的功能,但可能会被那些提供更高抽象程度的语言所吓倒;相比之下,那些使用更强大语言(如格雷厄姆视角下的 Lisp)的人,能够利用其额外特性来优化编码效率。
“Blub”悖论与对象关系映射(ORM)的选择以及 SQL 和关系型数据库等元素也有着密切的联系。例如,数据建模和数据库迁移取决于所使用的 ORM。在“Blub”语言中概念化的 ORM 提供行到对象的线性映射,辅助 CRUD(创建、读取、更新、删除)等基本操作,并提供复杂事务、自动模式迁移和高级缓存等附加功能,尽管存在抽象泄漏和严格性等局限性。然而,在 Lisp 语言中创建的 ORM 由于使用了宏,看起来非常原生,并且能够在不牺牲生产力或编码效率的情况下提供高级功能。
本质上,Blub 悖论意味着程序员可能会因为高估熟悉语言的简单性价值,而低估不熟悉语言的潜在好处,从而在不知不觉中忽略了更强大的 ORM 和语言的高级功能。虽然在“Blub”语言中构建的 ORM 可能具有便利性和简单性的诱惑,但那些在“Lisp”或其他高级语言中开发的 ORM 通常表现出更强的能力和适应性,特别是在使用 SQL 和关系型数据库处理复杂数据操作任务时。

