在EggJS中使用Sequelize做联表查询[转载]

1 在控制器中设置路由

访问的地址是:http://localhost:3000/cats/profile

@Controller("cats")
export class CatsController {
  @Get("profile")
  findAll(): string {
    return "this is action";
  }
}

2 获取 get 参数

http://localhost:3000/cats/profile?debug=true&name=xiaohua

@Controller("cats")
export class CatsController {
  @Get("profile")
  findAll(@Query() request: Request): string {
    console.log(request); //{ debug: 'true', name: 'xiaohua' }
    return "this is action";
  }
}

3 获取 post 参数

post 请求
http://localhost:3000/cats/doit

import { Controller, Get, Query, Body, Post } from "@nestjs/common";
import { Request } from "express";

@Controller("cats")
export class CatsController {
  @Post("doit")
  findDoit(@Body() newData: Request): string {
    console.log(newData);
    return "this is post";
  }
}

获取到入参{ componentName: 'backtop', address: '北京' }

4 获取 ip

import { Controller, Get, Query, Ip } from "@nestjs/common";
import { Request } from "express";

@Controller("cats")
export class CatsController {
  @Get("profile")
  findAll(@Query() request: Request, @Ip() ip: Request): string {
    return JSON.stringify(request) + "--" + ip;
  }
}

5 设置响应头

import { Controller, Get, Query, Ip, Header } from "@nestjs/common";
import { Request } from "express";

@Controller("cats")
export class CatsController {
  @Get("profile")
  @Header("Cache-Control", "no-store") //设置响应头
  findAll(@Query() request: Request, @Ip() ip: Request): string {
    return JSON.stringify(request) + "--" + ip;
  }
}

6 设置动态路由

访问链接:http://localhost:3000/cats/123

import { Controller, Get, Param } from "@nestjs/common";

@Controller("cats")
export class CatsController {
  @Get(":id")
  findAll(@Param() params): string {
    return params.id; //123
  }
}

7 service 完整示例

service 端

service/cats/cats.service.ts

import {Injectable} from '@nestjs/common';
import {Cat} from '../../interface/cat.interface';

@Injectable()
export class CatsService {
	private readonly cats: Cat[] = [];
	create(cat: Cat) {
		this.cats.push(cat);
	}
	findAll(): Cat[] {
		return this.cats;
	}
}

interface/cat.interface.ts

export interface Cat {
  name: string;
  age: number;
  breed: string;
}

controller/cats/cats.controller.ts

import {Body, Controller, Get, Param, Post} from '@nestjs/common';
import {Cat} from '../../interface/cat.interface';
import {CatsService} from '../../service/cats/cats.service';
@Controller('cats')
export class CatsController {
	constructor(private catsService: CatsService) {}
	@Post('saveData')
	async create(@Body() catsData: Cat) {
		await this.catsService.create(catsData);
		return 'success';
	}

	@Get('info')
	async getAll() {
		return this.catsService.findAll();
	}
}

也就是 controller 设置的路由,由异步方式,调用的 service,由 service 处理服务器端

8 全局中间件

如果我们想一次性将中间件绑定到每个注册路由,我们可以使用由 INestApplication 实例提供的 use()方法:

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { logger } from "./middleware/loggerfun.middleware";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(logger);
  await app.listen(3000);
}
bootstrap();

注意使用的全局中间件是函数式中间件

export function logger(req, res, next) {
  console.log("req", req.originalUrl);
  next();
}

10 管道 pipe 的使用方法

pipe 用来过滤数据,还可以处理数据

Controller 文件

import {Body, Controller, Get, HttpException, HttpStatus, Post} from '@nestjs/common';
import {AppService} from './app.service';
import {SaveData} from './interface/saveData.interface';
import {ValidationPipe} from './pipe/validate.pipe';

@Controller()
export class AppController {
	constructor(private readonly appService: AppService) {}
	@Post('list')
	async saveList(@Body(new ValidationPipe()) saveData: SaveData) { //在入参 saveData 中设置了管道来进行拦截
		return this.appService.saveList(saveData);
	}
}

其中管道 validate.pipe 定义

import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
} from "@nestjs/common";

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = this.handleData(value);
    if (error) {
      throw new BadRequestException("Validation failed");
    }
    const newValue = Object.assign(value, {
      adddress: "北京",
    });
    return newValue; //可以返回修改的数据
  }
  handleData(value) {
    console.log(JSON.stringify(value)); //value可以拿到进行拦截的数据
    return {
      error: false,
    };
  }
}

对应的 app.service 文件,这里的入参 cat 已经是管道 pipe 处理后的数据了

import { Injectable } from "@nestjs/common";

@Injectable()
export class AppService {
  saveList(cat): string {
    return JSON.stringify(cat);
  }
}

11 关联表的使用

11.1 一对一

//目录
database;
---database.module.ts; //module相当于index入口文件,在app.modules中引入import
---database.provider.ts; //初始化数据库,并且初始化数据库model的表关系
---models;
------article.ts; //定义表模型
------location.ts; //定义表模型
------index.ts;

首先 article.ts 文件

