当前位置: 首页 > news >正文

Golang实践录:sqlite的使用

本文使用 Golang 对 sqlite3 数据库进行操作。

概述

Golang 操作数据库有统一的接口,当然也有xorm这样的库,笔者接触的项目不大,对sql自由组装有要求,同时也会将这些sql用于数据库客户端查询,因此偏向于使用原生的sql。

为方便起见,本文只针对sqlite进行连接、读写、事务的测试。理论上可以扩展到其它数据库的操作。

技术小结

  • 引入的包有"database/sql"_ "github.com/mattn/go-sqlite3"
  • 使用sql.Open打开数据库,对于sqlite3,不存在目标文件时,会自创并使用。
  • 事务相关接口有:开始SQLDB.Begin()、提交tx.Commit()、回滚tx.Rollback()、结束SQLDB.Close()

设计

为让测试代码接近业务逻辑,设计场景如下:

  • 设2个数据表:一为版本号表,一为信息明细表。
  • 版本号更新了(如通过http下载数据,数据中有版本号),才更新明细表。程序通过读取数据库表的版本号进行判断。
  • 允许上述数据表为空或不存在,由于sqlite3是基于文件的,也允许sqlite文件不存在。
  • 同时写上述2个数据表,同时成功了方认为成功,因此使用到事务机制。

源码分析

完整代码见文后,本节按实现功能列出要点。

连接数据库

func CreateSqlite3(dbname string, create bool) (sqldb *sql.DB, err error) {if create == false && !IsExist(dbname) {return nil, errors.New("open database failed: " + dbname + " not found")}sqldb, err = sql.Open("sqlite3", dbname)if err != nil {return nil, errors.New("open database failed: " + err.Error())}err = sqldb.Ping()if err != nil {return nil, errors.New("connect database failed: " + err.Error())}fmt.Println("connect to ", dbname, "ok")return
}

读取版本号

读取版本号,如果不存在,则创建对应的表。

func readOrCreateDBTable(sqldb *sql.DB) (version, updateTime string) {needCreate := falsesqlstr := fmt.Sprintf(`select version, updateTime from %v order by version desc limit 1`,tableVersion)fmt.Printf("run sql: [%v]\n", sqlstr)results, err := sqldb.Query(sqlstr)if err != nil {if strings.Contains(err.Error(), "no such table") {needCreate = true} else {fmt.Println("query error: ", err)return}}if !needCreate {for results.Next() {var item1, item2 sql.NullStringerr := results.Scan(&item1, &item2)if err != nil {fmt.Println("scan error: ", err)break}if !item1.Valid || !item2.Valid {continue}version = item1.StringupdateTime = item2.String}defer results.Close()} else {fmt.Println("not found table, will create it.")for _, item := range sqlarr {_, err := sqldb.Exec(item)if err != nil {fmt.Printf("Exec sql failed: [%v] [%v] \n", err, item)}}}return
}

以事务方式入库

