什么是ORM?
在软件开发领域,对象关系映射(ORM)是一种编程技术,用于转换编程语言对象和数据库表之间的数据。传统 ORM框架(如:TypeORM、Entity Framework、Spring Data JPA 等)都要求应用程围绕它们的API构建业务。
以TypeORM为例,需要先定义实体类
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column('text')
description: string;
@Column()
filename: string;
@Column('int')
views: number;
@Column()
isPublished: boolean;
}
然后通过存储库API进行使用
import { Injectable, Inject } from '@nestjs/common';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
@Injectable()
export class PhotoService {
constructor(
@Inject('PHOTO_REPOSITORY')
private photoRepository: Repository<Photo>,
) {}
async findAll(): Promise<Photo[]> {
return this.photoRepository.find();
}
}
在遇到复杂查询时你必须切换到查询构建器API甚至完全使用原生SQL
const posts = await dataSource.getRepository(Post)
.createQueryBuilder("post")
.where((qb) => {
const subQuery = qb .subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery()
return "post.title IN " + subQuery
})
.setParameter("registered", true) .getMany()
这不仅丢失了类型安全还导致双重学习曲线(需要同时了解SQL和框架的API),而且生成的SQL是并不完全可控的。
Headless ORM
Drizzle 是一个以类型安全方式提供SQL-Like
和Relational
API且支持多种运行环境和数据库的 Headless ORM。
从传统数据库的使用方式迁移到使用Drizzle几乎无学习成本
安装
pnpm add drizzle-orm mysql2
pnpm add -D drizzle-kit
定义Schema
创建 src/db/schema.ts
文件用于定义数据库Schema
import { sql } from "drizzle-orm";
import * as t from "drizzle-orm/mysql-core";
export const dateColumns = {
updatedAt: t.datetime("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull().$onUpdate(() => sql`CURRENT_TIMESTAMP`),
createdAt: t.datetime("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
deletedAt: t.datetime("deleted_at"),
};
export const User = t.mysqlTable("t_user", {
id: t.int().primaryKey().autoincrement(),
email: t.varchar({ length: 255 }).notNull().unique(),
password: t.varchar({ length: 255 }).notNull(),
...dateColumns,
});
管理数据库
定义 drizzle.config.ts
配置文件
import dotenvx from '@dotenvx/dotenvx';
import { defineConfig } from 'drizzle-kit';
dotenvx.config({
path: [`./env/${process.env.NODE_ENV}.env`],
});
export default defineConfig({
out: './drizzle',
schema: './src/db/schema.ts',
dialect: 'mysql',
dbCredentials: {
database: process.env.DB_NAME!,
host: process.env.DB_HOST!,
port: Number.parseInt(process.env.DB_PORT!, 10),
user: process.env.DB_USER!,
password: process.env.DB_PASS!,
},
});
执行数据库迁移命令
pnpm dlx drizzle-kit push
其他命令
-
drizzle-kit generate
- 功能:基于你的 Drizzle 模式,让你生成 SQL 迁移文件,无论是在声明时还是在后续的变更中。
-
drizzle-kit migrate
- 功能:将生成的 SQL 迁移文件应用到你的数据库中。
-
drizzle-kit pull
- 功能:拉取现有数据库的模式,将其转换为 Drizzle 模式,并保存到你的代码库中。
-
drizzle-kit push
- 功能:将你的 Drizzle 模式推送到数据库中,无论是在声明时还是在后续的模式变更中。
-
drizzle-kit studio
- 功能:连接到你的数据库并启动 Drizzle Studio GUI界面,你可以使用它方便地浏览数据库。
-
drizzle-kit check
- 功能:遍历所有生成的迁移,并检查任何可能的竞态冲突。
-
drizzle-kit up
- 功能:用于升级之前生成的迁移的快照。
这些命令是 Drizzle ORM 提供的 CLI 工具集,用于帮助开发者在开发过程中更高效地管理数据库迁移、模式同步和其他数据库相关的任务。
连接数据源
import * as schema from '@/db/schema';
import mysql from "mysql2/promise";
const client = mysql.createPool({
host: process.env.DB_HOST,
port: Number.parseInt(process.env.DB_PORT, 10),
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
});
const db = drizzle({
client,
schema,
logging: true
});
获取表信息
import { User } from "@/db/schema";
import { getTableConfig } from "drizzle-orm/mysql-core";
const tableConfig = getTableConfig(User);
获取列信息
import { User } from "@/db/schema";
import { getTableColumns } from "drizzle-orm";
const tableColumns = getTableColumns(User);
Raw SQL API
使用SQL 模板,可以在保持类型安全和查询参数化的同时,实现所需的复杂查询
import { sql } from "drizzle-orm";
const id = 1;
await db.execute(sql`select ${User.id}, ${User.email} from ${User} where ${User.id} = ${id}`);
Relational API
import { eq } from "drizzle-orm";
await this.db.query.User.findFirst({ where: eq(User.id, 1) });
SQL-Like API
await this.db.select({
id: User.id
}).from(User).where(eq(User.id, 1));
过滤器和条件运算符
import { eq, ne, gt, gte, ... } from "drizzle-orm";
事务管理
await db.transaction(async (tx) => {
await tx.update(Account).set({ balance: sql`${Account.balance} - 100.00` }).where(eq(User.name, 'Dan'));
await tx.update(Account).set({ balance: sql`${Account.balance} + 100.00` }).where(eq(User.name, 'Andrew'));
});
批量操作
const batchResponse: BatchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
db.update(usersTable).set({ name: 'Dan' }).where(eq(usersTable.id, 1)), db.query.usersTable.findMany({}),
db.select().from(usersTable).where(eq(usersTable.id, 1)),
db.select({ id: usersTable.id, invitedBy: usersTable.invitedBy }).from(usersTable),
]);
动态构建
import { MySqlSelectBase } from "drizzle-orm/mysql-core";
function withPagination<T extends MySqlSelectBase>(
qb: T,
page: number = 1,
pageSize: number = 10,
) {
return qb.limit(pageSize).offset((page - 1) * pageSize);
}
const dynamicQuery = query.$dynamic();
withPagination(dynamicQuery, 1);
自定义类型
import { customType } from 'drizzle-orm/pg-core';
const customTimestamp = customType<
{
data: Date;
driverData: string;
config: { withTimezone: boolean; precision?: number };
}
>({
dataType(config) {
const precision = typeof config.precision !== 'undefined'
? ` (${config.precision})`
: '';
return `timestamp${precision}${config.withTimezone ? ' with time zone' : ''
}`;
},
fromDriver(value: string): Date {
return new Date(value);
},
});