import {Sequelize, Model, DataTypes} from 'sequelize';

class ArticleInfo extends Model {
	public readonly id!: number;
	public readonly user_id: number;
	public readonly title: string;
	public readonly full_name: string;
	public readonly other_name: string;
}

const defineArticleInfo = (db: Sequelize): void => {
	ArticleInfo.init(
		{
			id: {
				type: new DataTypes.INTEGER().UNSIGNED,
				primaryKey: true,
				autoIncrement: true,
			},
			user_id: {
				type: DataTypes.INTEGER,
				allowNull: false,
			},
			title: {
				type: DataTypes.STRING,
				allowNull: false,
			},
			full_name: {
				type: DataTypes.STRING,
				allowNull: false,
			},
			other_name: {
				type: DataTypes.STRING,
				allowNull: false,
			},
		},
		{
			sequelize: db,
			tableName: 'article_table',
			timestamps: false,
		}
	);
};

export {ArticleInfo, defineArticleInfo};

类似定了 location.ts 文件

import {Sequelize, Model, DataTypes} from 'sequelize';

class CatInfo extends Model {
	public readonly id!: number;
	public readonly name: string;
	public readonly age: number;
	public readonly address: string;
}

const defineCatInfo = (db: Sequelize): void => {
	CatInfo.init(
		{
			id: {
				type: new DataTypes.INTEGER().UNSIGNED,
				primaryKey: true,
				autoIncrement: true,
			},
			name: {
				type: DataTypes.STRING,
				allowNull: false,
			},
			age: {
				type: DataTypes.INTEGER,
				allowNull: false,
			},
			address: {
				type: DataTypes.STRING,
				allowNull: false,
			},
			type: {
				type: DataTypes.INTEGER,
				allowNull: false,
			},
		},
		{
			sequelize: db,
			tableName: 'location_table',
			timestamps: false,
		}
	);
};

export {CatInfo, defineCatInfo};

然后在 index 中定义了表的关系

import { Sequelize } from "sequelize";

import { CatInfo, defineCatInfo } from "./location";
import { ArticleInfo, defineArticleInfo } from "./article";

const initModels = (sequelize: Sequelize): void => {
  defineCatInfo(sequelize);
  defineArticleInfo(sequelize);
};
/**
 * 两个表:article+location;
 * article中user_id: 表示类型1、2、3
 * location中type[外键:foreignKey],表示1、2、3也就是对应article中user_id
 * 所以 location 是子表;article是父表
 * location.belongsTo(article)
 * article.hasOne(location)
 * eg
 * article表,父亲表:
 * id  |   title     |   user_id
 * 主键 | 文章标题信息  | 唯一类型,表示文章id
 *
 * location表,子表:
 * id  | other   | type
 * 主键 | 其他信息 | 外键,关联父亲表的user_id
 *
 *
 */
const initAssociations = () => {
  //相当于 location.belongsTo(article表,{})
  CatInfo.belongsTo(ArticleInfo, {
    foreignKey: "type",
    targetKey: "user_id",
    as: "leader", //定义查询父表的别名
  });
};
export { CatInfo, ArticleInfo, initModels, initAssociations };

然后 database.provider.ts 文件

import { Sequelize } from "sequelize";
// import {CatInfo, defineCatInfo} from './models/location';
// import {ArticleInfo, defineArticleInfo} from './models/article';

import { CatInfo, ArticleInfo, initModels, initAssociations } from "./models";

export const databaseProviders = [
  {
    provide: "SEQUELIZE",
    useFactory: async () => {
      let dbConfig = {
        host: "127.0.0.1",
        port: 3306,
        username: "root",
        password: "password",
        database: "my_location_demo",
      };

      const sequelize = new Sequelize({
        dialect: "mysql", //要链接数据库的语言
        host: dbConfig.host,
        port: dbConfig.port,
        username: dbConfig.username,
        password: dbConfig.password,
        database: dbConfig.database,
      });
      initModels(sequelize);
      initAssociations();

      return sequelize;
    },
  },
  {
    provide: "CatInfo",
    useValue: CatInfo,
    inject: ["sequelize"],
  },
  {
    provide: "ArticleInfo",
    useValue: ArticleInfo,
    inject: ["sequelize"],
  },
];

对应 database.module.ts 导出

import { Module } from "@nestjs/common";
import { databaseProviders } from "./database.providers";

@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

对应主 app.module.ts

import { Module, MiddlewareConsumer, NestModule } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { CatsController } from "./controller/cats/cats.controller";
import { CatsService } from "./service/cats/cats.service";
import { DatabaseModule } from "./database/database.module";

@Module({
  imports: [DatabaseModule], //这里
  controllers: [AppController, CatsController],
  providers: [AppService, CatsService],
})
export class AppModule {}

最后查询表方法:

async getTypeInfo() {
    return await this.catInfoModel.findAll({
        include: {
            model: this.ArticleInfoModel,
            as: 'leader',//定义查询父表的别名,需要在belongsTo定义表关系的时候定义
        },
    });
}

获取父表中某些属性

