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")}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/205360.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录二刷 | 栈与队列 | 前 k 个高频元素

代码随想录二刷 &#xff5c; 栈与队列 &#xff5c; 前 k 个高频元素 题目描述解题思路 & 代码实现 题目描述 347.前k个高频元素 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 示例 1: 输入: nu…

前端项目中CDN的一些问题【性能优化篇】

1. CDN的概念 CDN&#xff08;Content Delivery NetWork&#xff0c;内容分发网络&#xff09;&#xff0c;是指利用最靠近每位用户的服务区&#xff0c;更快的将资源发送给用户。 提高用户的访问速度减轻服务器压力提高网站的稳定性和安全性 2. CDN的作用 CDN一般用来托管…

【从零认识ECS云服务器 | 快速上线个人网站】阿里云手动搭建WordPress网站

第一步&#xff1a;部署 LAMP/LNMP 环境&#xff0c;需要在ECS实例中安装操作系统&#xff08;Linux&#xff0c;本例中使用的操作系统版本为CentOS 7.9 64位&#xff09;、Web服务器软件&#xff08;Apache/Nginx&#xff09;、数据库软件&#xff08;MySQL&#xff09;、网站…

GIT GUI使用

文章目录 一、新建本地仓库二、推送&#xff08;push&#xff09; 一、新建本地仓库 在空白处右键&#xff0c;找到GIT GUI here&#xff0c; 如果没有仓库&#xff0c;出现的是这样的&#xff1a; 如果有仓库&#xff0c;在本地仓库里打开就是这样的&#xff1a; 新建本地…

探索低代码的潜力、挑战与未来展望

低代码开发作为一种新兴的开发方式&#xff0c;正在逐渐改变着传统的编程模式&#xff0c;低代码使得开发者无需编写大量的代码即可快速构建各种应用程序。然而&#xff0c;低代码也引发了一系列争议&#xff0c;有人称赞其为提升效率的利器&#xff0c;也有人担忧其可能带来的…

$(document).ready()方法和window.onload有什么区别?

1.触发时间点&#xff1a;$(document).ready() 方法在 DOM 树构建完毕&#xff0c;并且所有的 DOM 元素都可以操作时触发&#xff0c;不需要等待所有的资源&#xff08;如图片&#xff09;都加载完成。而 window.onload 事件是在所有的资源都加载完成后触发。 2.执行顺序&#…

虎牙C++技术面经

虎牙C技术面经 1、虚函数底层 在C中&#xff0c;虚函数的实现涉及到虚函数表&#xff08;Virtual Table&#xff09;的概念。每个含有虚函数的类都会有一个对应的虚函数表&#xff0c;其中存储着指向各个虚函数的地址。当一个对象被创建时&#xff0c;编译器会将该对象的虚函…

代码随想录算法训练营 ---第五十八天

今天开启单调栈的征程。 第一题&#xff1a; 简介&#xff1a; 本题有两种解法&#xff0c;第一种&#xff1a;暴力破解 两层for循环 时间复杂度为O(n^2) 超时了 第二种&#xff1a;单调栈解法也是今天的主角。 单调栈是什么&#xff1f; 单调递增栈&#xff1a;单调递增栈…

卡通渲染总结《三》

接上回 卡通渲染总结《二》的描边技术&#xff0c;接下就是其绘画&#xff08;The Painter&#xff09;的技术。 Painter 的目的是从 3D 模型中生成平面图像。使用这种方法&#xff0c;可以通过改变阴影和高光参数以及着色计算的权重因子来产生各种样式。 阴影部分 单光源 …

PHP中对象数组化

我们在使用ThinkPHP已经Laval框架时&#xff0c;可以像使用数组的方式访问模型对象属性值&#xff0c;为什么我们自己实现的对象却无法通过这种方式访问属性呢&#xff1f;这个功能在PHP中称为对象数组化。 要让一个 PHP 对象可以通过数组方式访问&#xff0c;需要在该对象的类…

docker网络【重点】

一、网络知识 1、桥接模式&#xff1a;用于链接两个不同网络段的设备&#xff0c;是共享通信的一种方式 2、桥接设备&#xff1a;工作在OSI模型的第二层&#xff08;数据链路层&#xff09;。根据MAC地址转发数据帧&#xff0c;类似于交换机&#xff0c;只能转发同一网段&…

状态设计模式

package com.jmj.pattern.state.after;public abstract class LiftState {protected Context context;public void setContext(Context context) {this.context context;}//电梯开启操作public abstract void open();//电梯关闭操作public abstract void close();//电梯运行操…

双目光波导AR眼镜_AR智能眼镜主板PCB定制开发

AR眼镜方案的未来发展潜力非常巨大。随着技术的进步&#xff0c;AR眼镜的光学模块将变得更小巧&#xff0c;像素密度也会增加&#xff0c;实现更高分辨率的画面&#xff0c;甚至能够达到1080P、2K和4K级别的清晰度&#xff0c;从而提升用户的视觉体验。 AR智能眼镜的硬件方面&a…

shell/bash 让vi/vim显示空格,及tab字符

Vim 可以用高亮显示空格和TAB。 文件中有 TAB 键的时候&#xff0c;你是看不见的。要把它显示出来&#xff1a;:set listTAB 键显示为 ^I, $显示在每行的结尾,表示换行&#xff1b;空格仍然显示为空格。:set list 进入List Mode:set nolist 退出List Mode ------------…

河南诗词大会规则和流程

河南省诗词大赛是一场充满诗意的盛会&#xff0c;分为小学组、中学组和社会组。流程包括四个环节&#xff1a;“大浪淘沙” 、“月宫折桂” 、“飞花令”和“诗画南阳”。 比赛前两轮为“大浪淘沙”和“月宫折桂”环节&#xff0c;所有赛手采用平板现场答题&#xff0c;时间为2…

企业培训私有化解决方案PlayEdu

本文应网友 林枫 的要求而折腾&#xff1b; 什么是 PlayEdu &#xff1f; PlayEdu 是一款适用于搭建内部培训平台的开源系统&#xff0c;旨在为企业/机构打造自己品牌的内部培训平台。PlayEdu 基于 Java MySQL 开发&#xff1b;采用前后端分离模式&#xff1b;前端采用 React1…

学习记录---kubernetes中备份和恢复etcd

一、简介 ETCD是kubernetes的重要组成部分&#xff0c;它主要用于存储kubernetes的所有元数据&#xff0c;我们在kubernetes中的所有资源(node、pod、deployment、service等)&#xff0c;如果该组件出现问题&#xff0c;则可能会导致kubernetes无法使用、资源丢失等情况。因此…

git-stash操作

1.保存工作目录中的修改&#xff1a; git stash这个命令将暂存未提交的更改并将工作目录恢复到干净的状态。这些更改可以通过后续的 git stash apply 或 git stash pop 恢复出来。 2.保存修改并添加描述&#xff1a; git stash save "描述"使用此命令&#xff0c;你…

104.进程创建

目录 进程创建相关的函数 获取当前进程的进程ID&#xff08;PID&#xff09; 获取当前进程的父进程ID&#xff08;PPID&#xff09; 创建一个新的进程 fork()剖析 调用格式 创建子进程 子进程与父进程 父子进程执行流 代码演示 进程创建相关的函数 Linux中进程ID为pi…

『亚马逊云科技产品测评』活动征文|AWS云服务器EC2实例实现ByConity快速部署

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 前言 亚马逊是全球最大的在线零售商和云计算服务提供商。AWS云服务器在…