TypeScript与TypeORM:类型安全的数据库操作

316 阅读4分钟

随着前端技术的发展,后端开发也逐渐注重类型安全和开发效率。TypeScript作为一种静态类型语言,可以很好地与JavaScript生态系统集成,而TypeORM则是一个基于TypeScript构建的对象关系映射(ORM)工具,它允许开发者以面向对象的方式操作数据库,同时提供了强大的类型支持。

准备工作

安装Node.js

确保你的机器上已经安装了Node.js,可以通过命令node -v检查版本。

初始化项目

创建一个新的目录作为项目根目录,并在其中初始化一个新的Node.js项目:

mkdir typeorm-tutorial
cd typeorm-tutorial
npm init -y
安装依赖

安装TypeScript、TypeORM以及必要的数据库驱动程序(这里我们以MySQL为例):

npm install typescript typeorm reflect-metadata mysql2
npm install --save-dev @types/node @types/mysql2

接下来,配置TypeScript编译器:

npx tsc --init

编辑tsconfig.json文件,添加以下配置:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

设置数据库连接

在项目的根目录下创建一个名为.ormconfig.json的文件,用于配置TypeORM连接信息:

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "yourpassword",
  "database": "typeorm_tutorial",
  "synchronize": true,
  "logging": false,
  "entities": ["src/entities/*.ts"],
  "migrations": [],
  "subscribers": [],
  "cli": {
    "entitiesDir": "src/entities",
    "migrationsDir": "src/migrations",
    "subscribersDir": "src/subscribers"
  }
}

创建实体

实体是TypeORM中的核心概念之一,它代表数据库中的表。我们先定义一个简单的用户实体:

src/entities目录下创建User.ts文件:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;

  @Column({ default: '' })
  password: string;
}

连接数据库并执行操作

src/index.ts中设置数据库连接,并编写一些基本的CRUD操作:

import { createConnection } from 'typeorm';
import { User } from './entities/User';

createConnection().then(async connection => {
  console.log('Connected to database.');

  // 创建新用户
  const user = new User();
  user.name = 'Alice';
  user.email = 'alice@example.com';
  user.password = 'secret';
  await connection.manager.save(user);

  // 查询所有用户
  const users = await connection.manager.find(User);
  console.log(users);

  // 更新用户
  const updatedUser = await connection.manager.findOne(User, user.id);
  if (updatedUser) {
    updatedUser.name = 'Alicia';
    await connection.manager.save(updatedUser);
  }

  // 删除用户
  await connection.manager.remove(updatedUser);

  await connection.close();
}).catch(error => console.log(error));

运行应用

确保数据库已启动,然后运行:

tsc
node dist/index.js

观察控制台输出,确认数据操作是否成功。

关系管理

在实际应用中,数据通常不是孤立存在的,而是与其他数据之间存在关联。TypeORM 支持多种类型的关系定义,包括一对一(One-to-One)、一对多(One-to-Many)和多对多(Many-to-Many)。

一对一关系

假设我们有两个实体:UserProfile

定义实体:

import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToOne(type => Profile, profile => profile.user)
  @JoinColumn()
  profile: Profile;
}

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  bio: string;

  @OneToOne(type => User, user => user.profile)
  user: User;
}
一对多关系

假设我们有一个 User 实体和一个 Post 实体。

定义实体:

import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToMany(type => Post, post => post.author)
  posts: Post[];
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @ManyToOne(type => User, user => user.posts)
  author: User;
}
多对多关系

假设我们有一个 User 实体和一个 Group 实体。

定义实体:

import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany(type => Group, group => group.users)
  @JoinTable()
  groups: Group[];
}

@Entity()
export class Group {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany(type => User, user => user.groups)
  @JoinTable()
  users: User[];
}

自定义查询

TypeORM 提供了多种方式来进行自定义查询,包括使用 QueryBuilder 和原生 SQL 查询。

使用 QueryBuilder
import { getRepository } from 'typeorm';

const userRepository = getRepository(User);

// 查询所有用户
const users = await userRepository.find();

// 查询特定条件下的用户
const usersByName = await userRepository.find({
  where: { name: 'Alice' }
});

// 使用 QueryBuilder 进行复杂查询
const queryBuilder = userRepository.createQueryBuilder('user')
  .where('user.name = :name', { name: 'Alice' })
  .leftJoinAndSelect('user.posts', 'posts')
  .getMany();

console.log(await queryBuilder);
使用原生 SQL 查询
import { getConnection } from 'typeorm';

const result = await getConnection()
  .query(`SELECT * FROM users WHERE name = :name`, { name: 'Alice' });

console.log(result);

事务处理

事务处理可以确保一系列操作要么全部成功,要么全部失败。TypeORM 提供了事务管理的功能。

import { getConnection } from 'typeorm';

async function performTransaction() {
  const userRepo = getConnection().getRepository(User);
  const postRepo = getConnection().getRepository(Post);

  await getConnection().transaction(async transactionalEntityManager => {
    const user = new User();
    user.name = 'Alice';
    await transactionalEntityManager.save(user);

    const post = new Post();
    post.title = 'My First Post';
    post.content = 'This is my first post.';
    post.author = user;
    await transactionalEntityManager.save(post);
  });
}

performTransaction().catch(error => console.error(error));

迁移管理

TypeORM 提供了迁移管理功能,可以帮助你在开发过程中轻松地管理和更新数据库结构。

创建迁移文件
typeorm migration:create -n AddEmailToUser

这会在 src/migrations 目录下创建一个新的迁移文件。

编写迁移逻辑
import {MigrationInterface, QueryRunner} from "typeorm";

export class AddEmailToUser1632995728234 implements MigrationInterface {
    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "users" ADD "email" varchar NOT NULL`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "email"`);
    }
}
应用迁移
typeorm migration:run

性能优化

为了提高应用性能,可以采取以下措施:

批量插入
const users = [
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' },
  // ...
];

await userRepository.insert(users);
使用缓存

TypeORM 支持多种缓存策略,可以在一定程度上减少数据库访问次数。

import { Entity, Column, PrimaryGeneratedColumn, Cache } from 'typeorm';

@Entity()
@Cache()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}