typeorm使用总结

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

entity

主键

typeorm中,每个entity必须有主键

  • 普通主键

    @Entity()
    export class TunnelPart{
      @PrimaryGeneratedColumn()
      id: number;
    }
    
  • uuid主键

    @Entity()
    export class BaseFileEntity {
      @PrimaryGeneratedColumn("uuid")
      id: string;
    }
    

    这样的主键,就是uuid

  • 多列主键

    @Entity()
    export class StructureTeam {
        @PrimaryColumn({ type: 'int' })
        teamId: number;
    
        @PrimaryColumn({ type: 'int' })
        structureId: number;
    }
    

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

ManyToOne与oneToMany

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

只建立ManyToOne

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

    @Column('int')
    length: number;

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

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

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

同时建立ManyToOne 与 OneToMany

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

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

    @OneToMany(type => TunnelProcedure, procedure => procedure.fixture)
    procedures: TunnelProcedure[];
}
@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来查询

@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[];
}
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形式

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等函数

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的当时完成相同的操作。

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完成区间查询:

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来完成

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

子查询

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

where中

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();

或者写成

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中

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“是别名,或者写成

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 %大河%

事务

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

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

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

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

migration

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

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×