本文使用 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 := false sqlstr := 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.nullstring err := results.scan(&item1, &item2) if err != nil { fmt.println("scan error: ", err) break } if !item1.valid || !item2.valid { continue } version = item1.string updatetime = 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.tx tx, 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 = nil tx.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()
开始事务,分别调用insertdbversion
和insertdbdetail
入库,只有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 test import ( "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 := false sqlstr := 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.nullstring err := results.scan(&item1, &item2) if err != nil { fmt.println("scan error: ", err) break } if !item1.valid || !item2.valid { continue } version = item1.string updatetime = 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 := tablelist sqlstr := 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 = idx item.version = version item.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 := tableversion sqlstr := 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.tx tx, 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 = nil tx.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_t tmp.id = 100 tmp.version = "100" tmp.name = "latelee" tmp.city = "梧州" gxlist = append(gxlist, tmp) tmp = infolist_t{} tmp.id = 250 tmp.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") } }
总结
到此这篇关于golang对sqlite3数据库进行操作的文章就介绍到这了,更多相关golang使用sqlite内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论