在 MidwayJS 中使用 TypeORM的进行数据持久化操作

简介: Midway是面向未来的一款支持Serverless的Node框架,其内置的web框架也是非常的好用,还有一些其他的特性,前后端一体等等。

Midway是面向未来的一款支持Serverless的Node框架,其内置的web框架也是非常的好用,还有一些其他的特性,前后端一体等等。

TypeORM是一款非常实用的 ORM框架,可以通过实体的操作来对数据库的数据进行读写,只要将实体定义出来之后就可以非常简便的操作数据库了


准备工作


Midway中对TypeORM做了定制化,首先安装依赖,这里需要安装数据库驱动和typeorm

$ npm i @midwayjs/orm typeorm --save
$ npm install mysql2 --save
复制代码

然后将orm引入configuration.ts中

import { Configuration } from '@midwayjs/decorator';
import * as orm from '@midwayjs/orm';
@Configuration({
  imports: [
    orm                             // 加载 orm 组件
  ],
  // ……
})
export class ContainerConfiguratin {
}
复制代码

在config.local.ts中添加数据库配置

export const orm = {
  type: 'mysql',
  host: '127.0.0.1',
  port: 3306,
  username: 'remote',
  password: '123456',
  database: 'your_db_name',
  synchronize: false,   // 如果第一次使用,不存在表,有同步的需求可以写 true
  logging: false,
};
复制代码


创建实体


所谓的实体就是一个对象,这里我们使用User实体来做示例,我们第一步只需要将实体的属性罗列出来

class User {
  id: number;
  username: string;
  phone: string;
  sex: number;
  email: string;
  createTime: Date;
}
复制代码

现在我们需要使用装饰器来修饰一下这个对象,让它变成框架可以是别的实体

  1. 在原生的TypeORM中,声明实体使用的是@Entity,在Midway中使用的是@EntityModel装饰器,可以更好地和Midway结合
  2. 使用@PrimaryColumn来声明主键(PrimaryGeneratedColumn可以声明自增主键);使用@Column声明普通列,可以声明列的类型(默认是varchar(255))、名称……;使用@CreateDateColumn@UpdateDateColumn可以声明创建时间个更新时间,这两个时间会自动生成,不需要我们额外去维护
@EntityModel('user') // 可以指定表名,不指定会根据类名自己生成
export class User {
  @PrimaryGeneratedColumn()
  id: number;
  @Column({
    length: 20, // 长度
    name: 'user_name' // 指定列名
  })
  username: string;
  @Column({
    nullable: true, // 是否可以为空
    length: 11
  })
  phone: string;
  @Column({
    default: 0, // 默认值
    length: 1
  })
  sex: number;
  @Column({
    length: 30,
    nullable: true,
  })
  email: string;
  @CreateDateColumn()
  createTime: Date;
}
复制代码

这样一个可以被TypeORM识别的简单实体类就完成了


数据库操作


在进行数据库的操作之前我们需要先注入Entity的Model,通过@InjectEntityModel注解来进行操作数据库

import { Provide, Inject } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/orm';
import { User } from './entity/user';
import { Repository } from 'typeorm';
@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModel: Repository<User>;
}
复制代码

现在已经注入了userModel,下面的所有的数据库操作都会基于这个userModel进行

新增数据库记录只需要创建一个实体类的实例,将属性值复制之后调用userModel的insert方法即可添加数据

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModel: Repository<User>;
  async saveUser(): Promise<User> {
    const user = new User();
    user.username = 'King';
    cosnt insertRes = await this.userModel.insert(user); // save方法也可以
    return insertRes;
  }
}
复制代码

如果实体中没有配置某一属性是否可以为空或者设置默认值,则必须添加一个值,否则会报错

查数据算是最复杂的一项,可以使用find和findOne等多种方法,find返回的只有一条记录,find会返回一个数组,即使只有一条记录被筛选出来。

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModel: Repository<User>;
  async selectUser(id: number): Promise<User> {
    cosnt user = await this.userModel.findOne({
      where: { id }
    });
    return user;
  }
  async selectUsers(id: number): Promise<User[]> {
    cosnt users = await this.userModel.find({
      where: { sex: 1 }
    });
    return users;
  }
}
复制代码

find还有很多操作项,为了不影响整体的阅读体验,会整理一个table放在后面

