mysql
也算是比较经典的免费的关系型数据库了,很多人都在用,且很好用,
MongoDB
是一个介于关系型和非关系型数据库之间,性能表现不错
由于主要学习数据库,我们就从最经典的
mysql
开始,使用
typeorm
映射的
mysql
mysql
、
MongoDB
SQL基础教程
(如果数据库基础不是很好,可以参考这里,我也是参考这里回忆起来的数据库操作)
ps
:数据库是不区分大小写的,因此我们设置字段时无需过分关注大小写,我们只要保持和数据库字段一致即可,另外传参时为了避免给客户端带来大小写的麻烦,最好采用
蛇形变量(_)
数据库工具仍然使用的是
Database Client
数据表 Entity
编写之前,注意引入
typeorm
和
mysql
,前面有介绍
yarn add typeorm @nestjs/typeorm mysql2
是我们用于映射到数据库字段的入口,可以理解为,声明模型等于建立数据库表格
下面定义一个基础用户表格,需要注意的是我们自定义的字段,一定要设置默认值 default
,否则可能会报错
ps
:存放小数时很多老手都是推荐存放字符串,否则不同平台不同编译环境,会出现意想不到的精度问题,虽然了解后在意料之中,避免小数直接用数字是最好的,如果有些需要用到小数,后面改没办法,可以以将该参数转化为字符串返回即可(用的地方多了会稍微麻烦点,但也确实是一个好方案)
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true, default: null })
account: string
@Column({default: null})
nickname: string
@Column({ unique: true, default: null })
wxId: string
@Column({ default: null })
age: number
@Column('simple-enum', { enum: [1, 2, 0], default: 0 })
sex: number
@Column({ default: null })
mobile: string
@Column('text', { default: null })
desc: string
@CreateDateColumn({ type: 'timestamp' })
createTime: Date
@UpdateDateColumn({ type: 'timestamp' })
updateTime: Date
数据表关系
数据表中有下面几种关系一对一、一对多、多对一、多对多
,并且他们的关系往往是双向存在的(除非单纯的包含关系,那样可以是单向,无法反向查询)
OneToOne、OneToMany、ManyToOne、ManyToMany
上面会发现我们 user表
没有密码等信息,为了演示一对一,这里额外建立了一个 author表
用它来隐藏我们的隐私信息,也方便管理了,查询时也无需隐藏了,方便了很多
可能有人说一对一影响查询速度,没有存在的必要,我想说,我们选择使用关系型数据库,不只是为了性能,更多是为了安全,为了处理繁杂的关系而存在,我们的很多表格都是根据社会分工等关系建立的,否则很多东西全混杂到一个表格那才叫难受
例如:用户表 -- 在职企业信息(假设一个用户只有一个企业) -- 学生信息 等,要不要建立一对一呢,还是揉到一起呢
下面我们用上面的用户表,将 user
和 auth
表关联起来,
给 user
表 设置 OneToOne
关联
@Entity()
export class User {
@OneToOne(() => Auth, auth => auth.user)
auth: Auth
给我们的 auth
表 设置 OneToOne
关联,使用@JoinColumn()
设置外键,如果想自定义外键名称可以传递参数
@Entity()
export class Auth {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 30, default: null })
password: string
@Column({ length: 20, default: null})
idCard: string
@OneToOne(() => User, (user) => user.auth)
@JoinColumn()
user: User
@Column({ default: null })
userId: number
需要注意的是,一定要在一个表格中设置 @JoinColumn()
,其意思是设置外键关联,会给设置的表格自动添加一个外键id
,并指向另外一个表格的 id
属性,
本案例给 auth
添加的外键,在数据库中,默认名字为 userId
, 即,默认外键名称为: 参数 + Id
,并且,我们赋值时,需要给设置外键的auth
表的关联属性user
赋值,这样就会自动取另一个对象user
的id属性
会赋值给我们的外键 userId
保存到数据库中
auth.user = user;
await this.authRepository.save(auth);
为什么给 author
添加外键呢,我们user
是一个重要的表格,又可以称为主表,auth
作为一个子表扩展是比较合理的(ps:外键是用于和其他表建立关系扩展功能用的,因此又叫做子表、关联表)
上面会发现我们我们建立关联时,给外键表格 auth
额外增加了一个 userId
字段,前面说了,这个字段即使不声明数据库也会存在,我们设置他也是为了获取数据时,额外获取一个userId
,某些时候只需要对比 id
,则可以不用联表查询了,另外建立关联的逻辑 是 给外键赋予另一个表的 id
,所以,我们也可以直接给该 id
直接赋值建立关系(一定要确认另一个表的 id 存在哈)
auth.userId = user.id;
await this.authRepository.save(auth);
看看我们建立的数据库表格吧
一对多/多对一
这个也是我们常用的功能,我们就以掘金为例,一个用户可以编写多篇文章,每篇文章只会对应一个用户
,这就是典型的一对多/多对一
,他们两个实际上是一样的,只不过从哪个角度看,一个是多对一、另一个必然是一对多
就以掘金的文章和用户为例,建立 一对多/多对一
关系表
需要注意
的是,一对多/多对一
的外键
一定是建立在 多对一
的一方,因为数据多的那方只需要设置一个外键,即可关联另外一个表格,而另一个表格获取多个时,只需要将外键和自己匹配的全部获取即可
给文章 article
表 添加 多对一
,并添加外键
@Entity()
export class Article {
@PrimaryGeneratedColumn()
id: number
@Column({ default: null })
title: string
@Column({ default: null })
desc: string
@Column('mediumtext', { default: null })
content: string
@Column('simple-enum', {
enum: [ArticleStatus.editing, ArticleStatus.waiting, ArticleStatus.checking, ArticleStatus.success, ArticleStatus.failure],
default: ArticleStatus.editing,
status: ArticleStatus
@ManyToOne(() => User, user => user.articles)
@JoinColumn()
user: User
@Column({ default: null })
userId: number
@Column({ default: false, select: false })
isDelete: boolean
给 user 表添加一对多关联
@Entity()
export class User {
@OneToMany(() => Article, article => article.user)
articles: Article[]
多对多
就一种,我们还是以掘金的文章和表格为例,一篇文章可以被多个不同的人收藏、一个人可以收藏多篇文章
,这就是典型的一对多
多对多
没有那么多要求,两个都是多对多,我们的关联参数都只能是数组,无法设置外键,因此我们就要使用 @JoinTable()
新建立一个关系表格了(就是单纯的 article、user的id建立关系表)
@Entity()
export class User {
@ManyToMany(() => Article, article => article.collects)
collects: Article[]
这里我就以文章为主(多对多不分主次表,但使用的位置不同生成的数据库默认名称不同哈),在里面使用 @JoinTable()
建立关联表格
@Entity()
export class Article {
@Column({ default: 0 })
collectCount: number
@ManyToMany(() => User, user => user.collects)
@JoinTable()
collects: User[]
这里在 article
的 collects
上创建的关联 user
的关系表,因此系统给我们的关系表默认起的名字也很中规中矩,article_collects_user
,参数分别为 articleId
、userId
使用 @JoinTable()
时, 我们一般无需自定义名称,毕竟本来就很清晰了(如果我们真的有强迫症,需要改名字改表格,外键也要驼峰式,与@JoinColumn()
类似),如下所示
@JoinTable({
name: 'article_collects_user',
joinColumns: { name: 'article_id' },
inverseJoinColumns: { name: 'user_id' },
看看我们新建立的关系表吧
ps
:JoinTable虽然是最后出现,但一定是多对多才使用的么,可以思考一下
常用增删改查
我们的增删改查都是用的 typeorm
,因此要参考他的文档进行学习和使用,具体的我们可以在使用时点进方法,相信能看到我们想要的
常用的增删改查,我们就介绍 nestjs
给的 Repository
方法直接进行操作
本篇后面也会介绍,带有部分数据库查询语句的 QueryBuilder, 对复杂操作对部分基础查询和查询进行优化
官网、中文网
service编写前的配置
前面讲过,我们的逻辑基本上都是在 service
中编写的,因此 service
中也要进行基本配置,需要哪个模块引入那个模块即可
@Injectable()
export class AuthService
{
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
@InjectRepository(Auth)
private authRepository: Repository<Auth>,
@InjectRepository(User)
private featureRepository: Repository<Feature>,
@InjectRepository(Article)
private articleRepository: Repository<Article>,
另外我们使用时,需要引入其他模块的的数据库 Entity
和 Service
(我的service一开始也没多引用,好像也没事哈,但是数据不引用就会出问题,最好都引用了)
@Module({
imports: [
TypeOrmModule.forFeature([User]),
TypeOrmModule.forFeature([Auth]),
TypeOrmModule.forFeature([Article]),
controllers: [UserController],
providers: [
UserService,
AuthService,
ArticleService,
增加和改动我们都是直接使用的 save
、insert
、更新也可以使用 update
,save
最方便实用
ps
:需要注意的是,新增、保存
的时候会自动调用查询
,不然参数中的 id 和 保存的信息是获取不到的,同时也可能会引出查询的错误,定位错误的时候需要注意
let auth = new Auth();
auth.password = loginInfo.password;
await this.authRepository.save(auth);
await this.authRepository.insert(auth);
await this.authRepository.save([auth]);
await this.authRepository.insert([auth]);
更新和增加不太一样,一般需要先查询,后更改,方便返回数据,其中 update
是不会返回我们的新数据的,另外手动赋值最累,效果有时候最理想
因为save
保存会自动额外调用查询,所以 update
单纯从效率上来说要高于 save
一些,可以看情况使用
因此如果想直接更新,不需要返回内容,可以直接使用 update + 查询条件 + 内容
即可,还能提升效率
async updateUser(
userInfo: UserUpdateDto,
user: User,
user = await this.userRepository.findOneBy({
id: user.id
if (!user) {
return ResponseData.fail('该用户不存在')
if (userInfo.age) user.age = userInfo.age;
if (userInfo.mobile) user.mobile = userInfo.mobile;
if (userInfo.nickname) user.nickname = userInfo.nickname;
if (userInfo.sex) user.sex = userInfo.sex;
await this.userRepository.save(user)
return user
假如需要统一格式,赋予空时,可能传递为空字符串
或者 null
,如果想统一数据库格式,无论是手动赋值,还是update,那么最好手动处理一下数据,再更新
delete
直接根据某个参数删除, remove
根据 entity
删除,一般直接用 delete
await this.userRepository.delete({
account: loginInfo.account
查询(条件、排序、分页)
查询是用的最多的,也是最杂的,findOneBy
、findOne
、findBy
、find
、findAndCountBy
、findAndCount
这些也是用的最多的
by
系列的都是根据 entity
字段直接查询的,因此不具备条件、关系查询
;
findOne
系列查询一个,返回对象
find
系列查询多个,返回数组
findAndCount
系列查询多个的同时,统计总数量,结果返回二维数组,第一个参数为查询数组内容,第二个为统计总数量
by
系列查询
let user = await this.userRepository.findOneBy({
account: loginInfo.account,
非 by
系列条件关系查询,where
、relations
、order
(包含主次关系排序)
let user = await this.userRepository.findOne({
where: {
account: "admin",
relations: {
auth: true,
order: {
createTime: 'DESC',
分页查询 skip
、take
let articles = await this.articleRepository.find({
skip: 1,
take: 10,
或查询,即:多个 where 查询条件取或
let user = await this.userRepository.findOne({
where: [{
account: 'admin',
age: 20
关联表条件查询(对关系表中的数据筛选),直接嵌套一层关联对象条件即可
let user = await this.userRepository.findOne({
where: {
account: "admin",
auth: {
password: '123456',
relations: {
auth: true,
这里只介绍一部分,如果缺少,可以参考数据库的一些,另外可以使用一个,点进去看看支持哪些即可
Equal
:是否相等,我们默认的筛选虽然也可以,但是无法查询为 null 的参数(如果参数为 null
那么默认的对比,会认为没有该筛选条件
,即:如果全为 null,则返回数据库第一条数据或者全部数据),因此要上 Equal
,Equal(null)
相当于 IS NULL
,数据库没有 =NULL
操作
let account = ...
let user = await this.userRepository.findOne({
where: {
account: Equal(account)
Not
: Not("admin")
!=
注意:Not 不是 == 的对立关系,== 对立关系为 != 和 IS NULL
,即 == 对立关系为 Not(...) 和 IsNull()
LessThan
:LessThan(10)
<
LessThanOrEqual
:LessThan(10)
>=
MoreThan
:LessThan(10)
>
MoreThanOrEqual
:LessThan(10)
>=
Like
:Like("%mm_dd%")
模糊查询 LIKE
,区分大小写,%
为匹配多个,_
匹配一个
ILike
:ILike("%mm_dd%")
模糊查询 ILIKE
,不区分大小写,%
为匹配多个,_
匹配一个
Between
:Between(1, 10)
介于 1~10 之间 BETWEEN...AND
[1, 10]
,后面一般是开区间,mysql是两端闭区间
In
:In([1, 2, 3])
为包含关系 IN
,包含一个符合条件
IsNull
:IsNull()
相当于Equal(null)
,查询为空时的操作 IS NULL
(数据库中没有=NULL
的操作)
Raw
:Raw("age+10=maxAge")
使用数据库语句作为判断,但参数不能使用用户输入,避免注入攻击
注意:很多数据库不区分大小写
(mysql也是),且 mysql 中 LIKE、ILIKE 没有区别的,因此 LIKE 和 ILike 查出来的结果一样都是不区分大小写,且可能因为 ILIKE 关键字报错
count
:统计有多少条,支持分页
countBy
:和count一样但直接查询,不支持排序等
sum
:单列求和
average
:单列求平均值
minimum
:单列求最小值
maximum
:单列求最大值
没有看到分组等功能,这个只能自己统计了
QueryBuilder 增删改查
中文网、官网
如果是日常的查询等功能,上面的就足够用了,对于一些比较比较复杂的查询,通过 QueryBuilder
可以简化代码
,或者减少查询操作提高效率
用的不多,可代替性很强,下面演示一下批量插入
await this.userRepository.
.createQueryBuilder()
.insert()
.into(User)
.values([{ account: "admin" }, { account: "admin1" }])
.execute();
直接更新某条数据,可代替性很强
await this.userRepository.
.createQueryBuilder()
.update(User)
.set({ account: "admin" })
.where("id = :id", { id: 1 })
.execute();
需要注意的是 from 和 where,别一不小心把全部的都删了哈
await this.userRepository.
.createQueryBuilder()
.delete()
.from(User)
.where("id = :id", { id: 1 })
.execute();
查询(重点)
先直接上一个普通的 where、relations
查询,也就是 where、leftjoin
,我们的 where
语句可以使用 AND
也可以直接 andWhere
(或就是 OR、orWhere
)
简单关联查询案例对比
先上一个默认的关联查询
await this.articleRepository.findOne({
where: {
id: body.id,
isDelete: false,
relations: {
user: true,
翻一下尝试一下,发现上面的更加清晰一些
let article = await this.articleRepository
.createQueryBuilder('article')
.leftJoinAndSelect('article.user', 'user')
.where('article.id=:id AND article.isDelete=:isDelete', {
id: body.id,
isDelete: false,
.getOne()
.where({
id: body.id,
isDelete: false,
中等关联条件查询案例对比
我们有这么一个需求,做一个收藏功能,需要判断某个用户是否收藏了某篇文章,再多一个表连接和查询条件,复杂了一点,发现也没问题,这次嵌套了关联查询条件
let article = await this.articleRepository.findOne({
where: {
id: body.id,
isDelete: false,
collects: {
id: user.id
relations: {
collects: true,
user: true,
if (!article) {
return '该文章不存在'
let isCollect = false
if (article.collects?.length > 0) {
isCollect = true
article.collects = undefined;
return {
...article,
isCollect
完美翻译,发现还是上面的更清晰,我们多添加了一列,发现还是很ok的,只是看着代码多了
let article = await this.articleRepository
.createQueryBuilder('article')
.leftJoinAndSelect('article.user', 'user')
.leftJoinAndSelect('article.collects', 'collect')
.where('article.id=:id AND article.isDelete=:isDelete', {
id: body.id,
isDelete: false,
.andWhere('collect.id=:userId', {
userId: user.id
.getOne()
if (!article) {
return '该文章不存在'
let isCollect = false
if (article.collects.length > 0) {
isCollect = true;
article.collects = undefined;
return {
...article,
isCollect
复杂关联且或混合查询案例对比
在看看下面即有且又有或
的例子,表现就不是那么优秀了,QueryBuilder
的灵活性
获得了大满贯
我们有这么一个需求,查询一篇文章,支持 id、status、内容
三者联合查询取交集
,但内容
需要根据 title、desc、content
模糊查询;搜用户昵称时,支持模糊查询,满足即可,与前面是或的关系
我们使用常用的查询查询一下,发现逻辑很复杂
let articles = await this.articleRepository.findAndCount({
where: [
id: body.id,
title: body.name && Like(`%${body.name}%`),
status: body.status && In(body.status),
id: body.id,
desc: body.name && Like(`%${body.name}%`),
status: body.status && In(body.status),
id: body.id,
content: body.name && Like(`%${body.name}%`),
status: body.status && In(body.status),
{
user: {
nickname: ILike(`%${body.nickname}%`),
relations: {
user: true
使用 QueryBuilder
改进,发现代码简洁了很多(需要注意的是可选参数的条件,没有时不应当存在)
let articles = await this.articleRepository
.createQueryBuilder('article')
.leftJoinAndSelect('article.user', 'user')
.where('article.id=:id AND article.status=:status', {
id: body.id,
status: body.status,
.andWhere('article.title=:name OR article.desc=:name OR article.content=:name', {
name: body.name
.orWhere('user.nickname LIKE :nickname', {
nickname: `%${body.nickname}%`,
.getManyAndCount();
前面介绍了基础的排序只支持两级(first、last),实际上可能有三级甚至更多,这是就要用到 builder 来进行多级排序了,如下所示就可以实现多级排序了(但如果出现了按照指定类型优先级排序问题,为了避免假分页的问题,可以将类型按照实际排序优先级设计大小,这样就可以更方便实现了),数据库也有更加详细的条件等排序可以搜索查看,这里 写的挺不错
let articles = await this.articleRepository
.createQueryBuilder('article')
.orderBy('article.status', 'DESC')
.addOrderBy('article.updateTime', 'DESC')
.addOrderBy('article.createTIme', 'ASC')
中文排序 CONVERT
有时候会按照中文排序,直接使用 order by 会发现,排序看起来毫无章法(可能是按照编码排序,因此和拼音没关系),因此需要用到专门的参数 CONVERT 来排序
let articles = await this.articleRepository
.createQueryBuilder('article')
.orderBy('CONVERT(article.name USING gbk)', 'ASC')
查询进阶(补充)
上面有提到查询进阶的内容,那是 typeorm
默认给定的,且发现 GROUP BY 分组
功能 和 HAVING 句尾连接
没有
这两个在基础封装没有,但是 QueryBuilder 中却是有的,不信尝试一下
GROUP BY: 分组统计,结合统计函数,对一列或者多列对结果集进行分组统计
SELECT 列名A,统计函数(列名B)
FROM 表
WHERE ..
GROUP BY 列名A
假设 A 为 名字,B为去过的地址,统计函数为Count,则可以统计某个人去到的地址数量
HAVING:前面的查询是默认可以使用 where
的,但是对于使用分组统计后的结果,却没有筛选条件,此时可以使用 HAVING
来对统计后的结果进行追加筛选
,HAVING
SELECT 列名A,统计函数(列名B)
FROM 表
WHERE ..
GROUP BY 列名A
HAVING 统计函数(列名B) 查询条件
假设 A 为 名字,B为去过的地址,统计函数为Count,查询条件为 > 10, 则可以统计某个人去到的地址数量超过10个的
关联表更新(重点)
你以为上面就结束了么,关联表更新的操作才是重中之重
,这个除了优化代码
,还是能优化效率
的
我们又有了一个简单的需求,文章需要支持收藏功能,根据传递的参数决定是否收藏
let article = await this.articleRepository.findOne({
where: {
id: body.id,
isDelete: false,
relations: {
collects: true,
if (!article) {
return ResponseData.fail('该文章不存在');
let userId = user.id
let index = article.collects.findIndex(e => e.id == userId)
let isCollect = index >= 0
if (body.is_collect && !isCollect) {
article.collects.push(user);
} else if (!body.is_collect && isCollect) {
article.collects.splice(index, 1)
await this.articleRepository.save(article)
return ResponseData.ok();
使用 QueryBuilder
改进,发现逻辑清晰了很多
let builder = this.articleRepository
.createQueryBuilder()
.relation(Article, 'collects')
.of(article)
if (body.is_collect == 1) {
try {
await builder.add(user)
} catch(err) {
if (err.errno === 1062) {
return 'ok'
}else {
let msg = err.msg
return err.msg
}else {
await builder.remove(user)
上面就是根据QueryBuilder
对关联表操作的优势,我们直接看看怎么用吧,其他的基本用不到了
this.articleRepository
.createQueryBuilder()
.relation(Article, 'collects')
.of(article)
时间区间查询
前面提到过between在mysql的问题,如果是开区间的,可以直接使用 querybuilder + 运算符来判断,能避免时间判断问题
let day = dayjs(datetime)
console.log(day.toDate(), day.add(1, 'day').toDate())
let articles = await this.articleRepository.createQueryBuilder('user')
.where('updateTime>=:start AND updateTime<:end', {
start: day.toDate(),
end: day.add(1, 'day').toDate()
.orderBy('user.updateTime', 'DESC')
.getManyAndCount()
数据库表操作
对于线上的功能,我们一般不会直接开启同步功能,因此会使用一些常用的表操作,然后更新 entity 表,以减少损失的可能性,下面介绍一些常见的表操作
CREATE TABLE user(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100)
删除表(谨慎操作)
DROP TABLE aaaaaa;
其中 ALTER TABLE
表示修改的表,后面是更改语句
ALTER TABLE user
ADD idcard VARCHAR(18);
修改列类型
ALTER TABLE user
MODIFY name VARCHAR(200);
ps
:这个里面删除 uni 等约束直接改完走同步即可,不必使用操作语句,添加约束涉及到内容不满足的会失败,可以走迁移逻辑😂
ALTER TABLE user
RENAME COLUMN idcard to mobile;
删除列(谨慎操作)
ALTER TABLE user
DROP COLUMN mobile;
多人开发与数据表更改注意
平时我们独立开发的时候,一般都是 mysql
的 synchronize: true
,并且 yarn start:dev
,这样个人开发是很舒服的
但团队开发时尽量不要这样,这个 synchronize
会直接将我们更改的 entity
更新同步到数据库,配合yarn start:dev
的及时更新,对于多人开发来说可能就是一个灾难(主要针对于对列的增改)
两个人同时连接一个远端数据库,一个人增加了entity 和数据库字段,另一个人还没来得及拉取,那么会出现没有拉取的人会把拉取的人的数据库字段更改同步没了(他那里并没有新增字段,因此可能会导致新增的一列数据全没了),甚至出现报错
对于删除影响不大,最多报个错,多个字段
而对于修改字段类型、修改名称,这一个简直是一个灾难片,系统无法进行精准判断,会直接删除该列,然后创建新的一列,这一操作会直接导致原有数据列直接清空,并且多人开发过程中,即使这边进行了正确操作,另外一边没有及时拉取,那边如果开启同步运行的话,会出现修改会原字段的情况
那么我们该如何操作呢
有人说直接使用命令通过数据库,将数据库字段映射成entity
,不开启synchronize
,但仍然会出现多人开发时出现的问题,会把自己新增或者修改的字段直接全部覆盖掉,因此仍然存在隐性问题,并且会降低开发速度,很不舒适
那怎么解决最好呢?
解决方案一
个人感觉,多人开发过程中,synchronize
可以关闭(synchronize:false
),然后每次想同步的时候,拉去一下最新代码(别人更新的代码),然后直接临时将 synchronize
开启(synchronize:true
),然后yarn start
运行,运行完之后会自动同步,同步后关了 synchronize
,再重新运行即可
解决方案二
在我们的 src
中建立下面文件,就起名叫 typeorm-config.ts
吧,缺点是需要手动导入 entities
中
export const config: TypeOrmModuleOptions = {
type: 'mysql',
host: ...,
port: ...,
username: ...,
password: ...,
database: ...,
entities: [User, Auth],
synchronize: false,
export const AppDataSource: any = new DataSource(config as DataSourceOptions);
然后增加脚本,每次想同步时,先拉去git代码更新,然后执行下面命令手动更新即可(yarn sync),需要注意的是 entity 引入外键的路径,要使用相对路径
"scripts": {
"sync": "ts-node ./node_modules/typeorm/cli -d ./src/typeorm-config.ts schema:sync"
存在上线应用的操作
当需要部署应用到线上时,并且我们的线上应用已经存在数据库的情况,我们需要对比使用命令手动操作数据库(一般对于修改列名
的操作比较谨慎,因此记录一下修改哪里了,记录下来),即可,即:对于修改列名,只需要先手动修改数据库列名,使其和entity
一致就行了,对于新增和删除的的这个不会有问题,直接手动 synchronize
就没问题了(方案一、二都没问题)
此外,对于存在线上数据库
,还有另外一个操作对于我们来说相对比较安全,基本上不存在上述的同步问题,那就是数据迁移
,当我们进行后续开发时,更改元数据表时,直接新增一个 entity,名字稍微改的不一样,例如:user -> user1
,后面可以理解为版本号,然后全改成使用 新 entity
就行了,这样对于线上的表格就不会有任何影响了,然后需要做的数据迁移也很简单,我们直接写一个数据迁移接口(脚本)
,手动取出原数据库中所有数据,然后映射保存到最新的数据表即可(内存不够大就分页迁移,假如一次迁移10w条),此时,如果还想用之前的用户表名,直接再反向操作一次即可
最后,为了谨慎,部署前我们最好备份
我们的数据库,避免出现其他意外情况,必要时可以恢复
对此你还有什么更好的方案呢,欢迎讨论
如果有一个功能, 一个用户可以上传多个图片信息,一个订单可以传递多个图片信息,一个表格同时多对一两个表格,这样的你怎样设计呢?(个人提示:关联表),你有什么更好的思路么?
这篇文章东西还挺多的,但是如果有了点数据库基础,那么就真的不多了,甚至这篇文章可以收藏当做一个问题参考点,看了这些基本上复杂一些的操作也可以做了
最后,祝大家学习愉快!