问题
今天早上,我看了一下我在Univjobs编码的当前学生注册流程。当我打开学习领域的下拉列表时,我注意到每个字段都有一些重复的内容。这肯定是不对的。应该只有大约80-90个 "研究领域",这是我们在创建数据库时最初播种的东西。
为了确认这不只是前端渲染的问题,我看了一下从列表API返回的内容。
是的,有超过600个研究领域。我肯定做错了什么。
在对我们目前的数据库进行了一些调查,查看了模型,并回忆了我与这个项目的集成工程师的谈话,我意识到发生了什么。
我们刚刚转移到使用Elastic Beanstalk,我们必须更新我们的CI。在部署脚本的最后,我们正在运行最初的数据库播种机。现在,如果数据库约束已经被满足(比如唯一约束),数据库播种器应该会失败。我预计,如果我们试图两次添加相同的学习区域,它就会失败。
看一下学习领域的模型,我们可以注意到一些事情。
module.exports = function(sequelize, DataTypes) {
const ListsAreaOfStudy = sequelize.define('lists_area_of_study', {
area_of_study_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING(90),
allowNull: false,
unique: true // <== Strange, this seems to not have worked.
}
},{
timestamps: true,
underscored: true,
tableName: 'lists_area_of_study',
instanceMethods: {}
})
ListsAreaOfStudy.associate = (models) => {}
return ListsAreaOfStudy;
};
现在,我并没有完全做错,我在name 字段上有一个unique 约束,但是我可能只在Sequelize模型文件中做了这个,而不是在我第一次用脚本创建这个模型时的迁移脚本中。
让我们看看这个模型在我们的数据库中是什么样子的。
CREATE TABLE `lists_area_of_study` (
`area_of_study_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(90) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`area_of_study_id`)
) ENGINE=InnoDB AUTO_INCREMENT=640 DEFAULT CHARSET=utf8;
是的,这很可能是发生了什么,我们在数据库中丢失了unique 的约束。
那么我们如何解决这个问题呢?因为我们现在处理的是数以百计的生产用户使用该应用程序的结果,并且在他们的账户和他们的学习领域之间建立了一种关系。显然,我们不能去手动改变这一切,所以我们需要使用Sequelize迁移的脚本。
方法
以下是我们在迁移脚本中要做的事情。
- 获取所有的研究领域
- 将所有多余的研究领域的关系重新分配给初始id(1-60)。
- 删除多余的(60,以此类推)。
- 启用唯一约束并将
autoIncrement:true字段设置为false。
这应该能解决我们的问题......但首先,我们要做一个额外的数据库备份--以防我们搞砸了什么。
创建迁移
sequelize migration:create --name "fix area of studies"
这将在我的migrations文件夹中为我创建一个新的迁移文件,名为20180721190331-fix-area-of-studies.js 。
现在,我们要开始建立它了。
获取所有的研究领域
首先,我们要获取所有的研究领域。
'use strict';
const Models = require('../../app/models');
module.exports = {
up: (queryInterface, Sequelize) => {
return new Promise(async (resolve, reject) => {
let areaOfStudies;
let map = {};
try {
// Get all area of studies
areaOfStudies = await Models.ListsAreaOfStudy.findAll({
where: {}
})
console.log(areaOfStudies.length);
}
catch (err) {
console.log(err);
}
})
},
down: (queryInterface, Sequelize) => {}
};
所以,这应该能得到我们所有的研究领域。
呀,有639个。
好的,接下来我们将把它们全部分类到地图上。
// Place all of them in a map by name
areaOfStudies.forEach((area) => {
if (!map.hasOwnProperty(area.name)) map[area.name] = [];
map[area.name].push(area.area_of_study_id);
})
让我们看看到目前为止我们有什么。
到目前为止还不错。现在,让我们对这些键进行迭代(同时也对一些花哨的ES6语法进行迭代),并打印出我们打算对每个值做什么。
// Now, for each item in the map, we have to
// get the students that have that area of study
for (let [name, ids] of Object.entries(map)) {
// Get all students that have this area of study
let properId = ids[0];
let scrapIds = ids.splice(1);
for(let id of scrapIds) {
console.log(
`Students who have ${name} with ${id} should be => ${properId}
`);
}
}
让我们看看到目前为止我们有什么。
好吧,看起来我们肯定是在正确的轨道上。如果你没有注意到,我喜欢在做这种破坏性的事情时,把每一步都打印出来,以防止在我做了一些我不想做的事情时不得不重新开始。
重新分配关系
现在让我们实际操作一下。让我们重新分配这些关系。我们只需在这里添加一行...
// Now, for each item in the map, we have to
// get the students that have that area of study
for (let [name, ids] of Object.entries(map)) {
// Get all students that have this area of study
let properId = ids[0];
let scrapIds = ids.splice(1);
for(let id of scrapIds) {
console.log(
`Students who have ${name} with ${id} should be => ${properId}
`);
await queryInterface.sequelize.query(
`UPDATE student SET area_of_study_id = ${properId} where area_of_study_id = ${id};`
)
}
}
删除多余的研究领域
我们可以用这一行来清理其他我们不需要的东西,真的很容易。
await queryInterface.sequelize.query(`DELETE from lists_area_of_study WHERE area_of_study_id >= 60;`)
更新模型定义
我们需要做的最后一点是更新我们的模型定义,以便这种情况不会再次发生。 让我们在名字字段上添加一个唯一约束(这次是真的)。
await queryInterface.addConstraint('lists_area_of_study', ['name'], {
type: 'unique',
name: 'unique_area_of_study'
});
我们可以通过检查表的定义来确认这一点。下面是之前的情况。
CREATE TABLE `lists_area_of_study` (
`area_of_study_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(90) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`area_of_study_id`)
) ENGINE=InnoDB AUTO_INCREMENT=640 DEFAULT CHARSET=utf8;
然后是之后。
CREATE TABLE `lists_area_of_study` (
`area_of_study_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(90) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`area_of_study_id`),
UNIQUE KEY `unique_area_of_study` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=640 DEFAULT CHARSET=utf8;
问题解决了。
这就是整个脚本。
'use strict';
const Models = require('../../app/models');
module.exports = {
up: (queryInterface, Sequelize) => {
return new Promise(async (resolve, reject) => {
let areaOfStudies;
let map = {};
try {
// Get all
areaOfStudies = await Models.ListsAreaOfStudy.findAll({ where: { } })
// Place all of them in a map by name
areaOfStudies.forEach((area) => {
if (!map.hasOwnProperty(area.name)) map[area.name] = [];
map[area.name].push(area.area_of_study_id);
})
// Now, for each item in the map, we have to get the students that have that area of study
for (let [name, ids] of Object.entries(map)) {
// Get all students that have this area of study
let properId = ids[0];
let scrapIds = ids.splice(1);
for(let id of scrapIds) {
console.log(`Updating students who have ${name} with ${id} should be => ${properId}`);
await queryInterface.sequelize.query(`UPDATE student SET area_of_study_id = ${properId} where area_of_study_id = ${id};`)
}
}
// Then we'll delete all the old area of studies
await queryInterface.sequelize.query(`DELETE from lists_area_of_study WHERE area_of_study_id >= 60;`)
console.log("Deleted old")
// Then, we'll ensure that's a unique constraint
await queryInterface.addConstraint('lists_area_of_study', ['name'], {
type: 'unique',
name: 'unique_area_of_study'
});
console.log("Add unique constraint to lists_area_of_study")
resolve();
}
catch (err) {
console.log(err);
}
})
},
down: (queryInterface, Sequelize) => {}
};
sequelize ORM确实是用NodeJS构建强大网络应用的一个伟大工具。如果你正在考虑为NodeJS使用关系型数据库,并寻找一个可以使用的ORM,我会推荐Sequelize,因为它背后有一个非常好的社区,而且比其他一些工具更成熟。
然而,我对开始使用它的人的建议是,阅读文档。而且要读好它。Sequelize在生产中可能非常有用,但如果使用不当,也可能造成很多麻烦。
如果你想了解更多关于用Sequelize ORM构建应用程序的真实内容或教程,请告诉我!我已经经历了很多困难。我已经经历了它的壕沟。