30

最近开始接触数据库,现在普遍用的都是Mysql数据库,简单的了解了一下sql语句,没有太深入的学习,然后就开始找相关的ORM框架,然后锁定了Sequelize,个人感觉很强大,搜索了一些文档,但是很让人费解,讲的每一部分都是那么的官方,不太容易理解,记录一下学习路程。本文档以koa+Sequelize进行编码测试。

准备工作

在尝试使用Sequelize之前先确认是否安装了Mysql数据库,安装node,这里使用的Mysql 5.6版本。

首先要创建一个项目执行命令如下:

mkdir 文件夹名称
cd 文件夹名称
npm init -y     // 直接略过所有问答,全部采用默认答案

在开始之前首先要安装相关依赖:

//  安装 sequelize koa mysql2
npm install --save-dev sequelize koa mysql2

注意:这里是mysql2

建立连接

创建main.js文件,分别引入koasequelize建立与mqsql数据库连接。在实例化Sequelize时需要配置一些参数,第一个接收的参数时数据库的名称,第二个是用户名,第三个是密码,第四项是连接mysql的相关配置。

const Koa = require("koa");
const Sequelize = require("sequelize");
const app = new Koa();
const mysqlConfig ={
    host: 'localhost',  //  接数据库的主机
    port: '3306',       //  接数据库的端口
    protocol: 'tcp',    //  连接数据库使用的协议
    dialect: 'mysql',   //  使用mysql
    pool: { 
        max: 5,         //  最大连接数量
        min: 0,         //  最小连接数量
        idle: 10000     //  连接空置时间(毫秒),超时后将释放连接
    },
    retry: {        //  设置自动查询时的重试标志
        max: 3          //  设置重试次数
    },
    omitNull: false,    //  null 是否通过SQL语句查询
    timezone: '+08:00'  //  解决时差 - 默认存储时间存在8小时误差
};
const sequelize = new Sequelize('aarontest', 'root', '123456',mysqlConfig );
app.listen(3000);

通过上述代码就已经与Mysql建立了连接。这个地方没有什么可以讲的,按照上述进行配置就可以了。test数据库不一定要存在不存在也是可以正常建立连接的。配置参数还有很多,这里只说了一些常用的,就不多赘述了。如果觉得上述方法比较麻烦可以通过也提供了其他方法进行连接:

const sequelize = new Sequelize('mysql://root:123456@localhost:3306/aarontest', {
    ...mysqlConfig
});

这种连接方式与mongoose连接方法差不多,以至于具体使用哪种方式还是要根据个人的编码习惯来决定。

建立model

sequelize是通过define方法建立模型的,Model相当于数据库中的表,该对象不能通过构造函数实例化,而只能通过sequelize.define()sequelize.import()方法创建。

const AaronTest = sequelize.define('project', {
  title: Sequelize.STRING,
  description: Sequelize.TEXT
})

通过上面的方法创建好model之后,使用Navicat(数据库可视化工具)仍然无法看到我们所创建的表,尴尬。。。sequelize提供了一个sync()方法可以同步模型到数据库。使用该方法的时候一定要注意所连接的数据库一定要存在否则会报错。

define方法接收三个参数,第一个参数为表名称,第二个为所需要创建的数据库字段,第三个参数是相关表配置。

const AaronTest = sequelize.define('project', {
  title: Sequelize.STRING,
  description: Sequelize.TEXT
});
AaronTest.sync();

这样model所创建的字段就被同步到了数据库中,同样相对应的表也被创建好了,需要注意通过这种方式同步的表会在表名称后面添加一个s作为复数。同步数据库是会默认添加两个字段createdAtupdatedAt有的时候可能不需要这两个字段。这个时候需要在第三个字段中添加timestampsfalse默认为true

const AaronTest = sequelize.define('project', {
  title: Sequelize.STRING,
  description: Sequelize.TEXT
},{
  timestamps: false
})

