当前位置: 代码网 > it编程>前端脚本>Golang > Golang对sqlite3数据库进行操作实践记录

Golang对sqlite3数据库进行操作实践记录

2024年05月18日 Golang 我要评论
本文使用 golang 对 sqlite3 数据库进行操作。概述golang 操作数据库有统一的接口,当然也有xorm这样的库,笔者接触的项目不大,对sql自由组装有要求,同时也会将这些sql用于数据

本文使用 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()开始事务,分别调用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 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内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com