【Express.js】数据库初始化
数据库初始化
在软件开发阶段和测试阶段,为了方便调试,我们通常会进行一系列的数据库初始化操作,比如重置数据表,插入记录等等,或者在部署阶段进行数据初始化的操作
根据前面章节介绍过的 knex.js 和 sequelize.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脚本去初始化数据库。
有两条路:
- 运行环境先安装sqlite3客户端,node读取sql脚本内容,node通过
exec去指定目录下,打开sqlite3命令行连接sqlite数据库,同时把sql内容传递过去,在sqlite3中执行sql脚本完成数据库初始化操作 - 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文件的方法:
- 定义
runSql方法的传参和返回
我这里传入sql文件的路径,返回sql语句执行的promise链 - 内部实现,首先通过
fs模块读取sql脚本内容并转为字符串 - 把内容中的注释去掉
- 去掉内容首尾的空格
- 去掉
\r - 去掉
\n(我为了打印sql语段时更加美观,省去了这一步,不影响执行结果) - 把内容按照
;号分割成一个个独立的sql语句字串 - 过滤掉空字串(由每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文件中。
下一节-页面渲染
相关文章:
【Express.js】数据库初始化
数据库初始化 在软件开发阶段和测试阶段,为了方便调试,我们通常会进行一系列的数据库初始化操作,比如重置数据表,插入记录等等,或者在部署阶段进行数据初始化的操作 根据前面章节介绍过的 knex.js 和 sequelize.js&…...
【数理知识】三维空间旋转矩阵的欧拉角表示法,四元数表示法,两者之间的转换,Matlab 代码实现
序号内容1【数理知识】自由度 degree of freedom 及自由度的计算方法2【数理知识】刚体 rigid body 及刚体的运动3【数理知识】刚体基本运动,平动,转动4【数理知识】向量数乘,内积,外积,matlab代码实现5【数理知识】最…...
【业务功能篇63】Springboot聊聊 过滤器和拦截器
过滤器的场景:过滤器通常用于对数据或资源进行筛选、修改或转换的场景。例如,在一个电子商务网站中,用户进行商品搜索时,你可以使用过滤器来过滤特定的商品类别、价格范围或其他条件,以便用户仅看到符合筛选条件的结果…...
提高学生学习效率的模拟考试系统
在如今竞争激烈的社会环境下,提高学生的学习效率显得尤为重要。为了帮助学生评估自己的学习水平并提供有针对性的学习建议,开发一款模拟考试系统是非常必要的。 一、学生信息录入 模拟考试系统首先需要学生信息录入功能。学生可以通过一个简单的表单填…...
解决QWebEngineView在linux下加载本地html失败的问题
通常我们使用QWebEngineView加载本地html文件时,是通过 void load(const QUrl &url) void setUrl(const QUrl &url) 两个函数,传入html的相对或绝对路径,进行加载。 而在linux(uos x86)下运行时,却发现加载失败…...
如何使用Redis实现内容推送功能
导读 在日常使用中,我们经常能看见内容推送功能。 常见的场景有,比如你在bilibili关注了某个up主,当up主发布视频后,就会推送到你的收件箱或者是动态中,让粉丝能够及时得知所关注的人发布了内容。 又比如朋友圈&…...
怎么对视频进行压缩?
怎么对视频进行压缩?视频压缩,我们都知道是将视频文件进行压缩变小的过程,是我们日常办公中较为常用的手段。现如今,在视频技术不断发展与创新的基础上,视频分辨率也在不断提高,进而导致文件占有量也非常大…...
redisson配置类---SpringBoot集成、redis单机和集群模式配置
1项目配置文件: 1.1:pom.xml <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.7</version></dependency> 1.2 application.yml配置…...
瓴羊发布All in One 产品,零售SaaS的尽头是DaaS?
“打破烟囱、化繁为简,让丰富的能力、数据和智能All in One”,这是瓴羊新发布的产品瓴羊One承担的使命,也意味着瓴羊DaaS事业迈入了一个新阶段。 成立伊始,瓴羊就打出了“Not SaaS,But DaaS”旗号,将自己的…...
win10中Docker安装、构建镜像、创建容器、Vscode连接实例
Docker方便一键构建项目所需的运行环境:首先构建镜像(Image)。然后镜像实例化成为容器(Container),构成项目的运行环境。最后Vscode连接容器,方便我们在本地进行开发。下面以一个简单的例子介绍在win10中实现:Docker安装、构建镜像…...
贝锐蒲公英:快速搭建连锁门店监控体系,赋能企业高效管理
随着国民生活水平的提高和零售场景的变革,消费者对于餐饮类目的消费支出不断增加,线下社区生鲜商超作为下沉市场最主要的消费场景之一,蕴藏着巨大价值机会。 对于线下连锁生鲜超市而言,连锁门店多、员工多,门店管理时会…...
c++ WinInet InternetOpenUrl下载中文文件
windows自带的WinInet,几个函数就可以实现http文件下载, 且可获取文件大小,进度条等。 在用WinInet下载文件时,遇到个问题, 如果是中文,下载下来的文件大小为0 英文文件正常,为什么呢? bool WWWFileBuffer(const char* host, const char* path, char* outBuffer, in…...
算法通过村第三关-数组青铜笔记|单调数组
文章目录 前言单调数组问题搜索插入位置:数组合并问题:总结 前言 提示:本份真诚面对自己、坦然无碍面对他人,就是优雅。 数组中的比较经典性问题: 单调数组问题数组合并问题 单调数组问题 参考例子:896. 单调数列…...
Springboot MultipartFile文件上传与下载
yml文件配置是否可以上传及上传附件大小 servlet:multipart:# 允许文件上传enabled: true# 单个文件大小max-file-size: 20MB# 设置总上传的文件大小max-request-size: 50MB /*** param files* param request* Description 上传文件* Throws* Return java.util.List* Date 202…...
js this变量
js this变量 有个比较特殊的箭头函数没有自己的this,而是继承了外部作用域的this...
Ubuntu ip冲突,修改静态IP方法
虚拟机克隆Ubuntu造成的IP地址相同冲突的问题_虚拟机ip冲突怎么解决_昌哥不爱晚睡的博客-CSDN博客...
windows下dll文件的创建详细教程
1、前言 dll文件是啥,就不作过多赘述了。现在直接教大家如何创建与使用dll文件。 本文基于windows系统,使用的编译相关工具为visual studio 2019。 2、创建dll 2.1 创建dll工程 首先打开visual studio,然后选择创建新项目,在搜…...
一些Git Repo
文章目录 Fake-TcpWow Fishing Script模拟券商柜台 Fake-Tcp Fake-Tcp 自己写的一个伪装包测试。 尝试把UDP的包伪装成TCP包,再发送到Internet Wow Fishing Script 魔兽世界钓鱼脚本 自己写的魔兽世界钓鱼脚本,10.0初期钓鱼成功率90%以上。现在关服了…...
【Unity脚本开源】记录鼠标按下的位置和移动的距离来进行物体的旋转,并在鼠标释放后将物体恢复到初始旋转位置
♥️作者:白日参商 🤵♂️个人主页:白日参商主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识,和大家一起努力呀!!! 🎈🎈加油! 加油!…...
金蝶软件实现导入Excel数据分录行信息到单据体分录行中
>>>适合KIS云专业版V16.0|KIS云旗舰版V7.0|K/3 WISE 14.0等版本<<< 金蝶软件中实现[导入Excel数据业务分录行]信息到[金蝶单据体分录]中,在采购订单|采购入库单|销售订单|销售出库单等类型单据中,以少量的必要字段在excel表格中按模板填列好,很方便快捷地从…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
