阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 与数据库ORM

与数据库ORM

作者:陈川 阅读数:21408人阅读 分类: TypeScript

理解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解决方案,每个都有其独特的特点和优势:

  1. TypeORM:最全面的TypeScript ORM,支持Active Record和Data Mapper模式
  2. Prisma:下一代ORM,提供类型安全的数据库客户端
  3. Sequelize:成熟的ORM,对TypeScript的支持越来越好
  4. 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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