与数据库ORM
理解ORM的基本概念
ORM(Object-Relational Mapping)是一种编程技术,用于在面向对象编程语言和关系型数据库之间建立映射关系。它允许开发者使用面向对象的方式操作数据库,而不必直接编写SQL语句。在TypeScript中,ORM工具通常提供类型安全的API,使得数据库操作更加直观和安全。
// 传统SQL查询
const users = await db.query('SELECT * FROM users WHERE age > 18');
// 使用ORM查询
const users = await UserRepository.find({ where: { age: MoreThan(18) } });
TypeScript中流行的ORM库
TypeScript生态系统中有几个主流的ORM解决方案,每个都有其独特的特点和优势:
- TypeORM:最全面的TypeScript ORM,支持Active Record和Data Mapper模式
- Prisma:下一代ORM,提供类型安全的数据库客户端
- Sequelize:成熟的ORM,对TypeScript的支持越来越好
- MikroORM:基于Data Mapper模式的现代ORM
// TypeORM实体定义示例
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
}
实体定义与关系映射
在TypeScript ORM中,实体是与数据库表对应的类。通过装饰器或配置文件,我们可以定义表结构、字段类型和表间关系。
基本实体定义
// 使用TypeORM定义实体
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
name: string;
@Column('decimal', { precision: 10, scale: 2 })
price: number;
@Column({ default: true })
isActive: boolean;
}
关系类型
ORM通常支持以下几种关系:
- 一对一(OneToOne)
- 一对多(OneToMany)
- 多对一(ManyToOne)
- 多对多(ManyToMany)
// 一对多关系示例
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Book, book => book.author)
books: Book[];
}
@Entity()
export class Book {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => Author, author => author.books)
author: Author;
}
查询构建器与Repository模式
ORM提供了多种查询数据的方式,从简单的find方法到复杂的查询构建器。
基本查询
// 使用Repository进行简单查询
const userRepository = connection.getRepository(User);
// 查找所有用户
const allUsers = await userRepository.find();
// 带条件的查询
const adultUsers = await userRepository.find({ where: { age: MoreThan(18) } });
查询构建器
对于复杂查询,查询构建器提供了更灵活的方式:
// 使用QueryBuilder
const users = await userRepository
.createQueryBuilder('user')
.where('user.age > :age', { age: 18 })
.andWhere('user.isActive = :isActive', { isActive: true })
.orderBy('user.name', 'ASC')
.skip(10)
.take(5)
.getMany();
事务管理与数据操作
ORM简化了事务管理,确保数据操作的原子性。
基本事务
// 使用事务
await connection.transaction(async manager => {
const user = new User();
user.name = 'John';
user.age = 30;
await manager.save(user);
const profile = new Profile();
profile.user = user;
profile.bio = 'Software Developer';
await manager.save(profile);
});
批量操作
// 批量插入
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
await userRepository
.createQueryBuilder()
.insert()
.into(User)
.values(users)
.execute();
迁移与数据库同步
ORM通常提供迁移工具,帮助管理数据库结构变更。
自动同步
// TypeORM自动同步
await connection.synchronize(); // 开发环境中使用
手动迁移
// 创建迁移文件
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserTable1620000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE user (
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(100) NOT NULL,
age int
)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE user`);
}
}
性能优化与高级特性
延迟加载与急切加载
// 急切加载
const userWithPosts = await userRepository.findOne({
where: { id: 1 },
relations: ['posts']
});
// 延迟加载
@Entity()
export class User {
// ...
@OneToMany(() => Post, post => post.user, { lazy: true })
posts: Promise<Post[]>;
}
const user = await userRepository.findOne(1);
const posts = await user.posts; // 实际查询在这里执行
缓存策略
// 查询缓存
const users = await userRepository.find({
where: { isActive: true },
cache: true
});
TypeScript类型安全的优势
TypeScript ORM最大的优势在于类型安全,这显著减少了运行时错误。
// 类型安全的查询
const user = await userRepository.findOne({
where: {
name: 'John',
age: Between(20, 40)
}
});
// user自动推断为User | undefined
if (user) {
console.log(user.name); // 类型安全访问
console.log(user.nonexistent); // 类型错误
}
与GraphQL的集成
现代ORM通常提供与GraphQL的良好集成。
// TypeORM + TypeGraphQL示例
@Entity()
@ObjectType()
export class Recipe {
@Field(type => ID)
@PrimaryGeneratedColumn()
id: number;
@Field()
@Column()
title: string;
@Field(type => [Ingredient])
@OneToMany(() => Ingredient, ingredient => ingredient.recipe)
ingredients: Ingredient[];
}
测试与模拟
测试数据库相关代码时,ORM提供了便利的模拟方式。
// 使用内存数据库测试
beforeEach(async () => {
const connection = await createConnection({
type: 'sqlite',
database: ':memory:',
entities: [User, Post],
synchronize: true
});
// 测试代码...
});
// 模拟Repository
const mockUserRepository = {
find: jest.fn().mockResolvedValue([{ id: 1, name: 'Test User' }])
};
// 在测试中使用模拟
const users = await mockUserRepository.find();
常见问题与解决方案
N+1查询问题
// 有N+1问题的代码
const users = await userRepository.find();
for (const user of users) {
const posts = await postRepository.find({ where: { userId: user.id } });
// ...
}
// 解决方案:使用关系预加载
const users = await userRepository.find({ relations: ['posts'] });
复杂查询优化
// 使用子查询优化
const activeUsers = await userRepository
.createQueryBuilder('user')
.where(qb => {
const subQuery = qb.subQuery()
.select('post.userId')
.from(Post, 'post')
.where('post.createdAt > :date', { date: '2023-01-01' })
.getQuery();
return 'user.id IN ' + subQuery;
})
.getMany();
自定义Repository模式
扩展基础Repository功能可以更好地组织代码。
// 自定义Repository
@EntityRepository(User)
export class UserRepository extends Repository<User> {
findByName(name: string) {
return this.find({ where: { name } });
}
findAdults() {
return this.find({ where: { age: MoreThan(18) } });
}
}
// 使用自定义Repository
const userRepository = connection.getCustomRepository(UserRepository);
const adults = await userRepository.findAdults();
多数据库支持与分片
一些ORM支持多种数据库和分片策略。
// 多数据库配置
const connection = await createConnection({
type: 'mysql',
name: 'writeConnection',
host: 'write.db.example.com',
// ...
});
const readConnection = await createConnection({
type: 'mysql',
name: 'readConnection',
host: 'read.db.example.com',
// ...
});
// 根据操作类型选择连接
function getConnection(isReadOperation: boolean) {
return isReadOperation ? readConnection : connection;
}
原生SQL与ORM混合使用
有时需要直接使用SQL,ORM也支持这种方式。
// 执行原生SQL
const rawData = await connection.query(`
SELECT u.name, COUNT(p.id) as post_count
FROM user u
LEFT JOIN post p ON p.userId = u.id
GROUP BY u.id
`);
// 将原生结果映射到实体
const users = await connection
.createQueryBuilder(User, 'user')
.select('user.name')
.addSelect('COUNT(post.id)', 'postCount')
.leftJoin('user.posts', 'post')
.groupBy('user.id')
.getRawMany();
事件订阅与钩子
ORM通常提供生命周期钩子,用于在特定操作前后执行代码。
// 实体监听器
@Entity()
export class User {
// ...
@AfterInsert()
logInsert() {
console.log(`User inserted: ${this.id}`);
}
@BeforeUpdate()
updateTimestamp() {
this.updatedAt = new Date();
}
}
// 全局事件订阅
connection.subscribe('afterInsert', event => {
if (event.entity instanceof User) {
console.log('New user created:', event.entity);
}
});
与前端框架的集成
ORM可以与前端框架结合,提供端到端的类型安全。
// Angular服务中使用TypeORM
@Injectable()
export class UserService {
constructor(private connection: Connection) {}
async getUsers(): Promise<User[]> {
return this.connection.getRepository(User).find();
}
}
// React组件中使用Prisma
function UserList() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
prisma.user.findMany().then(setUsers);
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
微服务架构中的ORM使用
在微服务架构中,ORM可以帮助管理各个服务的数据库。
// 微服务中的数据库隔离
@Service()
export class UserService {
private userRepository: Repository<User>;
constructor(@InjectConnection('userDB') connection: Connection) {
this.userRepository = connection.getRepository(User);
}
async createUser(userData: CreateUserDto) {
const user = this.userRepository.create(userData);
return this.userRepository.save(user);
}
}
安全最佳实践
使用ORM时也需要注意安全问题。
// 防止SQL注入
// 错误方式(易受注入攻击)
const users = await userRepository.query(
`SELECT * FROM user WHERE name = '${userInput}'`
);
// 正确方式(使用参数化查询)
const users = await userRepository.query(
`SELECT * FROM user WHERE name = ?`,
[userInput]
);
// ORM方式(自动处理参数化)
const users = await userRepository.find({
where: { name: userInput }
});
日志与调试
ORM通常提供详细的日志功能,帮助调试数据库操作。
// 配置日志
const connection = await createConnection({
// ...
logging: true,
logger: 'advanced-console',
maxQueryExecutionTime: 1000 // 记录慢查询
});
// 自定义日志
connection.logger = {
log(level: 'log' | 'info' | 'warn', message: any) {
myCustomLogger.log(level, `[DB] ${message}`);
},
// ...
};
数据库连接池管理
合理的连接池配置对性能至关重要。
// 连接池配置
const connection = await createConnection({
// ...
extra: {
connectionLimit: 10, // 最大连接数
queueLimit: 0, // 无限制排队
acquireTimeout: 30000 // 30秒获取连接超时
}
});
// 监控连接池状态
setInterval(() => {
const pool = connection.driver.pool;
console.log(`Pool stats: ${pool.active} active, ${pool.idle} idle`);
}, 5000);
多租户架构实现
ORM可以帮助实现多租户系统。
// 基于模式的租户隔离
@Entity()
@Table({ schema: 'tenant_${tenantId}' })
export class Product {
// ...
}
// 动态设置租户
function setTenant(tenantId: string) {
const originalFind = connection.getRepository(Product).find;
connection.getRepository(Product).find = function(options) {
const schema = `tenant_${tenantId}`;
return originalFind.call(this, {
...options,
where: { ...options?.where, __schema__: schema }
});
};
}
与NoSQL数据库的交互
一些ORM也支持NoSQL数据库。
// TypeORM与MongoDB
@Entity()
export class Product {
@ObjectIdColumn()
id: ObjectID;
@Column()
name: string;
@Column()
price: number;
}
// 使用方式与关系型数据库类似
const products = await connection.getMongoRepository(Product).find({
where: { price: { $gt: 100 } }
});
数据库版本兼容性处理
处理不同数据库版本间的差异。
// 条件性SQL
async function getUsers() {
if (connection.options.type === 'postgres') {
return connection.query('SELECT * FROM users FOR UPDATE SKIP LOCKED');
} else {
return connection.query('SELECT * FROM users WITH (NOLOCK)');
}
}
// 使用ORM抽象
const users = await userRepository.find({
lock: { mode: 'optimistic', version: 1 }
});
批量操作与流式处理
处理大量数据时的优化策略。
// 批量插入
const batchSize = 1000;
for (let i = 0; i < largeData.length; i += batchSize) {
const batch = largeData.slice(i, i + batchSize);
await userRepository.insert(batch);
}
// 流式读取
const stream = await userRepository
.createQueryBuilder('user')
.stream();
stream.on('data', user => {
// 处理每个用户
});
stream.on('end', () => {
// 处理完成
});
自定义数据类型与转换
处理特殊数据类型和自定义转换。
// 自定义列类型
@Column({
type: 'json',
transformer: {
to(value: string[]): string {
return JSON.stringify(value);
},
from(value: string): string[] {
return JSON.parse(value);
}
}
})
tags: string[];
// 空间数据类型
@Column({
type: 'geometry',
spatialFeatureType: 'Point',
srid: 4326
})
location: Point;
数据库健康检查与监控
实现数据库健康监控。
// 健康检查端点
app.get('/health', async (req, res) => {
try {
await connection.query('SELECT 1');
res.status(200).json({ status: 'healthy' });
} catch (error) {
res.status(500).json({ status: 'unhealthy', error: error.message });
}
});
// 性能监控
connection.queryEvents.on('query', ({ query, duration }) => {
if (duration > 1000) {
logSlowQuery(query, duration);
}
});
数据库种子与测试数据
自动化生成测试数据。
// 使用faker生成测试数据
async function seedDatabase() {
const userRepository = connection.getRepository(User);
for (let i = 0; i < 100; i++) {
const user = new User();
user.name = faker.name.findName();
user.email = faker.internet.email();
user.age = faker.datatype.number({ min: 18, max: 80 });
await userRepository.save(user);
}
}
// 工厂模式
class UserFactory {
static create(overrides?: Partial<User>): User {
return {
name: faker.name.findName(),
email: faker.internet.email(),
age: faker.datatype.number({ min: 18, max: 80 }),
...overrides
} as User;
}
}
数据库索引优化
通过ORM管理数据库索引。
// 定义索引
@Entity()
@Index(['firstName', 'lastName'])
@Index(['email'], { unique: true })
export class User {
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
email: string;
}
// 查询时使用索引提示
const users = await userRepository
.createQueryBuilder('user')
.useIndex('IDX_USER_NAME')
.where('user.name LIKE :name', { name: '%John%' })
.getMany();
多语言与国际化支持
处理多语言数据。
// 多语言实体
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column('json')
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与Deno运行时