上面可以看出通过Sequelize.STRING设置当前字段的类型,Sequelize提供了很多数据类型供我们进行使用:

类型 说明
STRING 将字段指定为变长字符串类型,默认长度为 255。Sequelize.STRING(64)
CHAR 将字段指定为定长字符串类型,默认长度为 255。Sequelize.CHAR(64)
TEXT 将字段指定为(无)有限长度的文本列。可用长度:tiny, medium, long,Sequelize.TEXT('tiny')
INTEGER 32位整型,可用属性:UNSIGNED,ZEROFILL,Sequelize.INTEGER('UNSIGNED')
BOOLEAN 小数,接受一个或两个参数表示精度,Sequelize.BOOLEAN()
TIME 指定为时间类型列,Sequelize.TIME()
DATE 指定为日期时间类型列,Sequelize.DATE()
DATEONLY 指定为日期类型列,Sequelize.DATEONLY()
HSTORE 指定为键/值类型列,仅Postgres适用,Sequelize.HSTORE()
JSON 指定为JSON字符串类型列,仅Postgres适用,Sequelize.JSON()
JSONB 指定为预处理的JSON数据列,仅Postgres适用,Sequelize.JSONB()
NOW 一个表示当前时间戳的默认值,Sequelize.NOW()
UUID UUID类型列,其默认值可以为UUIDV1或UUIDV4,Sequelize.UUID()
ENUM 枚举类型,Sequelize.ENUM()
ARRAY 数组类型,仅Postgres适用,Sequelize.ARRAY()

设定model的时候在添加第三个参数可以对当前model进行二次设置,以使数据库更加强壮,常用字段如下

  • timestamps:不要添加时间戳属性 (updatedAt, createdAt)
  • paranoid:paranoid 属性只在启用 timestamps 时适用,不从数据库中删除数据,而只是增加一个 deletedAt 标识当前时间,我们常说的逻辑删除
  • underscored: 不使用驼峰式命令规则,这样会在使用下划线分隔,updatedAt的字段名会是 updated_at
  • freezeTableName:禁止修改表名. 默认情况下sequelize会自动使用传入的模型名(define的第一个参数)做为表名,如果你不想使用这种方式你需要进行以下设置
  • tableName:定义表名
  • createdAt:不想使用 createdAt
  • updatedAt:想 updatedAt 的实际名为'*'
  • deletedAt: 要将 deletedAt 设置为 destroyTime (注意要启用paranoid)

当创建model的时候可能需要有些附加属性,比如主键,自增,不能为null,默认值等等,可以在创建model的时候进行手动设置。

  • autoIncrement:是否自增
  • references:通过references选项可以创建外键
  • allowNull:设置 allowNull 选项为 false 后,会为列添加 NOT NULL 非空限制
  • defaultValue:设置默认值
  • type:字段类型
  • unique:添加唯一(unique)约束后插入重复值会报错,unique属性可以是boolean 或 string类型
  • primaryKey:设置为主键
  • comment:字段描述
  • field:指定数据库中的字段名

除了这些以外Sequelize在定义model的时候,还提供了一个validate属性,可以为添加的字段进行校验处理:

字段 说明 值类型
is 存储值必须满足正则 正则
not 除正则之外的值 布尔
isEmail 是否为邮箱 布尔
isUrl 检查Url格式 布尔
isIP 检查 IPv4 或 IPv6 格式 布尔
isIPv4 检查 IPv4 布尔
isIPv6 检查 IPv6 布尔
isAlpha 不能使用字母 布尔
isAlphanumeric 只允许字母数字字符 布尔
isNumeric 只能使用数字 布尔
isInt 只能是整数 布尔
isFloat 只能是浮点数 布尔
isDecimal 检查数字 布尔
isLowercase 检查小写字母 布尔
isUppercase 检查大写字母 布尔
notNull 不允许null 布尔
isNull 只能为null 布尔
notEmpty 不能空字符串 布尔
equals 只能使用指定值 字符串
contains 必须包含子字符串 字符串
notIn 不能是数组中的任意一个值 数组
isIn 只能是数组中的任意一个值 数组
notContains 不能包含子字符串 字符串
len 值的长度必在 2 和 10 之间 数组
isUUID 只能是UUID 数字
isDate 只能是日期字符串 布尔
isAfter 只能使用指定日期之后的时间 字符串
isBefore: 只能使用指定日期之前的时间 字符串
max 允许的最大值 数字
min 允许的最小值 数字
isArray 不能使用数组 布尔
isCreditCard 检查是有效的信用卡 布尔

