[Node.js与数据库]支持多种数据库的ORM框架 Sequelize

 2016年05月03日    2133     声明


Sequelize是一个关系型数据库ORM框架,它基于Promise构建。支持MySQLPostgreSQLMariaDBSQLiteMSSQL几种关系型数据库,功能非常强大。


  1. 介绍与安装
  2. 连接数据库
  3. 定义对象模型(定义表)
  4. 对象模型操作(增、删、改、查)

1. 介绍与安装

1.1 什么是ORM

ORM(Object Relational Mapping)即:对象关系映射,是随着面向对象的软件开发方法的发展而产生的一种技术。 。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。对象(数据)之间存在关联或继承的,而ORM就是将这些关系映射出来,并通过系统业务逻辑(对象操作)实现对关系数据的管理。

在应用中,我们可能会将数据存放在数据库中。在关系型数据库中,操作和查询数据都要基于SQL语句,这样操作即不直观、又不利于数据管理。而通过ORM技术,将数据库中的关系数据(表结构)映成对象模型和关系模型,这样我们就可以像操作对象一样实现数据库的操作。这时,我们对数据增、删、改、查等操作,都是在ORM中抽像出的一些具体的方法而不再是SQL语句。ORM会自动完成数据层的操作,我们完全不用再写SQL语句,甚至不用关系使用的是MySQL还是其它类型的数据库。


1.2 Sequelize安装

Sequelize本身只是一个ORM框架,它只做了对象模型和关系模型的映射与管理,并没有相关数据库的底层驱动。在使用进,除了要安装sequelize模块外,还要安装所使用数据库的驱动(操作)模块。

安装sequelize

$ npm install --save sequelize

根据你所使用的数据库类型,安装以下一个数据库驱动模块:

$ npm install --save pg pg-hstore  // PostgreSQL时安装
$ npm install --save mysql // MySQL 或 MariaDB 时安装
$ npm install --save sqlite3  // SQLite 时安装
$ npm install --save tedious // MSSQL时安装


2. 连接数据库

Sequelize将设置初始化一个连接池,在应用作用域内,应该为每个数据库创建唯一一个实例。Sequelize()是一个构造函数,通过这个函数可以返回一个Sequelize实例:

var Sequelize = require('sequelize');

var sequelize = new Sequelize(
  'my_db,   // 数据库名
  'root',   // 用户名
  'secret', // 用户密码
  {
    'dialect': 'mysql',  // 数据库使用mysql
    'host': 'localhost', // 数据库服务器ip
    'port': 3306         // 数据库服务器端口
  }
);

这样就创建了一个连接到MySQL的实例。创建数据库连接有多种形式,可以参考以下格式:

var sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql'|'mariadb'|'sqlite'|'postgres'|'mssql',

  pool: {
    max: 5,
    min: 0,
    idle: 10000
  },

  // 仅 SQLite
  storage: 'path/to/database.sqlite'
});

// 也可以通过一个URL创建
var sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');


3. 定义对象模型(定义表)

3.1 模型定义

模型是数据库表结构到对象的一种映射关系。可以通过Sequelize实例的sequelize.define()方法来定义模型,其语法结构如下:

 sequelize.define('name', {attributes}, {options})

如,定一个user模型:

var User = sequelize.define('user', {
  firstName: {
    type: Sequelize.STRING, //数据类型
    field: 'first_name' // 数据库字段名,即:数据库字段名为'first_name',而对象属性名为'firstName'
  },
  lastName: {
    type: Sequelize.STRING
  }
}, {
  freezeTableName: true // 模型对应的表名与模型名将相同
});

这样定义了一个名为user的模型对象,这个对象在库中对应表名也是user。在这个对象中有firstNamelastName两个属性,而在对应的表中会相应的生成first_namelastName两个字段。


3.2 表结构同步

定义模型对象后,对应的表并不能自动创建数据库中,这时还需要调用Model.sync()方法把模型对象同步到数据库表中。如,将上例中的user模型对象同步到数据库中:

User.sync({force: true}).then(function () {
  // 表创建完成
  return User.create({
    firstName: 'John',
    lastName: 'Hancock'
  });
});

执行完后,数据库my_db将会有一张名为user的表。在上面的同步操作中,还使用Model.create()方法向表中插入了一条数据。

调用Model.sync()方法后,Sequelize会按所使用数据库的语法规则生成创建表的SQL语名,并调用所使用数据库类型的驱动来执行这条语句。在这个方法中,我们使用了force:true参数,这个参数表示首先删除表再重新创建表。

