您现在的位置是:首页 > 技术教程 正文

Golang实践录:sqlite的使用

admin 阅读: 2024-03-19
后台-插件-广告管理-内容页头部广告(手机)

本文使用 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 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

读取版本号

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

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 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

以事务方式入库

// 入库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 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

函数开始时,先调用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: --- 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: --- PASS: TestSqlite (1.03s) PASS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

完整代码

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") } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索