除了上面的校验方法以外还提供了自定义校验方法,使用isEven去定义一个函数,其函数的第一个参数就是所存如的值:

const AaronTest = sequelize.define('project', {
    title: Sequelize.STRING,
    description: {
    type:Sequelize.TEXT,
    validate:{
        isEven: function(value) {
            if(parseInt(value) % 2 != 0) {
                throw new Error('Only even values are allowed!')
            }
        }
    }
    }
},{
    timestamps: false
})

上面说过使用sequelize.import()也可以创建model这个方法其实是模型导入,通过文件导入模型定义。检查模型是否已经定义。被导入的模型会被缓存,所以多次导入并不会重复加载,path表示要导入文件的路径,如果使用相对路径会自动转换为绝对路径。

const AaronTest = sequelize.import('../model/user.js');

user.js

module.exports = function(sequelize, DataTypes) {
  return sequelize.define("project", {
    name: DataTypes.STRING,
    description: DataTypes.TEXT
  })
}

CRUD

CRUD:是指在做计算处理时的增加(Create)、读取(Read)、更新(Update)和删除(Delete)几个单词的首字母简写。crud主要被用在描述软件系统中数据库或者持久层的基本操作功能。

创建

创建数据的方法有很多种,这里简单的介绍一些常用的:

第一种:

先创建数据实例,然后调用实例的save方法,完成数据存储。

const Aaron = AaronTest.build({
  'title': `后端 | ${Math.random()}`,
  'description': '技术部'
});
Aaron.save().then((result) => {
    //  成功
    console.log(result)
}).catch((error) => {
    //  失败
    console.log(error)
})

第二种:

通过静态create方法

const user = AaronTest.create({
  'title': `前端 | ${Math.random()}`,
  'description': '网络部'
}).then(function(result) {
    //  成功
    console.log(result)
}).catch(function(error) {
    //  失败
    console.log(error)
});
读取

通过findAll方法读取数据。

AaronTest.findAll({
  where: {
    description: '网络部'
  },
  limit: 10,
  offset: 0
}).then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

通过上述方法创建的数据可以直接作为返回结果返回给前台,但是如果对查询到的结果进行数据操作时不行的,因为查询到的结果是sequelize处理过的模型,如果想对其操作需要在参数中添加row:true属性。

AaronTest.findAll({
  where: {
    description: '网络部'
  },
  limit: 10,    //  查询多少条
  offset: 0,    //  查询开始位置
  raw:true
}).then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

添加row:true返回的则是一个没有被包装过的数组了。在项目过程中需要查询一下当前所查询的数据共有多少条返回给前端。

AaronTest.count({
  where:{
    description: '网络部'
  }
}).then().then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

使用这种方法是确实可以查询到想要的结果,但是无论是先查询列表还是先查询总数,都需要对数据库进行两次查询很繁琐。sequelize提供了一个很方便的方法。

AaronTest.findAndCountAll({
  where: {
    description: '网络部'
  },
  limit: 10,
  offset: 0,
  raw:true,
  attributes:["id", "title"]    //  需要查询出的字段
}).then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

通过上面的方法则可以查询到总数以及条件范围内的数据,一举两得。查询结果返回的是一个json对象,其包括controws两个属性,分别是总数和数据。很舒服有没有。

