Sequelize 基于数据库实例定义模型,可以方便的进行数据的增、删、改、查操作,并可以基于模型关系实现连接查询、事务等操作。但这些操作都是基于一个数据库实例,即:一个sequelize
实例,除读写分离外,Sequelize也并不支持多数据库操作。如果你的项目中需要对多个数据库进行操作,就不那么方便。本文将针对Sequelize操作多数据库的情况做一些整理。
1. 模型定义与导出
本文假设有以下两张表,分别存储在数据库db1
和db2
中:
users
- 用户信息表,存储db1
中orders
- 用户订单表,存储db2
中,并通过userId
字段与users
建立关联关系
1.1 模型定义
Users
模型定义如下models/users.js
:
module.exports = function (sequelize, DataTypes) { return sequelize.define('Users', { id:{type:DataTypes.BIGINT(11), autoIncrement:true, primaryKey : true, unique : true}, name: { type: DataTypes.STRING, comment:'姓名' }, sex: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, comment:'性别' } }, { timestamps: false, underscore: false, freezeTableName: true, tableName: 'users', charset: 'utf8', collate: 'utf8_general_ci' }); }
Orders
模型定义如下models/orders.js
:
module.exports = function (sequelize, DataTypes) { return sequelize.define('Orders', { id: { type: DataTypes.BIGINT(11), autoIncrement: true, primaryKey: true, unique: true, comment:'主键' }, userId: {type: DataTypes.BIGINT(11), allowNull: false, comment:'用户Id' }, total: { type: DataTypes.DECIMAL(10, 2), allowNull: false, comment:'订单总额' } }, { underscore: false, timestamps: false, freezeTableName: true, tableName: 'orders', comment: '订单表', charset: 'utf8', collate: 'utf8_general_ci' }); }
注意:本文假设基于同服务器的两个不同数据库操作。
1.2 模型导出
创建数据库实例工厂models/_dbs.js
:
'use strict' var Sequelize=require('sequelize'); // 数据库1 实例 exports.db1 = function () { var sequelize=new Sequelize( 'db1', 'root', '111111', { logging: console.log, timezone: '+08:00' } ); return sequelize; } // 数据库2 实例 exports.db2 = function () { var sequelize=new Sequelize( 'db2', 'root', '111111', { logging: console.log, timezone: '+08:00' } ); return sequelize; }
导出模型models/index.js
:
'use strict' var db1 = require('./_dbs').db1(); var db2 = require('./_dbs').db2(); module.exports = { Users: db1.import('./users'), Orders: db2.import('./orders') }
Model
层封装后并导出后,在外部调用的只是单个模型,在没有跨数据库事务、跨数据库关系模型查询的情况下,所有操作都和单数据库操作无异。
2. 基于多数据库的模型操作
经过以上封装,当操作单个模型时,多数据库与单数据库操作并没有任何区别。而操作关系模型时,会有一些差别,以下是几种操作场景。
在同一数据库实例(同一sequelize
实例)中,我们会像下面这样定义模型关系:
// 定义模型关系 Users.hasMany(Orders, {foreignKey: 'userId'}); Orders.belongsTo(Users, {foreignKey: 'userId'}); // 本例中未使用belongsTo关系
注意:定义模型关系后,使用sequelize.sync()
同步时,该方法会根据关系创建外键约束。对于不一个数据库中的两张表,则不能使用外键约束,但仍然可以定义模型关系,并手工指定关系键,通过定义模型可以来方便的进行一些关系模模型的查询、创建等操作。
2.1 多数据库数据插入
单模型数据插入
在不使用事务或模型关系的情况下,多数据库数据插入与单数据库并无区别:
// 多数据库数据插入 Users.create({name:'张小三', sex:1}).then(function (user) { return Orders.create({userId:user.id, total:199}) }).then(function (order) { console.log(order); })
以上会生成以下两条SQL语名:
INSERT INTO `users` (`id`,`name`,`sex`) VALUES (DEFAULT,'张小三',1); INSERT INTO `orders` (`id`,`userId`,`total`) VALUES (DEFAULT,1,199);
基于事务的关系模型数据插入
错误方式
在单数据库实例中,我们可以像下面这样基于事务插入数据:
Users.sequelize.transaction(function(t) { return Users.create({name:'王小五', sex:1}, {transaction:t}).then(function (user) { return Orders.create({userId:user.id, total:199}, {transaction:t}) }) }).then(function (results) { console.log(results) })
其执行过程如下:
START TRANSACTION; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET autocommit = 1; INSERT INTO `users` (`id`,`name`,`sex`) VALUES (DEFAULT,'李小四',1); INSERT INTO `orders` (`id`,`userId`,`total`) VALUES (DEFAULT,4,199); ROLLBACK;
可见sequelize.transaction
是基于一个数据库实例创建的事务,而Users
和Orders
分别们于不同的数据库,所以会抛出类型以下表不存在的错误:
Unhandled rejection SequelizeDatabaseError: ER_NO_SUCH_TABLE: Table 'db1.orders' doesn't exist
改良方式
当在同一服务器的不同数据库进行事务操作时,我们会通过数据库前缀来添加数据库及Schema
的引用,类似如下:
INSERT INTO `db`.`schema.talbe` (…) VALUES (…);
这样,可以使用sequelize.query()
,通过直接执行SQL的方式实现非同数据库实例的数据插入:
Users.sequelize.transaction(function(t) { return Users.create({name:'李小四', sex:1}, {transaction:t}).then(function (user) { return Users.sequelize.query('INSERT INTO `db2`.`orders` (`userid`, `total`) VALUES($1, $2)', {bind:[user.id, 199], transaction:t}) }) }).then(function (results) { console.log(results) })
SQL执行过程如下:
START TRANSACTION; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET autocommit = 1; INSERT INTO `users` (`id`,`name`,`sex`) VALUES (DEFAULT,'李小四',1); INSERT INTO `db2`.`orders` (`userid`, `total`) VALUES(6, 199) COMMIT;
多数据库数据查询
使用多个数据库时,依然可以基于模型的关联进行关系模型的查询。
hasMany
关系查询成功:
Users.findOne({name:'张小三'}).then(function (user) { return user.getOrders({raw:true}); }).then(function (userOrders) { console.log(userOrders.dataValues) })
查询结果如下:
[ { id: 1, userId: 1, total: 199 } ]
连接查询
include
连接查询失败
在同一数据库实例中,可以通过include
参数方便的进行连接查询。类似如下:
let include = [{ model: Users, required: false, attributes: ['id', 'name'] }]; Orders.findAll({include:include}).then(function (orders) { console.log(orders); })
在本例中,执行的SQL语句如下:
SELECT `Order`.`id`, `Order`.`userId`, `Order`.`total`, `User`.`id` AS `User.id`, `User`.`name` AS `User.name` FROM `orders` AS `Order` LEFT OUTER JOIN `users` AS `User` ON `Order`.`userId` = `User`.`id`;
由于不在同一实例中,当然会执行失败:
Unhandled rejection SequelizeDatabaseError: ER_NO_SUCH_TABLE: Table 'db2.users' doesn't exist
改良方式
同样的,也可以通过sequelize.query()
方法来实现跨数据库的连接查询:
Orders.sequelize.query('SELECT `Order`.`id`, `Order`.`userId`, `Order`.`total`, `User`.`id` AS `User.id`, `User`.`name` AS `User.name` FROM `orders` AS `Order` LEFT OUTER JOIN `db1`.`users` AS `User` ON `Order`.`userId` = `User`.`id`', {raw:true}).then((results) => { console.log(results) })
通过视图实现连接查询
除使用sequelize.query()
进行连接查询外,还可以通过视图实现连接查询。视图可以理解为只用于查询操作的一种特殊的表。
首先,在数据库db2
中创建视图:
CREATE VIEW v_orders AS SELECT `Order`.`id`, `Order`.`userId`, `Order`.`total`, `User`.`name` AS `name` FROM `orders` AS `Order` LEFT OUTER JOIN `db1`.`users` AS `User` ON `Order`.`userId` = `User`.`id`;
定义这个视图的模型:
module.exports = function (sequelize, DataTypes) { return sequelize.define('V_Order', { id: { type: DataTypes.BIGINT(11), autoIncrement: true, primaryKey: true, unique: true, comment:'主键' }, userId: {type: DataTypes.BIGINT(11), allowNull: false, comment:'用户Id' }, total: { type: DataTypes.DECIMAL(10, 2), allowNull: false, comment:'订单总额' }, name: { type: DataTypes.STRING, comment:'姓名' }, }, { underscore: false, timestamps: false, freezeTableName: true, tableName: 'v_orders', comment: '订单视图', charset: 'utf8', collate: 'utf8_general_ci' }); }
通过视图实现连接查询:
V_Orders.findAll({raw:true}).then((results) => { console.log(results); });
执行过程及查询结果:
Executing (default): SELECT `id`, `userId`, `total`, `name` FROM `v_orders` AS `V_Order`; [ { id: 1, userId: 1, total: 199, name: '张小三' }, { id: 2, userId: 6, total: 199, name: '李小四' } ]
本文基于MySQL
进行操作演示。除数据“插入”、“查询”操作外,数据“更新”和“删除”也类似,用法请诸君自行思考测试。
本文示例:sequelizeMultiDB