【Express.js】數據庫初始化

數據庫初始化

????????在軟件開發階段和測試階段,為了方便調試,我們通常會進行一系列的數據庫初始化操作,比如重置數據表,插入記錄等等,或者在部署階段進行數據初始化的操作

????????根據前面章節介紹過的 knex.jssequelize.js,我們可以利用它們提供的方法進行DDL,本節就數據庫表重置的初始化行為做一點探討,表結果為User{id: num, name: string, age: num},數據庫采用sqlite

Knex DDL

以下是利用 knex.schema 的一個簡單示例:
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 的一個簡單示例:
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 })  //這個相當于前兩個的結合體.then(res=>{console.log('Database inits successfully!');}).catch(err=>{console.error(err);
})

SQL文件

????????Springboot作為Web后端最流行的框架之一,想必各位都接觸過或者聽說過,在Springboot中,可以在配置文件中設置sql腳本的路徑,在項目啟動時執行sql腳本來完成初始化。
????????這是一種非常好的方法,因為有時候我們項目場景下的數據庫表結構與關系可能非常復雜,而且不同語言,不同框架的實現有些區別,用代碼去完成初始化操作將是一件非常麻煩的事,既然SQL是關系型數據庫通用的語言,那我們就可以通過SQL腳本來定義數據庫表的結構和關系,可以手寫SQL腳本,也可以借助如Navicat之類的工具設計表然后轉儲sql腳本,然后交給我們的程序去執行,或者手動執行。

Node的sql框架千千萬,我在幾個主流框架中似乎都沒看到有提供執行sql文件的特性,其實沒那么復雜,不從構造完美的框架角度,僅以為項目服務的角度考慮來說是這樣的,接下來我們就來簡單實現一下通過sql腳本去初始化數據庫。

有兩條路:

  1. 運行環境先安裝sqlite3客戶端,node讀取sql腳本內容,node通過exec去指定目錄下,打開sqlite3命令行連接sqlite數據庫,同時把sql內容傳遞過去,在sqlite3中執行sql腳本完成數據庫初始化操作
  2. Node安裝sqlite3依賴,通過sql框架連接sqlite數據庫,node讀取sql腳本內容,對內容進行規范化處理只剩下純凈的sql語句后,交給sql框架以sql語句的形式去運行

Springboot采用的就是第2種方法,那我們也在Node中實現一下吧
實現準備好sql腳本 schemal.sql

-- 先刪除user表
DROP TABLE IF EXISTS `user`;
-- 定義表結構,并創建user表
CREATE TABLE `user` (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,  --自增主鍵name TEXT NOT NULL,age INTEGER NOT NULL
);

Knex

先用Knex作為sql框架做個示范。獲取到項目根目錄路徑后,建立數據庫連接:
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;

接下來,為客戶端實現執行sql文件的方法:

  1. 定義runSql方法的傳參和返回
    我這里傳入sql文件的路徑,返回sql語句執行的promise鏈
  2. 內部實現,首先通過fs模塊讀取sql腳本內容并轉為字符串
  3. 把內容中的注釋去掉
  4. 去掉內容首尾的空格
  5. 去掉\r
  6. 去掉\n(我為了打印sql語段時更加美觀,省去了這一步,不影響執行結果)
  7. 把內容按照;號分割成一個個獨立的sql語句字串
  8. 過濾掉空字串(由每2個sql語句間的空格形成)