以上方式是查询列表,查询单条数据使用其他方法:

AaronTest.findOne({
  where:{
    id:6
  },
  raw:true,
  attributes:["id", "title"] 
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})
更新

修改数据可以直接调用静态的update方法,通过where条件查询,对其搜索到的数据进行查询,并对查询到的数据进行更改。

AaronTest.update({
  description: '前端部',
  title:`前端 | ${Math.random()}`
},{
  where:{
    description: "网络部"
  }
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

该方法修改所有查询的到的数据,返回结果为数组形式,数据只有一个值,也就是数组的第0项,则是N条数据修改成功。

删除

删除操作通过destroy方法,同样也是通过where条件查询,对所查询数据进行删除。

AaronTest.destroy({
  where: {
    description: "UI部",
  }
}).then(function(result) {
  console.log(result)
}).catch(function(error) {
  console.log(error)
});

当删除成功后,返回结果为Number,删除多少条数据,如果没有删除则会返回0。此方法属于物理删除,删除后无法进行恢复。

查询参数

CRUD操作过程中,都少不了的就是查询,细心的应该可以看的出,上面的例子中查询的时候多多少少的对其进行了一些小的改动。Sequelize中有两种查询:使用Model(模型)中的方法查询和使用sequelize.query()进行基于SQL语句的原始查询。上面用到的是Model查询方式,接下来就详细的介绍一些常用的参数以及其代表的意义。

attributes - 属性与查询字段

查询时,如果只需要查询模型的部分属性,可以在通过在查询选项中指定attributes实现。该选项是一个数组参数,在数组中指定要查询的属性即可,这个字段在上面进行查询的时候已经使用过了。

AaronTest.findOne({
  where:{
    id:6
  },
  raw:true,
  attributes:["id", "title", "description"]
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

查询属性(字段)可以通过传入一个嵌套数据进行重命名,这里需要强调一下重命名所指的是对查询出的数据键值进行重命名处理,而不是更改数据表中的字段名称。

AaronTest.findOne({
  where:{
    id:2
  },
  attributes:["id", ["title","t"]],
  raw:true
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})
//  注意这里t ↓
//  { id: 2, t: '前端 | 0.8765218593370694' }

通过sequelize.fn方法可以进行聚合查询,个人觉得这个方法不太常用,但是还是简单的介绍一下,这种查询方式是向当前查询内容中添加一个新的属性,并且查询的是列表还是查询单条数据。

应用场景:

比如有很多商品,每个商品都有自己的分类,根据id进行查询了一个商品,但是与其同类的商品有多少?就可以使用这个方法添加进去。下面例子中的count则是添加进去属性的键。

AaronTest.findAll({
    where:{
        id:2
    },
    attributes: [
        "id", 
        ["title","t"],
        [sequelize.fn('COUNT', sequelize.col('id')), 'count']
    ],
    raw:true
}).then((result) => {
    console.log(result)
}).catch((error) => {
    console.log(error)
})

可能有一种情况,当前所需要查询的表字段太多,但是只有一两个数据不想要,在attributes数组中添加很长的字段名称,这样会显得代码很臃肿。attributes不光可以为数组,还可以为对象在对象存在exclude这个属性,这个属性就是剔除掉那些不想要的属性。

AaronTest.findOne({
  where:{
    id:2
  },
  attributes:{
    exclude: ['id'] 
  },
  raw:true
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})
where - 指定筛选条件

上面的那么多例子中where出现的次数最多了,除了增加数据不需要,其他的都需要用到where条件,可以指定一个where选项以指定筛选条件,where是一个包含属性/值对对象,sequelize会根据此对象生产查询语句的筛选条件。

where的基础用法也就向上面那样,针对某些特定的条件进行查询处理。

AaronTest.findOne({
  where:{
    id:2
  },
  attributes:{
    exclude: ['id'] 
  },
  raw:true
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

就像上面那样简单的查询无法满足所有的业务需求,Sequelize还提供了操作符以满足更多的查询条件,常用的操作符如下:

$and: {a: 5}                    // AND (a = 5)
$or: [{a: 5}, {a: 6}]           // (a = 5 OR a = 6)
$gt: 6,                         // > 6
$gte: 6,                        // >= 6
$lt: 10,                        // < 10
$lte: 10,                       // <= 10
$ne: 20,                        // != 20
$not: true,                     // IS NOT TRUE
$between: [6, 10],              // BETWEEN 6 AND 10
$notBetween: [11, 15],          // NOT BETWEEN 11 AND 15
$in: [1, 2],                    // IN [1, 2]
$notIn: [1, 2],                 // NOT IN [1, 2]
$like: '%hat',                  // LIKE '%hat'
$notLike: '%hat'                // NOT LIKE '%hat'
$iLike: '%hat'                  // 包含'%hat' (case insensitive) (PG only)
$notILike: '%hat'               // 不包含'%hat'  (PG only)
$like: { $any: ['cat', 'hat']}  // 像任何数组['cat', 'hat'] -也适用于iLike和notLike
limit/offset - 分页与限制返回结果数

在进行列表查询时,不能把查询道德所有数据全部返回出去,需要对数据进行分页处理。

// 获取 10 条数据(实例)
AaronTest.findAll({ limit: 10 })
// 跳过 8 条数据(实例)
AaronTest.findAll({ offset: 8 })
// 跳过 5 条数据并获取其后的 5 条数据(实例)
AaronTest.findAll({ offset: 5, limit: 5 })
查询排序

order选项用于查询结果的排序数据。排序时应该传入一个包含属性-排序方向的元组/数组,以保证正确的转义:

AaronTest.findAll({
  order: [
    // 转义 username 并对查询结果按 DESC 方向排序
    ['username', 'DESC'],
    // 按 max(age) 排序
    sequelize.fn('max', sequelize.col('age')),
    // 按 max(age) DESC 排序
    [sequelize.fn('max', sequelize.col('age')), 'DESC'],
    // 按 otherfunction(`col1`, 12, 'lalala') DESC 排序
    [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
    // 按相关联的User 模型的 name 属性排序
    [User, 'name', 'DESC'],
    // 按相关联的User 模型的 name 属性排序并将模型起别名为 Friend
    [{model: User, as: 'Friend'}, 'name', 'DESC'],
    // 按相关联的User 模型的嵌套关联的 Company 模型的 name 属性排序
    [User, Company, 'name', 'DESC'],
  ]
  // 以下所有声明方式都会视为字面量,应该小心使用
  order: 'convert(user_name using gbk)'
  order: 'username DESC'
  order: sequelize.literal('convert(user_name using gbk)')
})

上面说的这些对于SQL语句了解一些,都是很容理解,有些API不常用也就没些,详细可以查看中文文档。

SQL语句查询

原始查询中有两种替换查询参数的方法,以:开头的参数的形式替换或以不命名以?替换。在选项对象中传递参数:

  • 如果传递一个数组,? 会按数组的顺序被依次替换
  • 巢传递一个对象,:key将会用对象的键替换。如果对象中未找到指定键,则会引发异常(反之亦然)
//  这里是sequelize,并不是model
sequelize.query('SELECT * FROM projects WHERE id = ?',
  { replacements: ['active'], type: sequelize.QueryTypes.SELECT }
).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

总结

以上是对sequelizeapi进行了整理,虽然不太全面,熟练掌握上述API可以做一个项目了,有关sequelize的更多的用法我也在继续爬坑中,可能文章中有些许错误,大家可以在下方留言,我会尽快做出改正。感谢大家的阅读。


Aaron
4k 声望6.1k 粉丝

Easy life, happy elimination of bugs.