async getTypeInfo() {
    return await this.catInfoModel.findAll({
        include: {
            model: this.ArticleInfoModel,
            attributes: ['title', 'fullname'],
            as: 'leader',
        },
    });
}

可以看到使用 子表.belongsTo(父表,{}) ,生成的 json 数据是子表的数据包含了父表的数据

[
  {
    "id": 1,
    "name": "xiaohua",
    "age": 12,
    "address": "shanghai",
    "type": "1",
    "leader": {
      "title": "片段1",
      "fullname": "代码片段1"
    }
  }
]

要想父表包含子表的数据,就需要改变关联表结构,在文件 models/index.ts

const initAssociations = () => {
  ArticleInfo.hasOne(CatInfo, {
    foreignKey: "type",
    sourceKey: "user_id",
    as: "leader",
  });
};

service 调用数据库

async getTypeInfo() {
    return await this.ArticleInfoModel.findAll({
        include: {
            model: this.catInfoModel,
            as: 'leader',
        },
    });
}

生成的数据是,父数据包含了子数据:

[
  {
    "id": 1,
    "user_id": 1,
    "title": "片段1",
    "full_name": "代码片段1",
    "other_name": "代码1",
    "leader": {
      "id": 1,
      "name": "xiaohua",
      "age": 12,
      "address": "shanghai",
      "type": "1"
    }
  }
]

一对多关系

比如 1 个用户对应多个文章,定义