删除数据很简单,只需要将要删除的数据查出来再调用一下remove方法即可

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModel: Repository<User>;
  async deleteUser(id: number): Promise<User> {
    cosnt user = await this.userModel.findOne({
      where: { id }
    });
    const res = await this.userModel.remove(user);
    return res;
  }
}
复制代码

在开发中一般不建议将数据进行remove,更推荐的是使用标记位📌来区分数据的有效性,删除数据时将数据标记置为0等

改的思路同删除,首先要将实体查出来,然后修改实体的属性值,然后再保存

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModel: Repository<User>;
  async updateUser(id: number, username: string): Promise<User> {
    cosnt user = await this.userModel.findOne({
      where: { id }
    });
    user.username = username;
    const res = await this.userModel.update(user);
    return res;
  }
}
复制代码

find操作项

find选项 参数类型 功能
select string[] 类似于SQL语句中的select查询,返回结果的列只有标注的列
relations string[] 关系需要加载的主体
join {alias: string, leftjoinAndSelect: Record<string, string>} 为实体执行连接操作
where Record<string, any> | Record<string, any>[] SQL语句中的where条件,类型为数组时会使用OR连接不同的部分
order Record<string, 'ASC' | 'DESC'> 会根据指定的属性及排序方式进行排序,ASC是升序
skip number 偏移量(分页)
take number 得到的最大实体数(分页)
cache boolean 启用或禁用查询结果缓存
lock { mode: "optimistic", version: number | Date } | { mode: "pessimistic_read" | "pessimistic_write" | "dirty_read" } 启用锁查询。 只能在findOne方法中使用

TypeORM 提供了许多内置运算符,可用于创建更复杂的查询:

Not、LessThan、LessThanOrEqual、MoreThan、MoreThanOrEqual、Equal、Like、ILike、Between、In、Any、IsNull、Raw,以上的操作符都可以与Not搭配使用

示例

cosnt users = await this.userModel.find({
  where: { sex: Not(Equal(1)) }
});
复制代码


实体进阶


前面介绍了实体类的简单定义,真正的开发过程中实体之间会存在多种联系:一对一、一对多……,通过在实体中定义这些关系可以让我们更简单地进行连表查询

还是前面的User实体,我们这里再新加一个签到表的实体Attendance,我们通过不同的记录方式来分别介绍几种关系

一对一

一对一是所有的关系中最简单的一种关系,说白了就是传统数据库的外键的概念,在需要添加外键的实体生声明一个标识就可以了

这种实体对应的模式是一个用户对应一条签到记录,每次签到会更新记录,根据updateTime和attendanceDays来判断连续签到

export class Attendance {
  @PrimaryGeneratedColumn()
  id: number;
  @Column({
    default: 0,
  })
  attendanceDays: number;
  @UpdateDateColumn()
  updateTime: Date;
  @OnoToOne(type => User)
  @JoinColumn()
  user: User;
  /**
   * 可以声明级联保存,级联保存之后不再需要单独保存,
   * 在声明级联的一侧保存实体会自动保存另一方
   * @OneToOne(type => User, user => user.attendance, {
   *    cascade: true,
   * })
   * @JoinColumn()
   * user: User;
   */
}
复制代码

通过OnoToOne来声明一对一的关系,使用JoinColumn来添加外键,保存时需要将user实例赋值给attendance实例的user属性,然后再保存

async saveAttendance() {
  const user = new User();
  user.username = 'King';
  await this.userModel.save(user);
  const attendance = new Attendance();
  attendance.user = user;
  await this.attendanceModel.save(attendance);
}
复制代码

在查询时需要声明relations可以将两个表联合起来查出来

async findUserA(userId: number) {
  const userA = await this.attendanceModel.findOne({
    where: { id: userId },
    relations: ['user']
  })
}
复制代码

一对多/多对一

一对多和多对一是一个对应的关系,在一个实体中声明一对多的关系需要在对应的实体中声明多对一关系

这次我们要将签到表的模式换为每次签到生成一条签到记录,一个用户对应多条签到记录,一条签到记录只属于一个用户

export class Attendance {
  @PrimaryGeneratedColumn()
  id: number;
  @UpdateDateColumn()
  updateTime: Date;
  @ManyToOne(type => User, user => user.attendances)
  user: User;
}
复制代码