// 入库2个表,以事务方式
func insertDBBatch(gxList []InfoList_t, version string) (err error) {SQLDB, err := CreateSqlite3(dbServer, false)if err != nil {// fmt.Println(err.Error())return err}var tx *sql.Txtx, err = SQLDB.Begin()if err != nil {err = errors.New("begin sql error: " + err.Error())return err}defer func() {if err != nil {err = errors.New("exec sql failed rollback: " + err.Error())tx.Rollback()} else {err = niltx.Commit()}// 延时一会,关闭Sleep(1000)SQLDB.Close()}()err = insertDBVersion(tx, version)if err != nil {return}err = insertDBDetail(tx, gxList, version)if err != nil {return}return
}

函数开始时,先调用SQLDB.Begin()开始事务,分别调用insertDBVersioninsertDBDetail入库,只有2者同时成功,才调用tx.Commit()提交事务,否则调用tx.Rollback()回滚。提交事务或回滚,通过Golang的defer机制实现,逻辑较清晰。

测试

测试日志如下:

go test -v -run TestSqlite没有数据库文件
test of sqlte3...
connect to  foobar.db3 ok
run sql:
select version, updateTime from myVersion order by version desc limit 1
not found table, will create it.
got db version [] update time []
connect to  foobar.db3 ok
insert db version [] at: [2023-12-02 10:42:18]
insert result:  <nil>
--- PASS: TestSqlite (1.04s)
PASS已有数据但版本较新
test of sqlte3...
connect to  foobar.db3 ok
run sql: [select version, updateTime from myVersion order by version desc limit 1]
got db version [20231202] update time [2023-12-02T10:48:20Z]
connect to  foobar.db3 ok
insert db version [20231203] at: [2023-12-02 10:48:47]
insert result:  <nil>
--- PASS: TestSqlite (1.03s)
PASS

完整代码

package testimport ("database/sql""errors""fmt""os""strings""testing""time""webdemo/pkg/com"_ "github.com/mattn/go-sqlite3"
)var (// 数据库文件名及表名dbServer     string = "foobar.db3"tableVersion string = "myVersion"tableList    string = "myList"
)// 信息表 结构体可对于json风格数据传输解析
type InfoList_t struct {Id         int    `json:"-"`Version    string `json:"-"`Name       string `json:"-"`City       string `json:"-"`UpdateTime string `json:"-"`
}var sqlarr []string = []string{// 版本号`CREATE TABLE "myVersion" ("version" VARCHAR(20) NOT NULL,"updateTime" datetime DEFAULT "",PRIMARY KEY ("version"));`,// 信息表`CREATE TABLE "myList" ("id" int NOT NULL,"version" VARCHAR(20) NOT NULL,"name" VARCHAR(20) NOT NULL,"city" VARCHAR(20) NOT NULL,"updateTime" datetime DEFAULT "",PRIMARY KEY ("id"));`,
}func IsExist(path string) bool {_, err := os.Stat(path)return err == nil || os.IsExist(err)
}func Sleep(ms int) {time.Sleep(time.Duration(ms) * time.Millisecond)
}func CreateSqlite3(dbname string, create bool) (sqldb *sql.DB, err error) {if create == false && !IsExist(dbname) {return nil, errors.New("open database failed: " + dbname + " not found")}sqldb, err = sql.Open("sqlite3", dbname)if err != nil {return nil, errors.New("open database failed: " + err.Error())}err = sqldb.Ping()if err != nil {return nil, errors.New("connect database failed: " + err.Error())}fmt.Println("connect to ", dbname, "ok")return
}func readOrCreateDBTable(sqldb *sql.DB) (version, updateTime string) {needCreate := falsesqlstr := fmt.Sprintf(`select version, updateTime from %v order by version desc limit 1`,tableVersion)fmt.Printf("run sql: [%v]\n", sqlstr)results, err := sqldb.Query(sqlstr)if err != nil {if strings.Contains(err.Error(), "no such table") {needCreate = true} else {fmt.Println("query error: ", err)return}}if !needCreate {for results.Next() {var item1, item2 sql.NullStringerr := results.Scan(&item1, &item2)if err != nil {fmt.Println("scan error: ", err)break}if !item1.Valid || !item2.Valid {continue}version = item1.StringupdateTime = item2.String}defer results.Close()} else {fmt.Println("not found table, will create it.")for _, item := range sqlarr {_, err := sqldb.Exec(item)if err != nil {fmt.Printf("Exec sql failed: [%v] [%v] \n", err, item)}}}return
}func insertDBDetail(tx *sql.Tx, gxList []InfoList_t, version string) (err error) {tablename := tableListsqlstr := fmt.Sprintf(`DELETE FROM %v`, tablename)stmt, err := tx.Prepare(sqlstr)if err != nil {err = errors.New("prepare for [" + sqlstr + "] failed: " + err.Error())return}_, err = stmt.Exec()if err != nil {err = errors.New("delete " + tablename + "failed: " + err.Error())return}sqlstr = fmt.Sprintf(`INSERT OR REPLACE INTO %v 
(id, version, name, city, updateTime) 
VALUES (?, ?, ?, ?, ?)`,tablename)stmt, _ = tx.Prepare(sqlstr)for _, item := range gxList {// item.Id = idxitem.Version = versionitem.UpdateTime = com.GetNowDateTime("YYYY-MM-DD HH:mm:ss")_, err = stmt.Exec(item.Id, item.Version, item.Name, item.City, item.UpdateTime)if err != nil {err = errors.New("insert " + tablename + "failed: " + err.Error())return}}return// debug 制作bug// TODO 制作锁住,制作语法错误err = errors.New("database is locked")return
}func insertDBVersion(tx *sql.Tx, version string) (err error) {tablename := tableVersionsqlstr := fmt.Sprintf(`DELETE FROM %v`, tablename)stmt, err := tx.Prepare(sqlstr)if err != nil {err = errors.New("prepare for [" + sqlstr + "] failed: " + err.Error())return}_, err = stmt.Exec()if err != nil {err = errors.New("delete " + tablename + " failed: " + err.Error())return}sqlstr = fmt.Sprintf(`INSERT OR REPLACE INTO %v (version, updateTime) VALUES (?, ?)`, tablename)stmt, err = tx.Prepare(sqlstr)if err != nil {err = errors.New("prepare for [" + sqlstr + "] failed: " + err.Error())return}updateTime := com.GetNowDateTime("YYYY-MM-DD HH:mm:ss")fmt.Printf("insert db version [%v] at: [%v]\n", version, updateTime)_, err = stmt.Exec(version, updateTime)if err != nil {err = errors.New("insert " + tablename + "failed: " + err.Error())return}return
}// 入库2个表,以事务方式
func insertDBBatch(gxList []InfoList_t, version string) (err error) {SQLDB, err := CreateSqlite3(dbServer, false)if err != nil {// fmt.Println(err.Error())return err}var tx *sql.Txtx, err = SQLDB.Begin()if err != nil {err = errors.New("begin sql error: " + err.Error())return err}defer func() {if err != nil {err = errors.New("exec sql failed rollback: " + err.Error())tx.Rollback()} else {err = niltx.Commit()}// 延时一会,关闭Sleep(1000)SQLDB.Close()}()err = insertDBVersion(tx, version)if err != nil {return}err = insertDBDetail(tx, gxList, version)if err != nil {return}return
}//
func makeData() (gxList []InfoList_t) {var tmp InfoList_ttmp.Id = 100tmp.Version = "100"tmp.Name = "latelee"tmp.City = "梧州"gxList = append(gxList, tmp)tmp = InfoList_t{}tmp.Id = 250tmp.Version = "250"tmp.Name = "latelee"tmp.City = "岑溪"gxList = append(gxList, tmp)return
}// 读取基础信息,尝试创建表
func readDBVersion() (version, datetime string) {SQLDB, err := CreateSqlite3(dbServer, true)if err != nil {fmt.Println(err.Error())return}version, datetime = readOrCreateDBTable(SQLDB)SQLDB.Close()return
}
func TestSqlite(t *testing.T) {fmt.Println("test of sqlte3...")// 1 尝试获取数据表的版本号(可能为空)version, datetime := readDBVersion()fmt.Printf("got db version [%v] update time [%v]\n", version, datetime)// 2 模拟业务:自定义版本号,较新时,才入库myVer := "20231202"if myVer > version {data := makeData()err := insertDBBatch(data, myVer)fmt.Println("insert result: ", err)} else {fmt.Println("db is newest, do nothing")}}

相关文章:

  • kettle作业发送@163邮件
  • 在安全环境中使用虚拟化进行隔离——Armv8.4上的安全世界软件架构
  • Nacos多数据源插件
  • Windows 基于 VMware 虚拟机安装银河麒麟高级服务器操作系统
  • 添加新公司代码的配置步骤-Part2
  • 禁奥义·SQL秘籍
  • Java架构师系统架构设计服务拆分应用
  • 汽车软件大时代,如何提升软件工程创新力?
  • LoadBalancer将服务暴露到外部实现负载均衡purelb-layer2模式配置介绍
  • 百度推送收录工具-免费的各大搜索引擎推送工具
  • 软件工程-(可行性分析、需求分析)
  • 3D模型材质编辑
  • 轻量封装WebGPU渲染系统示例<40>- 多层材质的Mask混合(源码)
  • Ubuntu systemd-analyze命令(系统启动性能分析工具:分析系统启动时间,找出可能导致启动缓慢的原因)
  • shell命令学习(1)——(待完善)
  • 分享的文章《人生如棋》
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Mysql数据库的条件查询语句
  • MySQL用户中的%到底包不包括localhost?
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • PHP 小技巧
  • python_bomb----数据类型总结
  • 机器学习学习笔记一
  • 聊一聊前端的监控
  • 如何实现 font-size 的响应式
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • #nginx配置案例
  • $.proxy和$.extend
  • (28)oracle数据迁移(容器)-部署包资源
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (windows2012共享文件夹和防火墙设置
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (十一)手动添加用户和文件的特殊权限
  • (四) Graphivz 颜色选择
  • (四)js前端开发中设计模式之工厂方法模式
  • (五)网络优化与超参数选择--九五小庞
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (一)十分简易快速 自己训练样本 opencv级联haar分类器 车牌识别
  • (转)C#调用WebService 基础
  • **《Linux/Unix系统编程手册》读书笔记24章**
  • .net core docker部署教程和细节问题
  • .Net Core 生成管理员权限的应用程序
  • .Net Core 微服务之Consul(二)-集群搭建
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .net的socket示例
  • .net分布式压力测试工具(Beetle.DT)
  • /dev下添加设备节点的方法步骤(通过device_create)
  • @ 代码随想录算法训练营第8周(C语言)|Day57(动态规划)
  • @hook扩展分析
  • [AI]ChatGPT4 与 ChatGPT3.5 区别有多大
  • [C#学习笔记]LINQ