數(shù)據(jù)庫(kù)初始化
????????在軟件開(kāi)發(fā)階段和測(cè)試階段,為了方便調(diào)試,我們通常會(huì)進(jìn)行一系列的數(shù)據(jù)庫(kù)初始化操作,比如重置數(shù)據(jù)表,插入記錄等等,或者在部署階段進(jìn)行數(shù)據(jù)初始化的操作
????????根據(jù)前面章節(jié)介紹過(guò)的 knex.js
和 sequelize.js
,我們可以利用它們提供的方法進(jìn)行DDL,本節(jié)就數(shù)據(jù)庫(kù)表重置的初始化行為做一點(diǎn)探討,表結(jié)果為User{id: num, name: string, age: num}
,數(shù)據(jù)庫(kù)采用sqlite
Knex DDL
以下是利用 knex.schema
的一個(gè)簡(jiǎn)單示例:
knex.js
const knex = require('knex');
const fs = require('fs');
const sqlClient = knex({
client: 'sqlite3',
connection: {
filename: `${__root}/db/data.db`,
acquireConnectionTimeout: 1000
},
useNullAsDefault: true
});
module.exports = sqlClient;
init.js
global.__root = __dirname;
const knex = require('./knex.js');
const drop = knex.schema.dropTableIfExists('user');
const create = knex.schema.createTable('user', (user)=>{
user.increments('id').notNullable().primary();
user.text('name').notNullable();
user.integer('age').notNullable()
});
const promises = [drop,create];
Promise.all(promises)
.then(res=>{
console.log('Database inits successfully!')
}).catch(err=>{
console.error(err);
})
Sequelize DDL
以下是利用 Sequelize.Model
的一個(gè)簡(jiǎn)單示例:
sequelize.js
const { Sequelize,DataTypes,Model } = require('sequelize');
const fs = require('fs');
const sqlClient = new Sequelize({
dialect: 'sqlite',
storage: `${__root}/db/data.db`
})
const User = sqlClient.define('User', {
id: {
primaryKey: true,
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
age: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
tableName: 'user',
timestamps: false,
});
module.exports = {
sqlz: sqlClient,
User
}
init.js
global.__root = __dirname;
const { User } = require('./sequelize');
// User.drop();User.sync();
User.sync({ force: true }) //這個(gè)相當(dāng)于前兩個(gè)的結(jié)合體
.then(res=>{
console.log('Database inits successfully!');
}).catch(err=>{
console.error(err);
})
SQL文件
????????Springboot作為Web后端最流行的框架之一,想必各位都接觸過(guò)或者聽(tīng)說(shuō)過(guò),在Springboot中,可以在配置文件中設(shè)置sql腳本的路徑,在項(xiàng)目啟動(dòng)時(shí)執(zhí)行sql腳本來(lái)完成初始化。
????????這是一種非常好的方法,因?yàn)橛袝r(shí)候我們項(xiàng)目場(chǎng)景下的數(shù)據(jù)庫(kù)表結(jié)構(gòu)與關(guān)系可能非常復(fù)雜,而且不同語(yǔ)言,不同框架的實(shí)現(xiàn)有些區(qū)別,用代碼去完成初始化操作將是一件非常麻煩的事,既然SQL是關(guān)系型數(shù)據(jù)庫(kù)通用的語(yǔ)言,那我們就可以通過(guò)SQL腳本來(lái)定義數(shù)據(jù)庫(kù)表的結(jié)構(gòu)和關(guān)系,可以手寫(xiě)SQL腳本,也可以借助如Navicat之類的工具設(shè)計(jì)表然后轉(zhuǎn)儲(chǔ)sql腳本,然后交給我們的程序去執(zhí)行,或者手動(dòng)執(zhí)行。
Node的sql框架千千萬(wàn),我在幾個(gè)主流框架中似乎都沒(méi)看到有提供執(zhí)行sql文件的特性,其實(shí)沒(méi)那么復(fù)雜,不從構(gòu)造完美的框架角度,僅以為項(xiàng)目服務(wù)的角度考慮來(lái)說(shuō)是這樣的,接下來(lái)我們就來(lái)簡(jiǎn)單實(shí)現(xiàn)一下通過(guò)sql腳本去初始化數(shù)據(jù)庫(kù)。
有兩條路:
- 運(yùn)行環(huán)境先安裝sqlite3客戶端,node讀取sql腳本內(nèi)容,node通過(guò)
exec
去指定目錄下,打開(kāi)sqlite3命令行連接sqlite數(shù)據(jù)庫(kù),同時(shí)把sql內(nèi)容傳遞過(guò)去,在sqlite3中執(zhí)行sql腳本完成數(shù)據(jù)庫(kù)初始化操作 - Node安裝sqlite3依賴,通過(guò)sql框架連接sqlite數(shù)據(jù)庫(kù),node讀取sql腳本內(nèi)容,對(duì)內(nèi)容進(jìn)行規(guī)范化處理只剩下純凈的sql語(yǔ)句后,交給sql框架以sql語(yǔ)句的形式去運(yùn)行
Springboot采用的就是第2種方法,那我們也在Node中實(shí)現(xiàn)一下吧
實(shí)現(xiàn)準(zhǔn)備好sql腳本 schemal.sql:
-- 先刪除user表
DROP TABLE IF EXISTS `user`;
-- 定義表結(jié)構(gòu),并創(chuàng)建user表
CREATE TABLE `user` (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, --自增主鍵
name TEXT NOT NULL,
age INTEGER NOT NULL
);
Knex
先用Knex作為sql框架做個(gè)示范。獲取到項(xiàng)目根目錄路徑后,建立數(shù)據(jù)庫(kù)連接:
knex.js
const knex = require('knex');
const fs = require('fs');
const sqlClient = knex({
client: 'sqlite3',
connection: {
filename: `${__root}/db/data.db`,
acquireConnectionTimeout: 1000
},
useNullAsDefault: true
});
module.exports = sqlClient;
接下來(lái),為客戶端實(shí)現(xiàn)執(zhí)行sql文件的方法:
- 定義
runSql
方法的傳參和返回
我這里傳入sql文件的路徑,返回sql語(yǔ)句執(zhí)行的promise鏈 - 內(nèi)部實(shí)現(xiàn),首先通過(guò)
fs
模塊讀取sql腳本內(nèi)容并轉(zhuǎn)為字符串 - 把內(nèi)容中的注釋去掉
- 去掉內(nèi)容首尾的空格
- 去掉
\r
- 去掉
\n
(我為了打印sql語(yǔ)段時(shí)更加美觀,省去了這一步,不影響執(zhí)行結(jié)果) - 把內(nèi)容按照
;
號(hào)分割成一個(gè)個(gè)獨(dú)立的sql語(yǔ)句字串 - 過(guò)濾掉空字串(由每2個(gè)sql語(yǔ)句間的空格形成)
sqlClient.runSql = (path)=>{
const script = fs.readFileSync(path).toString();
console.log("Going to run a sql file:");
console.log(script);
/**
* 拆成一句句sql來(lái)執(zhí)行是因?yàn)椋琸nex執(zhí)行一串語(yǔ)句時(shí),會(huì)把它們都算進(jìn)一個(gè)事務(wù)內(nèi)
* 利用正則忽略注釋
* 去首尾空格
* 按冒號(hào)分句
* 校驗(yàn)字串是否為sql語(yǔ)句
* @type {string[]}
*/
const sqls = script.replace(/\/\*[\s\S]*?\*\/|(--|\#)[^\r\n]*/gm, '').trim().replaceAll('\r','').split(';').filter(str=>{
return str.trim() ? true : false;
});
console.log("sqls");
console.log(sqls);
console.log("start run:");
const promises = sqls.map(sql=>{
sql += ';'; // knex會(huì)自動(dòng)補(bǔ)上冒號(hào),加不加無(wú)所謂其實(shí)
console.log("Going to run a sql:");
console.log(sql);
return sqlClient.raw(sql);
})
return promises;
}
到這里,我們就得到了純凈的一條條sql語(yǔ)句,接下來(lái)把sql語(yǔ)句丟給knex
即可:
init.js
global.__root = __dirname;
const knex = require('./knex.js')
const promises = knex.runSql(`${__root}/db/schema.sql`);
Promise.all(promises)
.then(res=>{
console.log("Database inits successfully!")
}).catch(err=>{
console.error(err);
})
輸出結(jié)果:
D:\Workstation\gitee-localRepo\express-demo\DatabaseInit>node index.js
Going to run a sql file:
-- 先刪除user表
DROP TABLE IF EXISTS `user`;
-- 定義表結(jié)構(gòu),并創(chuàng)建user表
CREATE TABLE `user` (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, --自增主鍵
name TEXT NOT NULL,
age INTEGER NOT NULL
);
sqls
[
'DROP TABLE IF EXISTS `user`',
'\n' +
'\n' +
'CREATE TABLE `user` (\n' +
' id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n' +
' name TEXT NOT NULL,\n' +
' age INTEGER NOT NULL\n' +
')'
]
start run:
Going to run a sql:
DROP TABLE IF EXISTS `user`;
Going to run a sql:
CREATE TABLE `user` (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
);
Server is ready on http://:::8080
Database inits successfully!
很好,我們可以很清晰的看到sql的執(zhí)行過(guò)程
Sequelize
????????如果你把knex
這套照搬過(guò)去,把knex.raw
換成sequelize.query
,你也許會(huì)尷尬的發(fā)現(xiàn),不太對(duì)勁,它先創(chuàng)建了user表,接著又把它給刪了,還大言不慚地打印了成功信息(我的環(huán)境下是這樣,不清楚別人會(huì)不會(huì),但既然發(fā)生了就說(shuō)明存在一定的問(wèn)題)。嘗試反復(fù)執(zhí)行knex示例和seuelize示例,前者永遠(yuǎn)正確,后者永遠(yuǎn)錯(cuò)誤,而且sequelize似乎更慢一點(diǎn),產(chǎn)生這樣的區(qū)別,可能是它們執(zhí)行sql語(yǔ)句的實(shí)現(xiàn)機(jī)制不太一樣,花費(fèi)精力去看它源碼沒(méi)有必要,既然在這個(gè)場(chǎng)景下我們這兩個(gè)步驟有著明確的先后順序,那我們就通過(guò)async/await讓它們完全的順序執(zhí)行即可:
sqlClient.runSql = async (path)=> {
const script = fs.readFileSync(path).toString();
console.log("Going to run a sql file:");
console.log(script);
/**
* 拆成一句句sql來(lái)執(zhí)行是因?yàn)?,knex執(zhí)行一串語(yǔ)句時(shí),會(huì)把它們都算進(jìn)一個(gè)事務(wù)內(nèi)
* 忽略注釋
* 去首尾空格
* 按冒號(hào)分句
* 校驗(yàn)字串是否為sql語(yǔ)句
* @type {string[]}
*/
const sqls = script.replace(/\/\*[\s\S]*?\*\/|(--|\#)[^\r\n]*/gm, '').trim().replaceAll('\r','').split(';').filter(str=>{
return str.trim() ? true : false;
});
console.log("sqls");
console.log(sqls);
console.log("start run:");
for (let sql of sqls) {
const res = await sqlClient.query(`${sql};`);
}
}
輸出結(jié)果:
D:\Workstation\gitee-localRepo\express-demo\DatabaseInit>node index.js
Going to run a sql file:
-- 先刪除user表
DROP TABLE IF EXISTS `user`;
-- 定義表結(jié)構(gòu),并創(chuàng)建user表
CREATE TABLE `user` (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, --自增主鍵
name TEXT NOT NULL,
age INTEGER NOT NULL
);
sqls
[
'DROP TABLE IF EXISTS `user`',
'\n' +
'\n' +
'CREATE TABLE `user` (\n' +
' id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n' +
' name TEXT NOT NULL,\n' +
' age INTEGER NOT NULL\n' +
')'
]
start run:
Server is ready on http://:::8080
Executing (default): DROP TABLE IF EXISTS `user`;
Executing (default): CREATE TABLE `user` (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
);
Database inits successfully!
Ok!現(xiàn)在Sequelize也按照我們的意愿完成了重置user表的初始化工作文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-639005.html
如果初始化過(guò)程中涉及嚴(yán)格的先后順序,務(wù)必做好同步流甚至回滾機(jī)制。此外,在實(shí)際項(xiàng)目中,為了項(xiàng)目的代碼規(guī)范性,應(yīng)當(dāng)將數(shù)據(jù)庫(kù)路徑,初始化腳本路徑都寫(xiě)在配置文件中,而不是像本節(jié)為了方便直接寫(xiě)在需要調(diào)用的js文件中。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-639005.html
下一節(jié)-頁(yè)面渲染
到了這里,關(guān)于【Express.js】數(shù)據(jù)庫(kù)初始化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!