sqlClient.runSql = (path)=>{const script = fs.readFileSync(path).toString();console.log("Going to run a sql file:");console.log(script);/*** 拆成一句句sql來執行是因為,knex執行一串語句時,會把它們都算進一個事務內* 利用正則忽略注釋* 去首尾空格* 按冒號分句* 校驗字串是否為sql語句* @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會自動補上冒號,加不加無所謂其實console.log("Going to run a sql:");console.log(sql);return sqlClient.raw(sql);})return promises;
}

到這里,我們就得到了純凈的一條條sql語句,接下來把sql語句丟給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);
})

輸出結果:

D:\Workstation\gitee-localRepo\express-demo\DatabaseInit>node index.js
Going to run a sql file:
-- 先刪除user表
DROP TABLE IF EXISTS `user`;
-- 定義表結構,并創建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的執行過程

Sequelize

????????如果你把knex這套照搬過去,把knex.raw換成sequelize.query,你也許會尷尬的發現,不太對勁,它先創建了user表,接著又把它給刪了,還大言不慚地打印了成功信息(我的環境下是這樣,不清楚別人會不會,但既然發生了就說明存在一定的問題)。嘗試反復執行knex示例和seuelize示例,前者永遠正確,后者永遠錯誤,而且sequelize似乎更慢一點,產生這樣的區別,可能是它們執行sql語句的實現機制不太一樣,花費精力去看它源碼沒有必要,既然在這個場景下我們這兩個步驟有著明確的先后順序,那我們就通過async/await讓它們完全的順序執行即可:

sqlClient.runSql = async (path)=> {const script = fs.readFileSync(path).toString();console.log("Going to run a sql file:");console.log(script);/*** 拆成一句句sql來執行是因為,knex執行一串語句時,會把它們都算進一個事務內* 忽略注釋* 去首尾空格* 按冒號分句* 校驗字串是否為sql語句* @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};`);}
}

輸出結果:

D:\Workstation\gitee-localRepo\express-demo\DatabaseInit>node index.js
Going to run a sql file:
-- 先刪除user表
DROP TABLE IF EXISTS `user`;
-- 定義表結構,并創建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!現在Sequelize也按照我們的意愿完成了重置user表的初始化工作


如果初始化過程中涉及嚴格的先后順序,務必做好同步流甚至回滾機制。此外,在實際項目中,為了項目的代碼規范性,應當將數據庫路徑,初始化腳本路徑都寫在配置文件中,而不是像本節為了方便直接寫在需要調用的js文件中。

下一節-頁面渲染

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35389.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35389.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35389.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于自適應曲線閾值和非局部稀疏正則化的壓縮感知圖像復原研究【自適應曲線閾值去除加性穩態白/有色高斯噪聲】(Matlab代碼實現)

💥💥💞💞歡迎來到本博客????💥💥 🏆博主優勢:🌞🌞🌞博客內容盡量做到思維縝密,邏輯清晰,為了方便讀者。 ??座右銘&a…

什么是媒體代發布?媒體代發布注意事項

傳媒如春雨,潤物細無聲,大家好,我是51媒體網胡老師。 媒體代發布是指將新聞稿或其他宣傳內容委托給專業的媒體代理機構或公司進行發布和推廣的活動。這些機構通常擁有豐富的媒體資源、人脈和經驗,能夠更好地將信息傳遞給目標受眾…

C語言 指針與內存之間的關系

一、內存與字節 一個內存單元一個字節一個地址 整型 int 類型中int類型的字節數是4 且一個字節表示八個bite位 一個二進制數位有著32個bite 所以又可以表示為:一個字節 8個比特位 32位數的二進制數位的八分之一 例如: int a 10; 該表達式…

項目實戰 — 消息隊列(9){編寫demo程序}

消息隊列服務器核心功能就是,提供了虛擬主機,交換機, 隊列,消息等概念的管理,實現三種典型的消息轉發方式,可以實現跨主機/服務器之間的生產者消費模型。 這里,就編寫一個demo,實現…

【實戰講解】數據血緣落地實施

?在復雜的社會分工協作體系中,我們需要明確個人定位,才能更好的發揮價值,數據也是一樣,于是,數據血緣應運而生。 今天這篇文章會全方位的講解數據血緣,并且給出具體的落地實施方案。 一、數據血緣是什么…

JAVA多線程和并發基礎面試問答(翻譯)

JAVA多線程和并發基礎面試問答(翻譯) java多線程面試問題 1. 進程和線程之間有什么不同? 一個進程是一個獨立(self contained)的運行環境,它可以被看作一個程序或者一個應用。而線程是在進程中執行的一個任務。Java運行環境是一個包含了不同的類和程序…

蘇州OV泛域名RSA加密算法https

RSA加密算法是一種非對稱加密算法,它被廣泛應用于信息安全領域。與對稱加密算法不同,RSA加密算法使用了兩個密鑰,一個公鑰和一個私鑰。公鑰可以公開,任何人都可以使用它加密信息,但只有私鑰的持有者才能解密信息。RSA加…

php如何對接偽原創api

在了解偽原創api的各種應用形態之后,我們繼續探討智能寫作背后的核心技術。需要說明的是,智能寫作和自然語言生成、自然語言理解、知識圖譜、多模算法等各類人工智能算法都有緊密的關聯,在百度的智能寫作實踐中,常根據實際需求將多…

全球勞動力革命,Papaya Global 打破薪資界限

員工需求和勞動力結構的進一步變化,只會增加對更加自動化和全面的全球薪資解決方案的需求。 遠程工作潮流與全球勞動力的蓬勃發展,使得企業在全球范圍內,尋找最優秀的人才成為可能。然而,隨之而來的復雜薪資管理挑戰,也…

優雅地處理RabbitMQ中的消息丟失

目錄 一、異常處理 二、消息重試機制 三、錯誤日志記錄 四、死信隊列 五、監控與告警 優雅地處理RabbitMQ中的消息丟失對于構建可靠的消息系統至關重要。下面將介紹一些優雅處理消息丟失的方案,包括異常處理、重試機制、錯誤日志記錄、死信隊列和監控告警等。…

BUUCTF題目Web部分wp(持續更新)

關于SQL注入的一些通用辦法 可以訪問哪些表 如有權限,查詢當前用戶可以訪問的所有表 --Oracle查詢當前用戶可訪問的所有表 select owner, table_name from all_tables order by table_name; --MySQL查詢用戶可訪問的所有數據庫和表 select table_sche…

爬蟲017_urllib庫_get請求的quote方法_urlencode方法_---python工作筆記036

按行來看get請求方式 比如這個地址 上面這個地址復制粘貼過來以后 可以看到周杰倫變成了一堆的Unicode編碼了 所以這個時候我們看,我們說https這里,用了UA反爬,所以這里 我們構建一個自定義的Request對象,里面要包含Us

電腦mfc140u.dll丟失的怎么辦呢?這個方法親測可以解決

修復mfc140u.dll是我最近遇到的一個技術問題,雖然在解決過程中遇到了一些困難,但最終的成功修復讓我對技術的力量有了更深的體會。 首先,我想談談遇到問題時的困惑。當我嘗試運行一個應用程序時,突然彈出一個錯誤提示,…

Docker Dirtypipe(CVE-2022-0847)漏洞復現與分析容器逃逸

安裝環境 ./metarget cnv install cve-2022-0847 --verbose 原理 同臟牛,通過寫只讀內存,對映射的內存做篡改 EXP docker run --rm -it -v $(pwd):/exp --cap-addCAP_DAC_READ_SEARCH ubuntu如果提示 Unknown capability to add: "CAP_CAP_DAC_RE…

第五十二天

HTML5 ●MathML 是數學標記語言,是一種基于XML(標準通用標記語言的子集)的標準,用來在互聯網上書寫數學符號和公式的置標語言。 ●拖放 拖放是一種常見的特性,即抓取對象以后拖到另一個位置。 在 HTML5 中&#xf…

YAMLException: java.nio.charset.MalformedInputException: Input length = 1

springboot項目啟動的時候提示這個錯誤:YAMLException: java.nio.charset.MalformedInputException: Input length 1 根據異常信息提示,是YAML文件有問題。 原因是yml配置文件的編碼有問題。 需要修改項目的編碼格式,一般統一為UTF-8。 或…

分別用python和go語言來實現的風靡一時的2048 游戲,包含完整代碼

目錄 1、Python實現2、Go實現 2048 游戲實現主要包括以下幾個步驟: 創建一個棋盤,通常使用二維列表表示。實現棋子的移動規則,左移、右移、上移、下移。判斷游戲是否結束,即棋盤是否已滿或者無空位可移動。實現游戲界面的顯示。 …

【Android】ViewBinding+DataBinding+MVVM新手快速上手

為什么寫這篇博客 網上大部分博客,代碼量都比較大,把實際的業務都代入進去了 這篇博客的目的,就是為了講解基本原理和使用思路,然后給出一個最簡單的Demo 這里不講解具體用法,那樣篇幅會太長,直接看Demo…

TENNECO EDI 項目——X12與XML之間的轉換

近期為了幫助廣大用戶更好地使用 EDI 系統,我們根據以往的項目實施經驗,將成熟的 EDI 項目進行開源。用戶安裝好知行之橋EDI系統之后,只需要下載我們整理好的示例代碼,并放置在知行之橋指定的工作區中,即可開始使用。 …

YOLOv5入門實踐(3)— 手把手教你如何去劃分數據集

前言:Hello大家好,我是小哥談。數據集標注完成之后,下一步就是對這些數據集進行劃分了。面對繁雜的數據集,如果手動劃分的話,不僅麻煩而且不能保持隨機性。本節課就給大家介紹一種方法,即使用代碼去劃分數據…