iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 22
0
Modern Web

讀官網文件邊走邊學nest.js系列 第 22

Day 22-TypeORM(九) RelationQueryBuilder(下) & Refactor

昨天用更新User的兩個關聯資料為例,介紹RelationQueryBuilder,今天把UserService裡面全部用QueryBuilder改寫,另外值得提的是這樣做以後其實可以改注入entitymanager或是connection就可以了,兩者的優劣就可能就是個人偏好或是大家可以討論(應該是跟Performance有關吧!?)

注入Entitymanager

用QueryBuilder全部改寫,意謂不會再用到TypeORM的Repository API,我們可以只注入EntityManager,習慣寫SQL多一點的人適合。

修改app.service constructor部分

@Injectable()
export class UsersService {
    constructor(
        //@InjectRepository(User) // 注入 typeorm repository
        //private readonly userRepo: Repository<User>,

        // 注入EntityManager可以指定Connection
        // 會用default connection,多個connection要參考nestjs官網
        @InjectEntityManager()
        private readonly em: EntityManager,
        private depService: DepartmentService, 
        private roleService: RolesService, 
      ){}

...
}

app.module不用修改

這下關於userRepo的地方下面會有紅色小蚯蚓,把useRep按F2 Refactor成em

還有一個要修改的地方是

// 會有紅色小蚯蚓
this.em.createQueryBuilder('u') 
// EntityManager必須要傳入Entity Type
this.em.createQueryBuilder(User, 'u')

使用EntityManager的好處是可以使用Transaction,未來有機會再介紹,簡單說可以實踐多個SQL Operation在一個Transaction

Refactor getUserById

async getUserById(userId): Promise<User>{
        // 載入roles導覽屬性
        // 設定eager=true後要把dep拿掉,重複載入SQL語法錯誤
        //return await this.userRepo.findOne(id, {relations: ['dep', 'roles']});
        // return await this.userRepo.findOneOrFail(id); // 以id搜尋,沒找到會丟出例外
    return await this.em
                    .createQueryBuilder(User, 'u')
                    .leftJoinAndSelect('u.roles', 'r')
                    .leftJoinAndSelect('u.dep', 'd')
                    .whereInIds(userId)
                    .select([
                      'u.id',
                      'u.username',
                      'u.email',
                      'd.id',
                      'd.depName',
                      'r.id',
                      'r.roleName',
                    ])
                    .getOne(); // 單筆使用getOne
}

Refactor addUser

async addUser(data: UserDTO): Promise<User>{
        const user = new User();

        user.username = data.username;
        user.email = data.email;
        
        // // user.depId  = data.depId; 不能只指定id,必須傳入物件
        // user.dep = await this.depService.getDepById(data.depId);
        // // 先要取得role物件陣列,再指給user物件下的roles,save時才會儲存關聯
        // user.roles = await this.roleService.getRolesByIds(data.roleIds);
        // return await this.userRepo.save(user); // 新增一筆user資料
        let userId;
        await this.em.createQueryBuilder()
                  .insert() // 不接受任何引數
                  .into(User) //
                  .values(user) // 先更新關聯以外的欄位
                  .execute() // 必須execute才會產生SQL送到DB
                  .then(async (result) => {
                    Logger.log(result); // 到console看回傳的格式
                    userId = result.identifiers[0].id; // 取得新增後回傳的id
                    // 以下更新關聯資料
                    await this.em.createQueryBuilder()
                    .relation(User, 'roles')
                    .of(userId)
                    .add(data.roleIds)
                    .then(async () => {
                      await this.em.createQueryBuilder()
                                 .relation(User, 'dep')
                                 .of(userId)
                                 .set(data.depId);
                    });
                  });

        return this.getUserById(userId);
      }

Refactor deleteUser

async deleteUser(id){
    const userDeleted = this.getUserById(id); // 先把原本的User存起來
    return this.em.createQueryBuilder()
               .delete()
               .from(User)
               .whereInIds(id) //指定id for delete
               .execute()
               .then(result => userDeleted); // 回傳raw沒有資料
}

用RelationQueryBuilder取得關聯物件

昨天少介紹到載入關聯物件,使用RelationQueryBuilder中的loadMany或是LoadOne方法

async updateUserById(userId, data: UserDTO){
    // const user = await this.userRepo.findOne(userId, {relations: ['roles']});
    const roles = await this.em.createQueryBuilder()
                            .relation(User, 'roles')
                            .of(userId)
                            .loadMany(); // 取得roles物件陣列

使用postman測試

cache

取得user list的時候可以使用cache(Repository API也有),以QueryBuilder改寫getUsers


async getUsers(pageInfo: UserQueryDTO): Promise<User[]>{
         return await this.em
                        .createQueryBuilder(User, 'u')
                        .leftJoinAndSelect('u.roles', 'r')
                        .leftJoinAndSelect('u.dep', 'd')
                        .select([
                          'u.id',
                          'u.username',
                          'u.email',
                          'd.id',
                          'd.depName',
                          'r.id',
                          'r.roleName',
                        ])
                        .orderBy('d.depName', 'ASC')
                        .addOrderBy('u.username')
                        .skip((pageInfo.page - 1) * pageInfo.pageSize)
                        .take(pageInfo.pageSize) // 取pageSize筆數
                        .cache(60000) // 1 min內
                        .getMany();
      }

使用postman測試

TypeORM還有最後一個值得分享是Listener跟有關embedded entity,再來就回到nestjs,database花了蠻多天....

Github source code


上一篇
Day21-TypeORM(八) RelationQueryBuilder(上)
下一篇
Day23-'TypeORM(十) Embedded Entity及Entity Listener& Subscriber
系列文
讀官網文件邊走邊學nest.js31

尚未有邦友留言

立即登入留言