在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
}
}
]
}
]
- 总结
用时 4 小时,调试加数据库设置,代码编写,查文档。允许我偷个懒,不想总结了,仔细阅读内容,基本上可以了解 Sequelize 在联表查询上的基本用法了
作者:中 v 中
链接:https://www.jianshu.com/p/078087c69b77
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。