User.HasMany(File, {
	foreignKey: 'creator_id',	// 外键
	sourceKey: 'id',	// 源模型的关联键,默认主键,通常省略
}
/**
 * 两个表:article+location;
 * article中user_id: 表示类型1、2、3
 * location中type[外键:foreignKey],表示1、2、3也就是对应article中user_id
 * 所以 location 是子表;article是父表
 * location.belongsTo(article)
 * article.hasOne(location)
 * eg
 * article表,父亲表:
 * id  |   title     |   user_id
 * 主键 | 文章标题信息  | 唯一类型,表示文章id
 *
 * location表,子表:
 * id  | other   | type
 * 主键 | 其他信息 | 外键,关联父亲表的user_id
 *
 *
 */
const initAssociations = () => {
  ArticleInfo.hasMany(CatInfo, {
    //父亲有多个子表
    foreignKey: "type",
    sourceKey: "user_id",
    as: "leader",
  });
};

生成的数据

[
  {
    "id": 1,
    "user_id": 1,
    "title": "片段1",
    "full_name": "代码片段1",
    "other_name": "代码1",
    "leader": [
      {
        "id": 1,
        "name": "xiaohua",
        "age": 12,
        "address": "shanghai",
        "type": "1"
      },
      {
        "id": 4,
        "name": "lili",
        "age": 22,
        "address": "beijing",
        "type": "1"
      }
    ]
  }
]

从返回的数据中可以看出 hasMany 和 hasOne 的区别,

多对多

Student 对 Lession,中间表是 LessionStudent

student.js

// 与Lessison存在多对多关系,使用belongsToMany()
app.model.Student.belongsToMany(app.model.Lession, {
  through: app.model.LessionStudent,
  foreignKey: "studentId",
  otherKey: "lessionId",
});

Lession.js

Lession 对 Student,中间表是 LessionStudent

Lession.associate = function () {
  // 与student表是多对多关系
  app.model.Lession.belongsToMany(app.model.Student, {
    through: app.model.LessionStudent,
    foreignKey: "lessionId",
    otherKey: "studentId",
  });
};

中间表lession_student

const LessionStudent = app.model.define("lession_student", {
  lessionId: {
    type: INTEGER,
    primaryKey: true,
  },
  studentId: {
    type: INTEGER,
    primaryKey: true,
  },
});

LessionStudent.associate = function () {};

=============

1.EggJS 引用 Sequelize

1、安装 sequelize 依赖和 mysql 驱动

cnpm i egg-sequelize mysql2 -S

2、启用 sequelize 插件

在 config/plugin.js 里面添加

sequelize: {
enable: true,
package: 'egg-sequelize',
},

3、配置数据库

在 config/config.default.js 里面添加

config.sequelize = {
  dialect: "mysql", // 表示使用 mysql
  host: "127.0.0.1", // 连接的数据库主机地址
  port: 3306, // mysql 服务端口
  database: "demo", // 数据库名
  username: "root", // 数据库用户名
  password: "root", // 数据库密码
  define: {
    // model 的全局配置
    timestamps: true, // 添加 create,update,delete 时间戳
    paranoid: true, // 添加软删除
    freezeTableName: true, // 防止修改表名为复数
    underscored: false, // 防止驼峰式字段被默认转为下划线
  },
  timezone: "+8:00", // 由于 orm 用的 UTC 时间,这里必须加上东八区,否则取出来的时间相差 8 小时
  dialectOptions: {
    // 让读取 date 类型数据时返回字符串而不是 UTC 时间
    dateStrings: true,
    typeCast(field, next) {
      if (field.type === "DATETIME") {
        return field.string();
      }
      return next();
    },
  },
};

2.定义 Model

刚开始使用 egg-init 构建的 Egg 项目是没有 app/model 目录的,初始的项目结构如下:

itzishu
├── README.md
├── app
│ ├── controller
│ │ └── home.js
│ └── router.js
├── appveyor.yml
├── config
│ ├── config.default.js
│ └── plugin.js
├── package.json
└── test
└── app
└── controller
└── home.test.js

先在 app 目录下新建一个目录为 model,里面用来存放所有的数据库里面定义的表的实例对象内容。

数据库表的内容如下:

/_
Navicat Premium Data Transfer
Source Server : 系统数据库 3306
Source Server Type : MySQL
Source Server Version : 50725
Source Host : localhost:3306
Source Schema : demo
Target Server Type : MySQL
Target Server Version : 50725
File Encoding : 65001
Date: 12/05/2019 15:11:37
_/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

---

-- Table structure for classes

---

DROP TABLE IF EXISTS `classes`;
CREATE TABLE `classes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

---

-- Records of classes

---

BEGIN;
INSERT INTO `classes` VALUES (1, '软件工程 1601', '2019-05-12 13:11:43', '2019-05-12 13:11:47', NULL);
INSERT INTO `classes` VALUES (2, '网络工程 1601', '2019-05-12 13:12:10', '2019-05-12 13:12:13', NULL);
COMMIT;

---

-- Table structure for info

---

DROP TABLE IF EXISTS `info`;
CREATE TABLE `info` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`age` int(11) NOT NULL,
`sex` tinyint(255) NOT NULL DEFAULT '1' COMMENT '1 为男,0 为女',
`studentId` int(11) NOT NULL,
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;

---

-- Records of info

---

BEGIN;
INSERT INTO `info` VALUES (1, '许仙', 23, 1, 1, '2019-05-12 13:25:58', '2019-05-12 13:26:01', NULL);
INSERT INTO `info` VALUES (2, '白素贞', 20, 0, 2, '2019-05-12 13:26:41', '2019-05-12 13:26:46', NULL);
INSERT INTO `info` VALUES (3, '法海', 22, 1, 3, '2019-05-12 13:27:20', '2019-05-12 13:27:22', NULL);
INSERT INTO `info` VALUES (4, '小青', 18, 0, 4, '2019-05-12 13:27:48', '2019-05-12 13:27:51', NULL);
INSERT INTO `info` VALUES (5, '金如意', 20, 0, 5, '2019-05-12 13:28:34', '2019-05-12 13:28:37', NULL);
INSERT INTO `info` VALUES (6, '景松', 23, 1, 6, '2019-05-12 13:30:07', '2019-05-12 13:30:10', NULL);
COMMIT;

---

-- Table structure for lession

---

DROP TABLE IF EXISTS `lession`;
CREATE TABLE `lession` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

---

-- Records of lession

---

BEGIN;
INSERT INTO `lession` VALUES (1, '计算机网络', '2019-05-12 13:12:32', '2019-05-12 13:12:35', NULL);
INSERT INTO `lession` VALUES (2, 'Java 程序设计', '2019-05-12 13:12:50', '2019-05-12 13:12:52', NULL);
INSERT INTO `lession` VALUES (3, '软件项目管理', '2019-05-12 13:13:07', '2019-05-12 13:13:10', NULL);
INSERT INTO `lession` VALUES (4, '网络安全', '2019-05-12 13:13:22', '2019-05-12 13:13:25', NULL);
COMMIT;

---

-- Table structure for lession_student

---

DROP TABLE IF EXISTS `lession_student`;
CREATE TABLE `lession_student` (
`lessionId` int(11) NOT NULL,
`studentId` int(11) NOT NULL,
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
PRIMARY KEY (`lessionId`,`studentId`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

---

-- Records of lession_student

---

BEGIN;
INSERT INTO `lession_student` VALUES (1, 1, '2019-05-12 13:20:35', '2019-05-12 13:20:40', NULL);
INSERT INTO `lession_student` VALUES (1, 2, '2019-05-12 13:20:51', '2019-05-12 13:20:53', NULL);
INSERT INTO `lession_student` VALUES (1, 3, '2019-05-12 13:21:02', '2019-05-12 13:21:05', NULL);
INSERT INTO `lession_student` VALUES (1, 4, '2019-05-12 13:21:15', '2019-05-12 13:21:19', NULL);
INSERT INTO `lession_student` VALUES (1, 5, '2019-05-12 13:21:29', '2019-05-12 13:21:32', NULL);
INSERT INTO `lession_student` VALUES (1, 6, '2019-05-12 13:21:43', '2019-05-12 13:21:45', NULL);
INSERT INTO `lession_student` VALUES (2, 1, '2019-05-12 13:23:10', '2019-05-12 13:23:13', NULL);
INSERT INTO `lession_student` VALUES (2, 3, '2019-05-12 13:23:28', '2019-05-12 13:23:31', NULL);
INSERT INTO `lession_student` VALUES (2, 4, '2019-05-12 13:23:40', '2019-05-12 13:23:43', NULL);
INSERT INTO `lession_student` VALUES (2, 5, '2019-05-12 13:23:54', '2019-05-12 13:23:57', NULL);
INSERT INTO `lession_student` VALUES (3, 1, '2019-05-12 13:24:21', '2019-05-12 13:24:24', NULL);
INSERT INTO `lession_student` VALUES (3, 4, '2019-05-12 13:24:39', '2019-05-12 13:24:42', NULL);
INSERT INTO `lession_student` VALUES (4, 2, '2019-05-12 13:24:59', '2019-05-12 13:25:03', NULL);
INSERT INTO `lession_student` VALUES (4, 6, '2019-05-12 13:25:12', '2019-05-12 13:25:15', NULL);
COMMIT;

---

-- Table structure for student

---

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`number` varchar(12) NOT NULL COMMENT '学号',
`password` varchar(32) NOT NULL,
`classId` int(11) NOT NULL,
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
PRIMARY KEY (`id`,`number`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;

---

-- Records of student

---

BEGIN;
INSERT INTO `student` VALUES (1, '160101', '202cb962ac59075b964b07152d234b70', 1, '2019-05-12 13:16:09', '2019-05-12 13:16:12', NULL);
INSERT INTO `student` VALUES (2, '160201', '202cb962ac59075b964b07152d234b70', 2, '2019-05-12 13:16:32', '2019-05-12 13:16:35', NULL);
INSERT INTO `student` VALUES (3, '160102', '202cb962ac59075b964b07152d234b70', 1, '2019-05-12 13:17:17', '2019-05-12 13:17:21', NULL);
INSERT INTO `student` VALUES (4, '160103', '202cb962ac59075b964b07152d234b70', 1, '2019-05-12 13:17:51', '2019-05-12 13:17:54', NULL);
INSERT INTO `student` VALUES (5, '160104', '202cb962ac59075b964b07152d234b70', 1, '2019-05-12 13:18:13', '2019-05-12 13:18:16', NULL);
INSERT INTO `student` VALUES (6, '160202', '202cb962ac59075b964b07152d234b70', 2, '2019-05-12 13:18:36', '2019-05-12 13:18:39', NULL);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

其中,各个表之间存在联系为:

  • student 与 info 存在一对一关系
  • classes 与 student 存在一对多关系
  • student 与 lession 存在多对多关系,中间表为 lession_student

3、根据数据表的结构,我们确定关系并写好 model 目录下相关文件

student.js

module.exports = (app) => {
  const { STRING, INTEGER } = app.Sequelize;

  const Student = app.model.define("student", {
    id: {
      type: INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    number: {
      type: STRING,
      allowNull: false,
    },
    password: {
      type: STRING(32),
      allowNull: false,
    },
    classId: {
      type: INTEGER,
      allowNull: false,
    },
  });

  Student.associate = function () {
    // 与Info存在一对多关系,所以是hasOne()
    app.model.Student.hasOne(app.model.Info, { foreignKey: "studentId" });
    // 与Classes存在多对一关系,所以使用belongsTo()
    app.model.Student.belongsTo(app.model.Classes, {
      foreignKey: "classId",
      targetKey: "id",
    });
    // 与Lessison存在多对多关系,使用belongsToMany()
    app.model.Student.belongsToMany(app.model.Lession, {
      through: app.model.LessionStudent,
      foreignKey: "studentId",
      otherKey: "lessionId",
    });
  };

  return Student;
};

info.js

module.exports = (app) => {
  const { STRING, INTEGER, BOOLEAN } = app.Sequelize;

  const Info = app.model.define("info", {
    id: {
      type: INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    name: {
      type: STRING(50),
      allowNull: false,
    },
    age: {
      type: INTEGER,
      allowNull: false,
    },
    sex: {
      type: BOOLEAN,
      allowNull: false,
      get() {
        if (this.getDataValue("sex")) {
          return "男";
        } else {
          return "女";
        }
      },
    },
    studentId: {
      type: INTEGER,
      allowNull: false,
    },
  });

  Info.associate = function () {
    app.model.Info.belongsTo(app.model.Student, {
      foreignKey: "studentId",
      targetKey: "id",
    });
  };

  return Info;
};

这里注意下,在 sex 字段中,有一个 get(){}方法,因为在数据表里面,sex 字段存了 1 或 0 ,1 为男 0 为女,为了直接返回"男"或"女",这里使用 get 方法在找到数据后先做了处理,那返回给调用的函数的数据就是我们设置的值

classes.js

module.exports = (app) => {
  const { STRING, INTEGER, BOOLEAN } = app.Sequelize;

  const Classes = app.model.define("classes", {
    id: {
      type: INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    name: {
      type: STRING(50),
      allowNull: false,
    },
    age: {
      type: INTEGER,
      allowNull: false,
    },
    sex: {
      type: BOOLEAN,
      allowNull: false,
      get() {
        if (this.getDataValue("sex")) {
          return "男";
        } else {
          return "女";
        }
      },
    },
    studentId: {
      type: INTEGER,
      allowNull: false,
    },
  });

  Classes.associate = function () {
    // classes与student是一对多关系,所以这里使用hasMany()
    app.model.Classes.hasMany(app.model.Student, {
      foreignKey: "classId",
      targetKey: "id",
    });
  };

  return Classes;
};

lession.js

module.exports = (app) => {
  const { INTEGER, STRING } = app.Sequelize;

  const Lession = app.model.define("lession", {
    id: {
      type: INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    name: {
      type: STRING,
      allowNull: false,
    },
  });

  Lession.associate = function () {
    // 与student表是多对多关系
    app.model.Lession.belongsToMany(app.model.Student, {
      through: app.model.LessionStudent,
      foreignKey: "lessionId",
      otherKey: "studentId",
    });
  };

  return Lession;
};

lession-student.js

module.exports = (app) => {
  const { INTEGER } = app.Sequelize;

  const LessionStudent = app.model.define("lession_student", {
    lessionId: {
      type: INTEGER,
      primaryKey: true,
    },
    studentId: {
      type: INTEGER,
      primaryKey: true,
    },
  });

  LessionStudent.associate = function () {};

  return LessionStudent;
};

总结一下 Model 定义的内容
针对 MYSQL 常用的字段类型
字段类型从 app.Sequelize 获取,对应名字如下

Sequelize.STRING // VARCHAR(255)
Sequelize.STRING(1234) // VARCHAR(1234)
Sequelize.STRING.BINARY // VARCHAR BINARY
Sequelize.TEXT // TEXT
Sequelize.TEXT('tiny') // TINYTEXT

Sequelize.INTEGER // INTEGER
Sequelize.BIGINT // BIGINT
Sequelize.BIGINT(11) // BIGINT(11)

Sequelize.FLOAT // FLOAT
Sequelize.FLOAT(11) // FLOAT(11)
Sequelize.FLOAT(11, 12) // FLOAT(11,12)

Sequelize.DOUBLE // DOUBLE
Sequelize.DOUBLE(11) // DOUBLE(11)
Sequelize.DOUBLE(11, 12) // DOUBLE(11,12)

Sequelize.DECIMAL // DECIMAL
Sequelize.DECIMAL(10, 2) // DECIMAL(10,2)

Sequelize.DATE // DATETIME 针对 mysql / sqlite, TIMESTAMP WITH TIME ZONE 针对 postgres
Sequelize.DATE(6) // DATETIME(6) 针对 mysql 5.6.4+. 小数秒支持多达 6 位精度
Sequelize.DATEONLY // DATE 不带时间.
Sequelize.BOOLEAN // TINYINT(1)

其他的数据库所允许的类型参考:数据类型
字段属性值

属性名 类型 默认值 说明 说明
type Any 数据类型
primaryKey Boolean false 主键
autoIncrement Boolean false 自增
allowNull Boolean false 是否允许为空
defaultValue Any 默认值
field String 字段名 自定义字段名
unique Any 约束

表与表的关联性

在 sequelize 中,表与表之间用代码来说存在三种关联关系:一对一,一对多,多对多

一对一

在该项目中,student 表和 info 表是存在一对一关系的,一个学生有一条专属信息。

在 student.js 中,使用了 hasOne()方法,第一个参数为关联的模型对象 Info,第二个参数为一个对象,其包含一个属性为 foreginKey 为对应的信息表中 studentId 字段

在 info.js 中,使用了 belongsTo()方法,第一个参数为关联的模型对象 Student, 第二个参数也是一个对象,其有两个属性,foreginKey 为 info 表中的"studentId"字段,第二个参数 targetKey 为 student 表中的"id"字段

总结: hasOne()和 belongsTo()第一个参数为本表关联的另外一个表的 Model 实例,第二个参数中,都有 foreginKey 属性,对 hasOne 来说,这个属性值是对方表与自己 Id 对应的字段,对 belongsTo 来说,这个属性值是本表上的与对方表 id 对应的字段名。belongsTo 比 hasOne 多了个 targetKey 属性,其为对方表的对应主键名

一对多

classes 与 student 是一对多的关系,一个班级有多个学生,多个学生组成一个班级。

在 student.js 中,使用了 belongsTo(),在 classes.js 中,使用了 hasMany(),发现 hasMany()与 belongsTo()所需要的参数是类似的,但是这里注意,hasMany()里面的 foreginKey 值是对方表的 classesId。结合第上面"一对一"的分析,我们可以总结出:

has 开头的方法中,foreginKey 属性值从对方的表上找,如果有 targetKey 的值则是自己的主键;
belongs 开头的方法中,foreginKey 属性值在自身表上找,targetKey 属性值则是对方表上

多对多

分析多对多关系,一个学生有多门课,一个课有多个学生,那我们可以用一个中间表 lession-student.js 做这个联系。

在 student.js 中,我们使用了 belongsToMany()方法,lession.js 文件中也是如此,通过该方法的参数内容,可以发现其多了一个 through 属性,其值是中间表的 Model 实例。根据上面的规律,belongs 开头的方法里面 foreginKey 找自己,otherKey 找其他,所以很容易理解。

总结: 在 Model 的实例里面,重写 Model 的 associate 方法,将关联的关系放到里面。
一对一的方法有:hasOne(Model, {foreignKey:对方,})和 belongsTo(Model,{foreignKey:自己,targetKey:对方})
一对多的方法有: hasMany(Model,{foreignKey:对方, targetKey:自己})和 belongsTo(Model,{foreignKey:自己,targetKey:对方})
多对多的方法有: belongsToMany(Model,{through:Model, targetKey:自己, otherKey:对方})

3.联表查询

一对一

在 controller 里面如下写

// 获取学生信息 通过一对多的联系
async info(){
const { ctx, app } = this;
let result = await app.model.Student.findAll({
include: {
model: app.model.Info
}
});
ctx.body = result;
}

获取到的值如下:

[
  // 第一个学生
  {
    "id": 1,
    "number": "160101",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 1,
    "createdAt": "2019-05-12 13:16:09",
    "updatedAt": "2019-05-12 13:16:12",
    "deletedAt": null,
    "info": {
      // 联表查到的信息
      "sex": "男",
      "id": 1,
      "name": "许仙",
      "age": 23,
      "studentId": 1,
      "createdAt": "2019-05-12 13:25:58",
      "updatedAt": "2019-05-12 13:26:01",
      "deletedAt": null
    }
  },
  // 第二个学生
  {
    "id": 2,
    "number": "160201",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 2,
    "createdAt": "2019-05-12 13:16:32",
    "updatedAt": "2019-05-12 13:16:35",
    "deletedAt": null,
    "info": {
      "sex": "女",
      "id": 2,
      "name": "白素贞",
      "age": 20,
      "studentId": 2,
      "createdAt": "2019-05-12 13:26:41",
      "updatedAt": "2019-05-12 13:26:46",
      "deletedAt": null
    }
  },
  {
    "id": 3,
    "number": "160102",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 1,
    "createdAt": "2019-05-12 13:17:17",
    "updatedAt": "2019-05-12 13:17:21",
    "deletedAt": null,
    "info": {
      "sex": "男",
      "id": 3,
      "name": "法海",
      "age": 22,
      "studentId": 3,
      "createdAt": "2019-05-12 13:27:20",
      "updatedAt": "2019-05-12 13:27:22",
      "deletedAt": null
    }
  },
  {
    "id": 4,
    "number": "160103",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 1,
    "createdAt": "2019-05-12 13:17:51",
    "updatedAt": "2019-05-12 13:17:54",
    "deletedAt": null,
    "info": {
      "sex": "女",
      "id": 4,
      "name": "小青",
      "age": 18,
      "studentId": 4,
      "createdAt": "2019-05-12 13:27:48",
      "updatedAt": "2019-05-12 13:27:51",
      "deletedAt": null
    }
  },
  {
    "id": 5,
    "number": "160104",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 1,
    "createdAt": "2019-05-12 13:18:13",
    "updatedAt": "2019-05-12 13:18:16",
    "deletedAt": null,
    "info": {
      "sex": "女",
      "id": 5,
      "name": "金如意",
      "age": 20,
      "studentId": 5,
      "createdAt": "2019-05-12 13:28:34",
      "updatedAt": "2019-05-12 13:28:37",
      "deletedAt": null
    }
  },
  {
    "id": 6,
    "number": "160202",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 2,
    "createdAt": "2019-05-12 13:18:36",
    "updatedAt": "2019-05-12 13:18:39",
    "deletedAt": null,
    "info": {
      "sex": "男",
      "id": 6,
      "name": "景松",
      "age": 23,
      "studentId": 6,
      "createdAt": "2019-05-12 13:30:07",
      "updatedAt": "2019-05-12 13:30:10",
      "deletedAt": null
    }
  }
]

一对多

// 获取班级名为 软件工程 1601 的班级学生
async student(){
const { ctx, app } = this;
let result = await app.model.Classes.findAll({
where: {
name: '软件工程 1601'
},
include: {
model: app.model.Student
}
})
ctx.body = result;
}

获取数据如下:

[
  {
    "id": 1,
    "name": "软件工程1601",
    "createdAt": "2019-05-12 13:11:43",
    "updatedAt": "2019-05-12 13:11:47",
    "deletedAt": null,
    "students": [
      {
        "id": 1,
        "number": "160101",
        "password": "202cb962ac59075b964b07152d234b70",
        "classId": 1,
        "createdAt": "2019-05-12 13:16:09",
        "updatedAt": "2019-05-12 13:16:12",
        "deletedAt": null
      },
      {
        "id": 3,
        "number": "160102",
        "password": "202cb962ac59075b964b07152d234b70",
        "classId": 1,
        "createdAt": "2019-05-12 13:17:17",
        "updatedAt": "2019-05-12 13:17:21",
        "deletedAt": null
      },
      {
        "id": 4,
        "number": "160103",
        "password": "202cb962ac59075b964b07152d234b70",
        "classId": 1,
        "createdAt": "2019-05-12 13:17:51",
        "updatedAt": "2019-05-12 13:17:54",
        "deletedAt": null
      },
      {
        "id": 5,
        "number": "160104",
        "password": "202cb962ac59075b964b07152d234b70",
        "classId": 1,
        "createdAt": "2019-05-12 13:18:13",
        "updatedAt": "2019-05-12 13:18:16",
        "deletedAt": null
      }
    ]
  }
]

多对多

从学生获取课程信息

// 获取学生的选课内容
async lession(){
const { ctx, app } = this;
let result = await app.model.Student.findAll({
where:{
id: 1,
},
include: [
{model: app.model.Info},
{model: app.model.Lession}
]
});
ctx.body = result;
}

这里的话,注意 include 的值为一个数组了,这样可以多个联表获取数据

数据如下:

[
  {
    "id": 1,
    "number": "160101",
    "password": "202cb962ac59075b964b07152d234b70",
    "classId": 1,
    "createdAt": "2019-05-12 13:16:09",
    "updatedAt": "2019-05-12 13:16:12",
    "deletedAt": null,
    "info": {
      "sex": "男",
      "id": 1,
      "name": "许仙",
      "age": 23,
      "studentId": 1,
      "createdAt": "2019-05-12 13:25:58",
      "updatedAt": "2019-05-12 13:26:01",
      "deletedAt": null
    },
    "lessions": [
      {
        "id": 1,
        "name": "计算机网络",
        "createdAt": "2019-05-12 13:12:32",
        "updatedAt": "2019-05-12 13:12:35",
        "deletedAt": null,
        "lession_student": {
          "lessionId": 1,
          "studentId": 1,
          "createdAt": "2019-05-12 13:20:35",
          "updatedAt": "2019-05-12 13:20:40",
          "deletedAt": null
        }
      },
      {
        "id": 2,
        "name": "Java 程序设计",
        "createdAt": "2019-05-12 13:12:50",
        "updatedAt": "2019-05-12 13:12:52",
        "deletedAt": null,
        "lession_student": {
          "lessionId": 2,
          "studentId": 1,
          "createdAt": "2019-05-12 13:23:10",
          "updatedAt": "2019-05-12 13:23:13",
          "deletedAt": null
        }
      },
      {
        "id": 3,
        "name": "软件项目管理",
        "createdAt": "2019-05-12 13:13:07",
        "updatedAt": "2019-05-12 13:13:10",
        "deletedAt": null,
        "lession_student": {
          "lessionId": 3,
          "studentId": 1,
          "createdAt": "2019-05-12 13:24:21",
          "updatedAt": "2019-05-12 13:24:24",
          "deletedAt": null
        }
      }
    ]
  }
]

从课程获取选课学生:

// 获取某个课的参课学生
async lessionStudent(){
const { ctx, app } = this;
let result = await app.model.Lession.findAll({
where:{
name: '网络安全'
},
include: {
model: app.model.Student,
include: {
model: app.model.Info
}
}
});
ctx.body = result;
}

这里注意,在 include 的下面又有一个 include,第二个 include 是相对 Student 表的

数据如下:

[
  {
    "id": 4,
    "name": "网络安全",
    "createdAt": "2019-05-12 13:13:22",
    "updatedAt": "2019-05-12 13:13:25",
    "deletedAt": null,
    "students": [
      {
        "id": 2,
        "number": "160201",
        "password": "202cb962ac59075b964b07152d234b70",
        "classId": 2,
        "createdAt": "2019-05-12 13:16:32",
        "updatedAt": "2019-05-12 13:16:35",
        "deletedAt": null,
        "lession_student": {
          "lessionId": 4,
          "studentId": 2,
          "createdAt": "2019-05-12 13:24:59",
          "updatedAt": "2019-05-12 13:25:03",
          "deletedAt": null
        },
        "info": {
          "sex": "女",
          "id": 2,
          "name": "白素贞",
          "age": 20,
          "studentId": 2,
          "createdAt": "2019-05-12 13:26:41",
          "updatedAt": "2019-05-12 13:26:46",
          "deletedAt": null
        }
      },
      {
        "id": 6,
        "number": "160202",
        "password": "202cb962ac59075b964b07152d234b70",
        "classId": 2,
        "createdAt": "2019-05-12 13:18:36",
        "updatedAt": "2019-05-12 13:18:39",
        "deletedAt": null,
        "lession_student": {
          "lessionId": 4,
          "studentId": 6,
          "createdAt": "2019-05-12 13:25:12",
          "updatedAt": "2019-05-12 13:25:15",
          "deletedAt": null
        },
        "info": {
          "sex": "男",
          "id": 6,
          "name": "景松",
          "age": 23,
          "studentId": 6,
          "createdAt": "2019-05-12 13:30:07",
          "updatedAt": "2019-05-12 13:30:10",
          "deletedAt": null
        }
      }
    ]
  }
]
  1. 总结

用时 4 小时,调试加数据库设置,代码编写,查文档。允许我偷个懒,不想总结了,仔细阅读内容,基本上可以了解 Sequelize 在联表查询上的基本用法了

作者:中 v 中
链接:https://www.jianshu.com/p/078087c69b77
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2021-01-08 15:59  小猪冒泡  阅读(264)  评论(0编辑  收藏  举报