typeorm使用总结

总结一下typeorm常用的使用方法

entity

主键

typeorm中,每个entity必须有主键

  • 普通主键

    1
    2
    3
    4
    5
    @Entity()
    export class TunnelPart{
    @PrimaryGeneratedColumn()
    id: number;
    }
  • uuid主键

    1
    2
    3
    4
    5
    @Entity()
    export class BaseFileEntity {
    @PrimaryGeneratedColumn("uuid")
    id: string;
    }

    这样的主键,就是uuid

  • 多列主键

    1
    2
    3
    4
    5
    6
    7
    8
    @Entity()
    export class StructureTeam {
    @PrimaryColumn({ type: 'int' })
    teamId: number;

    @PrimaryColumn({ type: 'int' })
    structureId: number;
    }

    这样,就形成了一个有多列形成的主键

ManyToOne与oneToMany

ManyToOne 与 oneToMany是最常用的关系,两者可同时使用,ManyToOne可以单独使用,基本操作,如:保存、查询、级联删除等,放到后边下边来写,这里只写关系的建立

只建立ManyToOne

1
2
3
4
5
6
7
8
9
10
11
@Entity()
export class TunnelSection{
@PrimaryGeneratedColumn()
id: number;

@Column('int')
length: number;

@ManyToOne(type=>TunnelMethod)
method: TunnelMethod;
}

在ManyToOne的创建中,只提用一个参数即可,这个参数是一个箭头函数,指向One所对应的表
关系在建立的时候,可以指明一些参数,比如OnDetete,当用CASCADE时,可以用作级联删除。

1
2
@ManyToOne(type=>TunnelMethod, { onDelete: 'CASCADE' })
method: TunnelMethod;

同时建立ManyToOne 与 OneToMany

1
2
3
4
5
6
7
8
9
10
11
@Entity()
export class TunnelFixture{
@PrimaryGeneratedColumn()
id: number;

@Column('varchar', {length: 128})
name: string;

@OneToMany(type => TunnelProcedure, procedure => procedure.fixture)
procedures: TunnelProcedure[];
}
1
2
3
4
5
6
7
8
9
10
11
@Entity()
export class TunnelProcedure{
@PrimaryGeneratedColumn()
id: number;

@Column("varchar", {length: 128})
name: string;

@ManyToOne(type => TunnelFixture, fixture => fixture.procedures)
fixture: TunnelFixture;
}

在建立双向关系时,除了指明所在的entity,还要指明对方entity的属性

联合查询

relation查询

创建ManyToOne与OneToMany的关系以后,可以通过repository来查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;

@Column()
url: string;

@ManyToOne(type => User, user => user.photos)
user: User;
}

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

@Column()
name: string;

@OneToMany(type => Photo, photo => photo.user)
photos: Photo[];
}
1
2
3
4
5
const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["photos"] });

const photoRepository = connection.getRepository(Photo);
const photos = await photoRepository.find({ relations: ["user"] });

也可以用createQueryBuilder形式

1
2
3
4
5
6
7
8
9
10
11
const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.getMany();

const photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo")
.leftJoinAndSelect("photo.user", "user")
.getMany();

一直觉得relations只在findOne中,可用,看了官网,发现都可以。
有些情况下,都是自己创建id字段来连接另外一个表使用,这种情况下,只能使用createQueryBuilder,查询出来的是地卡尔乘积的结果,有些情况下,需要经过去重处理。
这里要注意leftJoinAndSelectleftJoin的区别。leftJoin不会查询出join表的字段

Raw查询

getRawMany()时,注意给列起别名,否则列名包括了表名。查询的数据不是entity时,采用raw方式查询。包括联表的自定义字段、SUM、COUNT等函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public async getMaterialList(subprojId:number, type?:string):Promise<Material[]>{
let param = Object.create(null);
param.subprojId = subprojId;

let condition = "material.subprojId=:subprojId "
if(!isNullOrUndefined(type)){
param.type = type
condition += " and material.type=:type"
}

return await this.materialRepos.createQueryBuilder("material")
.select("material.id", "id")
.addSelect("material.type", "type")
.addSelect("material.name", "name")
.addSelect("material.unit", "unit")
.addSelect("material.metaQuantityId", "metaQuantityId")
.addSelect("material.subprojId", "subprojId")
.addSelect("material.createAt", "createAt")
.addSelect('materialPrice.price', "price")
.leftJoin("material.price", "materialPrice")
.where(condition, param)
.getRawMany();
}

