在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  小猪冒泡  阅读(366)  评论(0)    收藏  举报