然后要在User中添加一对多关系

export class User {
  // ……
  @ManyToOne(type => Attendance, attendance => attendance.user)
  attendances: Attendance[];
}
复制代码

这种情况下会在attendance表中将user中的主键作为外键

多对多

多对多关系算是比较复杂的一种情况,再声明多对多关系之后会生成一张使用两个实体主键作为联合主键的表

这里我们使用用户表和权限表做示例,一个用户可以有多个角色,一个角色下对应多个用户,这两者就是多对多的关系

通过ManyToMany来声明两个实体之间的关系,通过JoinTable来表明关系的拥有者

export class Role {
  @PrimaryGeneratedColumn()
  id: number;
  @Column({
    default: 0
  })
  roleType: number;
  @ManyToMany(type => User, user => user.roles)
  users: User[]
  // 如果只需要在用户表中关联查role这里可以不声明,即为单向关系,申明双向关系之后可以在两个实体中查询
}
复制代码

在User实体中新增关系声明

export class User {
  // ……
  @ManyToMany(type => Role, role => role.users)
  @JoinTable()
  roles: Role[];
}
复制代码


QueryBuilder


QueryBuilder是 TypeORM 最强大的功能之一 ,它允许你使用优雅便捷的语法构建 SQL 查询,执行并获得自动转换的实体。

示例

let photos = await this.photoModel
    .createQueryBuilder("photo") // alias
    .innerJoinAndSelect("photo.metadata", "metadata") //relations & alias
    .leftJoinAndSelect("photo.albums", "album")
    .where("photo.isPublished = true")
    .andWhere("(photo.name = :photoName OR photo.name = :bearName)")
    .orderBy("photo.id", "DESC")
    .skip(5)
    .take(10)
    .setParameters({ photoName: "My", bearName: "Mishka" })
    .getMany();
复制代码

更多的QueryBuilder这里无法展开细说,看一下官网文档

相关文章
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
13774 1
|
Windows
Windows 10下安装Miniconda3
Windows 10下安装Miniconda3
2757 1
Windows 10下安装Miniconda3
|
JavaScript 前端开发
js中模糊搜索 模糊匹配如何实现?
js中模糊搜索 模糊匹配如何实现?
|
6月前
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
用array.filter()来实现数据筛选、数据清洗和链式调用,相对于for循环更加清晰,语义化强,能显著提升代码的可读性和可维护性。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
8月前
|
人工智能 开发者 C++
通义灵码流式补全性能优化场景DEMO
通义灵码流式补全性能优化DEMO展示了在处理大段代码时的高效展示方案。通过在VS Code中实现流式补全,模型可在500毫秒内开始展示代码,首包返回后逐行流式输出,大大减少了开发者的等待时间,提升了coding流畅度,让AI更好地适应开发者需求。
116 0
|
10月前
|
JSON 缓存 API
1688 商品详情数据接口(1688.item_get)
1688商品详情数据接口(1688.item_get)由阿里巴巴提供,旨在帮助开发者获取1688网站上的商品详细信息。开发者需先注册并创建应用获取API凭证,随后申请调用权限。接口通过必填与可选参数组合使用,如app_key、timestamp、fields等,以JSON格式返回商品详情,包括ID、名称、价格、库存等信息。
|
11月前
|
Java Maven Spring
用javadoc生成springboot的文档
本文介绍了生成Spring Boot项目JavaDoc文档的步骤,包括创建项目、在pom文件中添加maven-javadoc-plugin插件配置、执行Maven命令生成文档,以及查看生成的文档结果。
221 0
用javadoc生成springboot的文档
|
SQL Oracle 关系型数据库
SQL查询结果导出方法详析及实践指南
导出SQL查询结果是数据库管理中的一个重要环节。不同的数据库系统提供了各自的工具和方法来完成这项任务。选择合适的方法取决于具体的应用场景、数据规模和个人偏好。无论是在命令行中使用简单的SQL语句,还是通过GUI工具或编程语言实现自动化脚本,都有助于提高工作效率,简化数据管理流程。
|
Kubernetes Cloud Native API
轻量级容器管理工具 Containerd
轻量级容器管理工具 Containerd
|
JavaScript 安全 前端开发
原生JS实现一键复制,一键粘贴
原生JS实现一键复制,一键粘贴
220 0

热门文章

最新文章