大家有任何想法,都可以联系博主沟通。
本系列为实战文章,最终实现的桌面工具软件,获取方式:百度网盘地址:pan.baidu.com/s/1yrl0jYpt…
前言
最近桌面软件需要管理的数据增多,将数据保存在缓存或者本地json文件的方式,已经不能满足频繁的数据存取操作,所以需要寻找本地数据持久化的方案。
一、引读
阅读本文的同学,可能对数据库了解不多,所以这里先简单阐述下数据库,帮助大家更好理解数据库。
首先要理解数据库的本质,是一个文件,文件里存储数据,每次需要操作数据,就操作文件里的数据,完成增删改查,从而实现持久化存储的能力。(都存本地文件了,当然持久)
从存储功能维度来看,数据库的数据文件和我们手动创建txt、json、js来保存数据,并没有什么本质区别。
那我们为什么要选择数据库呢?
因为数据库还提供了软件功能,软件部分负责操作数据库数据文件,从而实现快速、高并发、安全、一致、可拓展、可版本管理地操作数据。数据库提供了如此多强大的功能,肯定不能简单通过鼠标点点点就全部实现,所以它提供了一系列的api,来管理数据。
虽然数据库提供了一系列强大的api,能够协助我们完成所有的数据库运维工作,但是愿意记那么多api去一点点敲命令,除了特殊环境下不得不这样做的情况,可能也只有装x犯能忍受。所以我们还需要一个便捷的可视化操作工具,例如navicat。
了解了数据库的基本情况,我们就可以很容易选择最适合我们的数据库。我的数据库要集成到electron中,运行到每一个PC机而不是服务器,关系与非关系型都可以,功能不需要太复杂,综合考虑,最合适的就是精简轻量的数据库——sqlite。
二、哪些人可以参考本文
如果读者使用的是midwayjs、nestjs等成熟后端框架,那么可能有更便捷的插件方式,将typeorm+sqlite数据库应用于项目,这种情况,一定不要被本文误导,你需要的是去官网寻找方案。
如果读者的开发语言是ts,会有更强大的装饰器语法,更方便的初始化sqlite,更简单地使用orm,不需要完全参考本文,不过本文部分内容可以为你提供思路。
如果读者的开发语言是js,那么完全可以通过本文,完成typeorm+sqlite初始化的所有工作。
三、使用步骤
- 下载sqlite。
npm install sqlite3 --save
- 下载typeorm
npm install typeorm --save
orm可选有很多,选一个自己熟悉的即可。
- 实例化orm。创建一个文件sqliteInit.js,用来统一管理数据库初始化的配置。
const typeorm = require('typeorm')
const path = require('path')
const glob = require('glob');
const database = path.join(process.env.Electron, 'data', 'sqlite', 'database.sqlite')
// 动态获取实体路径
const entities = glob.sync(process.env.Electron+'data/sqlite/entry/*.js');
const dataSource = new typeorm.DataSource({
type: 'sqlite',
database,
entities,
// synchronize: true //自动创建表,否则新增entry时数据库中没有对应的表,设置无效,必须手动创建表
})
module.exports=dataSource
代码解释:
-
const database = path.join(process.env.Electron, 'data', 'sqlite', 'database.sqlite'):获取数据库数据文件地址。第一次运行时,发现没有数据文件,初始化时会自动创建这个数据文件。创建的文件大小为0,当插入数据后,初始大小为50K左右。
-
const entities = glob.sync(process.env.Electron+'data/sqlite/entry/*.js'):我所有的实体对象,都保存在entry文件夹中,所以此处使用glob工具,遍历所有实体对象,如果使用require一个个引入对象,也是可以的。
- 上面只是实例化了orm,如果不调用初始化方法,会报错:“EntityMetadataNotFoundError: No metadata for "xxxx" was found”
在main.js中初始化orm:
const AppDataSource=require('../data/sqlite/sqliteInit')
app.whenReady().then(async () => {
...省略代码
//初始化orm
await AppDataSource.initialize()
})
- 创建对应的实体对象:
const { EntitySchema } = require('typeorm');
// 使用 EntitySchema 来定义实体
module.exports = new EntitySchema({
name: "Service",
tableName: "serviceManage",
columns: {
id: {
primary: true,
type: "int",
generated: true,
},
jarId: {
//对应的jar包id
type: "varchar",
nullable:true
},
name:{
//服务名
type: "varchar",
},
runStatus:{
//运行状态
type: "boolean",
nullable:true
}
},
})
上面代码中,主要是了解创建实体的方法是new EntitySchema,网上这部分资料比较少,这是官网推荐方式。
其中的属性,可以自行查阅。
四、使用typeOrm操作数据库
4.1保存和修改数据
const AppDataSource = require('../sqliteInit.js')
const {nanoid}=require('nanoid')
class ServiceManage {
async addService(option={}) {
const {serviceName} = option
const id = nanoid();
const serviceRepository= AppDataSource.getRepository('Service')
const rs =await serviceRepository.save({
id,
name:serviceName
})
console.log(rs)
return 'rs'
}
}
module.exports= ServiceManage
代码解释:
-
AppDataSource是实例化后的对象,后续所有的api都是通过AppDataSource 对象来操作。
-
nanoid是一个生成随机id的库,类似与uuid,比uuid生成的长度更短,库大小更轻量。
-
AppDataSource.getRepository('Service')可以得到对应实体的实例化对象serviceRepository,serviceRepository对象提供了各种操作api
-
serviceRepository.save是保存数据的方法,返回的是promise对象。
如果保存数据时,发现id已经存在,则会更新修改数据
4.2删除数据
写这篇笔记时,还没有完善功能,所以只跑通一个保存方法,其他方法,参考以往的代码,先简单实现,应该大差不差。
复杂的sql语句,最好借助queryBuilder方式:
async deleteService(id){
const photoRepository = AppDataSource.getRepository('Service')
const rs = await photoRepository.createQueryBuilder('Service')
.delete()
.where(`Service.id = ${id}`)
.execute()
return rs
}
4.3查询数据
查询语法也是盲写,还未验证,为大家提供个思路:
async findServiceList(option) {
const {id, skip, limit} = option
const photoRepository = AppDataSource.getRepository('Service')
let queryBuilder = photoRepository.createQueryBuilder('Service')
const total = await queryBuilder.getCount()
if (id) {
queryBuilder = queryBuilder
.where(`'Service'.id = ${id}`)
}
if (skip) {
queryBuilder
.skip(skip)
.take(limit)
} else if (skip === 0) {
queryBuilder
.take(limit)
}
const rs = await queryBuilder.getMany()
return {
success: true,
total,
data: rs
}
}
skip:跳过多少条数据,limit:查询几条数据,这两个参数是为了实现分页功能。
五、总结
遇到网络资源缺乏的场景,一定要首先去查阅官网,如果官网查阅有难度,再考虑借鉴原创博主的方案,比如一直坚持原创的【中二少年学编程】同学,哈哈哈。