Model.sync()方法并不同必须的,我们也可以先通过数据库对应IDE创建好表结构后,再根据表结构来定义模型对象。如果需要通过Model.sync()方法来同步表结构,一般只需要在创建表或修改表时调用一次,在笔者所应用的项目中是将.sync()表结构同步单独写了一个处理脚本,在初始化或升级时单独调用处理。


3.3 Promise规范API

Sequelize基于Promise进行异步控制,其所有API包括但不限于数据的增、删、改、查操作,其都是基于Promise规范处理。

Promise规范中,其基于.then()方法得到处理结果,基于.catch()方法进行错误处理。在下面这个操作中:

// 不正确的使用方式
user = User.findOne()

console.log(user.get('firstName'));

这样并不能得到所期望的结果,对象的findOne()方法返回的是一个promise对象(即:user是一个promise对象),而不是数据查询结果。应该像下面这样处理:

User.findOne().then(function (user) {
  // 得到结果
  console.log(user.get('firstName'));
}).catch(function (err){
  // 进行错误处理
  console.error(err);
});


4. 对象模型操作(增、删、改、查)

通过Sequelize获取的模型对象实例列是一个DAO(Data Access Object)对象,这些对象会拥有许多操作数据库表的实例对象方法,这个对象会有create()find()update()destroy()等对象操作方法,这些方法会对应的生成INSERTSELECTUPDATEDELETESQL语句。

4.1 增:create操作

create操作是指创建数据记录,我们可通过Model.create()方法添加一条数据或通过Model.bulkCreate()方法添加多条数据:

User.create({
  firstName: 'liu',
  lastName: '小明'
}).then(function (created){
  // 创建结果
  console.log(created);
}).catch(function(err){
  // 出错了
  console.log(err);	
})

在这个方法,其实际执行SQL语句类似如下:

INSERT INTO `user` (`id`,`first_name`,`lastName`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'liu','小明','2016-05-03 15:22:06','2016-05-03 15:22:06');

可以看出,除了我们在User对象定义时的两个字段外,其还额外处理了idcreatedAtupdatedAt三个字段。id是数据表的主键,如果在定义对象时添加了主键,将使用自定义的主键,而没有添加时将自动添加一个名为id的主键。createdAtupdatedAt两个字段同样是自动添加的,如果不需要使用可以在定义模型时进行设置:

sequelize.define('user', {
  firstName: {
    type: Sequelize.STRING,
    field: 'first_name'
  },
  lastName: {
    type: Sequelize.STRING
  }
}, {
  freezeTableName: true,
  'createdAt': false,
  'updatedAt': 'utime'
});

更多Model定义参数请参考:Model API


4.2 改:update操作

update操作是指修改数据记录,我们可通过Model.update()方法来修改已经存在的数据记录:

User.update({firstName:'liu'}, {where:{id:1}}).then(function (result){
  // 修改结果
  console.log(result);
}).catch(function(err){
  // 出错了
  console.log(err);	
})

.update()方法中,其实际生成的SQL语句类似如下:

UPDATE `user` SET `first_name`='liu',`updatedAt`='2016-05-03 15:43:32' WHERE `id` = 1


4.3 查:find操作

find操作是指查询已存在的数据记录,我们可通过Model.findOne()方法查找一条数据,或通过Model.findAll()方法查找所有数据:

// 按指定条件查找一条数据
User.findOne({where:{id:1}}).then(function (result){
  // 查询结果
  console.log(result);
}).catch(function(err){
  // 出错了
  console.log(err);	
})

// 查找所有数据
User.findAll().then(function (result){
  // 查询结果
  console.log(result);
}).catch(function(err){
  // 出错了
  console.log(err);	
})

在上面的查询操作中,其实际执行SQL语句分别类似如下:

// 单条数据查询
SELECT `id`, `first_name` AS `firstName`, `lastName`, `createdAt`, `updatedAt` FROM `user` AS `user` WHERE `user`.`id` = 1

// 多条数据查询
SELECT `id`, `first_name` AS `firstName`, `lastName`, `createdAt`, `updatedAt` FROM `user` AS `user`


4.4 删:destroy操作

destroy操作是指删除已存在的数据记录,我们可通过Model.destroy()方法来删除数据:

User.destroy({where:{id:1}}).then(function (result){
  // 操作结果
  console.log(result);
}).catch(function(err){
  // 出错了
  console.log(err);	
})

在以上.destroy()方法中,其实际生成的SQL语句类似如下:

DELETE FROM `user` WHERE `id` = 1