如何NestJS里像MyBatis那样使用SQL注解

1,107 阅读4分钟

最近用NestJS作为Web主力框架,积极拥抱TypeScript。奈何找不到一款类似MyBatis那样可以直接写SQL的数据库工具,而NestJS通常用的数据库工具都是一些很自定义语法的ORM框架,用着蛮别扭。不得已自己通过TS的装饰器写了一个类似MyBatis的注解工具——SpeedSQL,现分享给大家。

特性

  • 遵循NestJS的Module注入方式编写,内置mysql2库支持。
  • 使用TypeScript的装饰器,类似Java的注解,Java背景的使用者会比较熟悉。
  • 支持命名参数的方式,或者实体类的方式,进行绑定值。
  • 支持数据转实体POJO类。

看代码,以下是使用方法(后面有详解),是不是很眼熟啊:

import { Injectable } from '@nestjs/common';
import { Delete, Update, Param, ResultType, Insert } from 'speedsql';
import { UserDto } from './entity/user.dto';
import { ParamDto } from './entity/param.dto';

@Injectable()
export class AppService {
  @Update('update user set age = #{age} where name = #{name}')
  setUserAge(@Param('name') name: string, @Param('age') age: number): number {return;}

  @Delete('delete from user where name = #{name}')
  deleteUser(@Param('name') name: string): number {return;}

  @ResultType(UserDto)
  @Select('select `name`, `age` from `user` where `uid` = #{uid} and `name` = #{name}')
  getRecords(paramDto: ParamDto): UserDto[] {return;}

  @Insert('insert into user (name, age) value (#{name}, #{age})')
  addUser(user: UserDto): number {return;}
}

支持的注解(即TypeScript的装饰器)

@Param, @ResultType, @Select, @Insert, @Update, @Delete.

依赖安装

在你的package.json里面加入依赖。

"dependencies": {
    "speedsql": "latest"
}

NestJS内的配置和使用

1. 准备数据源和实体类

db.provider.ts

import { createPool, Pool } from 'speedsql';

export const DbProviders = [
  {
    provide: 'SPEED_POOL',
    useFactory: async (): Promise<Pool> => {
      return await createPool({
        host: 'localhost',
        user: 'root',
        port: 3306,
        password: 'qwer1234',
        database: 'test',
      });
    },
  },
];

entity/param.dto.ts

export class ParamDto {
  constructor(public name: string, public age: number) {}
}

entity/user.dto.ts

export class UserDto {
  constructor(public name: string, public age: number) {}
}

2. 作为Module导入到NestJS

app.mudule.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { SpeedService } from 'speedsql';
import { DbProviders } from './db.provider';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, SpeedService, ...DbProviders],
})
export class AppModule {}

3. 使用(跟MyBatis类似的注释)

在你的Service类里面,使用Select,Insert,Update,Delete等注解。

app.service.ts

import { Injectable } from '@nestjs/common';
import { Delete, Update, Param, ResultType, Insert } from 'speedsql';
import { UserDto } from './entity/user.dto';
import { ParamDto } from './entity/param.dto';

@Injectable()
export class AppService {
  @Update('update user set age = #{age} where name = #{name}')
  setUserAge(@Param('name') name: string, @Param('age') age: number): number {return;}

  @Delete('delete from user where name = #{name}')
  deleteUser(@Param('name') name: string): number {return;}

  @ResultType(UserDto)
  @Select('select `name`, `age` from `user` where `uid` = #{uid} and `name` = #{name}')
  getRecords(paramDto: ParamDto): UserDto[] {return;}

  @Insert('insert into user (name, age) value (#{name}, #{age})')
  addUser(user: UserDto): number {return;}
}

调用你的Service。

app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ParamDto } from './entity/param.dto';
import { UserDto } from "./entity/create-cat.dto";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  async getHello() {
    await this.appService.setUserAge("zzz", 20);
    return "hello world";
  }
}

参数绑定

参数绑定在@Select, @Insert, @Update, @Delete增删查改注解上都可以使用。

参数绑定有两种方式:(不能两种同时用在一个操作上面)

1. 参数实体类

我们可以通过建立一个实体类作为参数输入,这样挺面向对象的。

export class ParamDto {
  constructor(public name: string, public age: number) {}
}

然后在查询上面定义实体类参数

import { ResultType, Select } from 'speedsql';

@ResultType(UserDto)
@Select('select `name`, `age` from `user` where `uid` = #{uid} and `name` = #{name}')
getRecords(paramDto: ParamDto): UserDto[]{return;}

最后是使用

const users: UserDto[] = await this.appService.getRecords(
  new ParamDto("zzz", 10)
);

2. 命名参数方式

@Param注解定义了绑定参数。

和MyBatis类似,SpeedSQL支持命名的方式绑定参数,而不像mysql2库那样只能通过问号占位符。

import { ResultType, Select, Param } from 'speedsql';

@ResultType(UserDto)
@Select(select `name`, `age` from `user` where `uid` = #{uid} and `name` = #{name}')
getRecords(@Param('uid') uid:number, @Param('name') name:string): UserDto[]{return;}

使用方式:

const users: UserDto[] = await this.appService.getRecords('zzz', 10);

增删查改

@Select

SpeedSQL使用 @ResultType 来注解查询结果数据的实体类,它会返回一个该实体类的数组。

建立实体类:

export class UserDto {
  constructor(public name: string, public age: number) {}
}

然后查询

import { ResultType, Select, Param } from 'speedsql';

@ResultType(UserDto)
@Select('select `name`, `age` from user where uid = #{uid} and name = #{name} ')
getRecords(@Param('uid') uid:number, @Param('name') name:string): UserDto[] {return;}

如果返回数据的字段名与实体类的参数对应不上,那么可以通过SQL语法的AS来修正到一直。

import { ResultType, Select, Param } from 'speedsql';

@ResultType(UserDto)
@Select('select `realname` as `name`, `age` from user where uid = #{uid} and name = #{name} ')
getRecords(@Param('uid') uid:number, @Param('name') name:string): UserDto[] {return;}

@Insert

@Insert 也支持绑定参数。

@Insert 返回一个数值,就是新增记录的ID,当然也可以忽略。

import { Insert } from 'speedsql';

@Insert('insert into user (name, age) value (#{name}, #{age})')
addUser(user: UserDto): number {return;}

@Update 和 @Delete

@Update@Delete 也支持绑定参数。

它们也会返回一个数值,指代的是影响行数,可忽略。

import { Delete, Update, Param } from 'speedsql';

@Update('update user set age = #{age} where name = #{name}')
setUserAge(@Param('name') name: string, @Param('age') age: number): number {return;}

@Delete('delete from user where name = #{name}')
deleteUser(@Param('name') name: string): number {return;}

Issue

有任何问题或指教,请提Issue:github.com/SpeedPHP/sp…

感谢 NestJSmysql2MyBatis