zoukankan      html  css  js  c++  java
  • 在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
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    dedecms调用新增加的内容模型字段
    dedecms另一种单页制作方法
    dedecms文章列表加上序号效果
    解析DeDecms系统的SEO内部优化技巧
    织梦dedecms如何对列表添加判断语句
    织梦DEDE CMS文章列表arclist标签大全
    织梦dedecms 幻灯片 自定义设置
    当前标签dede[field:**] 标签调用说明
    织梦dedecms中的{dede:php}{/dede:php}的详细用法
    uva1628 Pizza Delivery
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/14251875.html
Copyright © 2011-2022 走看看