注意join都是迪卡尔积,会有重复。
OneToOne关系比较适合,不会用重复

区间查询

LessThan、MoreThan、Between

这里在repository中查询时,使用了以上区间函数;
同样可以使用createQueryBuilder的当时完成相同的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public async getMaterialPriceList(materialId:number, startDate?:string, endDate?:string){
let condition = Object.create(null);
condition.materialId = materialId;

if(startDate!=null && endDate==null){
condition.createAt = MoreThan(new Date(Date.parse(startDate)));
} else if(startDate==null && endDate!=null){
condition.createAt = LessThan(new Date(endDate));
} else if(startDate!=null && endDate!=null){
condition.createAt = Between(new Date(startDate), new Date(endDate));
}

return this.priceRepos.find({...condition});
}

用createQueryBuilder完成区间查询:

1
2
3
4
5
6
7
8
public async getMaterialPriceList(materialId:number, startDate?:string, endDate?:string){
return await this.priceRepos.createQueryBuilder("materialPice")
.where('materialPice.materialId = :materialId')
.andWhere('materialPice.createAt >= :startDate')
.andWhere('materialPice.createAt <= :endDate')
.setParameters({materialId:materialId, startDate:new Date(startDate),endDate:new Date(endDate)})
.getMany();
}

分页查询

同样是使用skip与take来完成

1
2
3
4
5
6
const users = await getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(5)
.take(10)
.getMany();

子查询

子查询是两个查询的嵌套,通常发生在where与from中

where中

1
2
3
4
5
6
7
8
9
10
11
12
const posts = await connection.getRepository(Post)
.createQueryBuilder("post")
.where(qb => {
const subQuery = qb.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery();
return "post.title IN " + subQuery;
})
.setParameter("registered", true)
.getMany();

或者写成

1
2
3
4
5
6
7
8
9
10
const userQb = await connection.getRepository(User)
.createQueryBuilder("user")
.select("user.name")
.where("user.registered = :registered", { registered: true });

const posts = await connection.getRepository(Post)
.createQueryBuilder("post")
.where("post.title IN (" + userQb.getQuery() + ")")
.setParameters(userQb.getParameters())
.getMany();

from中

1
2
3
4
5
6
7
8
9
10
const posts = await connection
.createQueryBuilder()
.select("user.name", "name")
.from(subQuery => {
return subQuery
.select("user.name", "name")
.from(User, "user")
.where("user.registered = :registered", { registered: true });
}, "user")
.getRawMany();

from中的“user“是别名,或者写成

1
2
3
4
5
6
7
8
9
10
11
const userQb = await connection.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true });

const posts = await connection
.createQueryBuilder()
.select("user.name", "name")
.from("(" + userQb.getQuery() + ")", "user")
.setParameters(userQb.getParameters())
.getRawMany();

全列查询

全表查询指的是对全部字段进行模糊查询,网上找资料看到一些方式,通过引入插件,再用函数的方式来实现,这样的实现在typeorm中很难实现。

  • 列少时,可以考虑多列的模糊

    select * from t where phonenum='digoal' or info ~ 'digoal' or c1='digoal'

  • 将所有字段记录在1列中,从一列中查询

  • pgsql中使用::text
    select * from structure where structure::text like %大河%

事务

1
2
3
4
5
import {getManager} from "typeorm";
await getManager().transaction(async transactionalEntityManager => {
await transactionalEntityManager.save(users);
await transactionalEntityManager.save(photos);
});

事物可以使用装饰器方式来书写:

1
2
3
4
@Transaction()
save(@TransactionManager() manager: EntityManager, user: User) {
return manager.save(user);
}
1
2
3
4
@Transaction()
save(user: User, @TransactionRepository(User) userRepository: Repository<User>) {
return userRepository.save(user);
}

可以在事务中指明隔离级别

migration

  • 需要注意其ormconfig.json配置文件的书写
  • 其原理是通过检测migration中的文件,来执行文件,并且在数据库中创建一个migrations的数据表,用来存储执行过的文件。在执行的时候,会对比文件夹中的文件与数据库执行过的文件,并选择new的进行执行。
  • 这种方式需要看一个revert如果执行。
  • migration其实是一种命令模式。