【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.mzph.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大家好,我是小哥谈。数据集标注完成之后,下一步就是对这些数据集进行划分了。面对繁杂的数据集,如果手动划分的话,不仅麻烦而且不能保持随机性。本节课就给大家介绍一种方法,即使用代码去划分数据…