iris 数据库配置及连接封装

iris 数据库配置及连接封装

Posted by 锐玩道 on April 25, 2021

如果❤️我的文章有帮助,欢迎点赞、关注。这是对我继续技术创作最大的鼓励。更多往期文章在我的个人博客

上一节讲完项目结构 & 生成数据表映射之后,接下来就是项目连接数据库操作数据库的代码封装:

连接数据库配置

数据库配置的主要目的就是为了把 Golang 链接数据库链接

“[User]:[Pwd]@tcp([Host]:[Port])/[DbName]?charset=[DbCharset]”

中的各项参数,变成结构体可标准配置。比如:

type DbConf struct {
	Host string
	Port int
	User string
	Pwd string
	DbName string
	DbCharset string
}

工业项目一般都涉及多人开发,开发维护的时候为了统一管理。开发组内都会有自己的代码标准,所以这也是项目需要这配置那配置的配置化原因

真正链接数据库的时候,使用fmt格式化输出函数,就可以轻易获得链接数据库链接

const DriverName  = "mysql"

var MasterDbConf DbConf = DbConf{
	Host : "127.0.0.1",
	Port : 3306,
	User : "root",
	Pwd : "",
    DbName : "superstar",
    DbCharset: "utf8",
}

conf := MasterDbConf
driverSource := fmt.Sprintf(
    "%s:%s@tcp(%s:%d)/%s?charset=%s",
    c.User, c.Pwd, c.Host, c.Port, c.DbName, c.DbCharset
)
fmt.Println(driverSource)

masterEngine, err := xorm.NewEngine(conf.DriverName, driverSource)
if err != nil {
    log.Fatal("dbhelper.DbInstanceMaster err:", err)
    return nil
}

配置可以不只一份,比如主从配置,读写分离。数据同步交给数据库主从同步,读取数据xorm.NewEngine注意使用从库配置即可:

var SlaveDbConf DbConf = DbConf{
	Host : "127.0.0.1",
	Port : 3306,
	User : "root",
	Pwd : "",
	DbName : "superstar",
    DbCharset: "utf8",
}

到了实操阶段,我们在项目根目录下的conf文件夹建db.go文件:

conf/db.go

package conf

const DriverName  = "mysql"

type DbConf struct {
	Host string
	Port int
	User string
	Pwd string
	DbName string
}

var MasterDbConf DbConf = DbConf{
	Host : "127.0.0.1",
	Port : 3306,
	User : "root",
	Pwd : "",
	DbName : "superstar",
}

var SlaveDbConf DbConf = DbConf{
	Host : "127.0.0.1",
	Port : 3306,
	User : "root",
	Pwd : "",
	DbName : "superstar",
}

连接数据库方法封装

封装好数据库配置之后,为了使链接数据库方法变得一致,有利于日后统一管理。于是把上面提及的数据库链接方式封装成函数,就能在需要连接数据库时建立新的数据库链接。方便调用:

package datasource

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"log"
	"superstar/conf"
	"sync"
	"xorm.io/xorm"
)

func InstanceMaster() *xorm.Engine {
	c := conf.MasterDbConf
	driverSource := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8",
		c.User, c.Pwd, c.Host, c.Port, c.DbName)
	fmt.Println(driverSource)

	masterEngine, err := xorm.NewEngine(conf.DriverName, driverSource)
	if err != nil {
		log.Fatal("dbhelper.DbInstanceMaster err:", err)
		return nil
	}

	return masterEngine
}

但每次操作数据库是,都建立新的数据库链接。不仅浪费数据库连接池资源、还容易在并发高情况下造成无法链接数据库。

于是我们需要一个数据库链接对象masterEngine,在链接数据库前,检查对象是否为空,不为空就复用数据库链接;为空则重新寄哪里建立数据库链接:

func InstanceMaster() *xorm.Engine {
	if masterEngine != nil {
		return masterEngine
	}

	c := conf.MasterDbConf
	driverSource := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8",
		c.User, c.Pwd, c.Host, c.Port, c.DbName)
	fmt.Println(driverSource)

	masterEngine, err := xorm.NewEngine(conf.DriverName, driverSource)
	if err != nil {
		log.Fatal("dbhelper.DbInstanceMaster err:", err)
		return nil
	}

	return masterEngine
}

并发情况下数据库链接

但这里还有一个问题:

if masterEngine != nil {
	return masterEngine
}

如果一下子,进来 10 个请求。第 1 个请求的 masterEngine 没来得及生成,导致后面 9 个请求也需要重复实例化 masterEngine 这种情况也不合理。

为了避免这种情况,我们在实例化 masterEngine 时加锁:

if masterEngine != nil {
	return masterEngine
}

lock.Lock()
defer lock.Unlock() // 最后解锁

协程情况下数据库链接

协程时,上面 10 个请求一起进来,每个都到 lock.Lock() 加锁挂起。等第 1 个请求解锁后,后面 9 个请求也逐个实例化 masterEngine 这种情况也不合理。

为了避免这种情况,我在再加锁之后再次 判断 masterEngine 是否已存在:

if masterEngine != nil {
	return masterEngine
}

lock.Lock()
defer lock.Unlock()

// 协程时,上面十个一起进来,每个都到 lock.Lock() 加锁挂起。这里避免 第一个加锁然后创建实例后,每个协程都创建实例
if masterEngine != nil {
	return masterEngine
}

到了实操阶段,我们在项目根目录下的datasource文件夹建dbhelper.go文件:

datasource/dbhelper.go

package datasource

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"log"
	"superstar/conf"
	"sync"
	"xorm.io/xorm"
)

// 数据库单例 dbhelper.go
var (
	masterEngine *xorm.Engine
	slaveEngine  *xorm.Engine
	lock         sync.Mutex
)

func InstanceMaster() *xorm.Engine {
	if masterEngine != nil {
		return masterEngine
	}

	lock.Lock()
	defer lock.Unlock()

	if masterEngine != nil {
		return masterEngine
	}

	c := conf.MasterDbConf
	driverSource := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8",
		c.User, c.Pwd, c.Host, c.Port, c.DbName)
	fmt.Println(driverSource)

	masterEngine, err := xorm.NewEngine(conf.DriverName, driverSource)
	if err != nil {
		log.Fatal("dbhelper.DbInstanceMaster err:", err)
		return nil
	}

	// DEBUG 模式,打印全部sql, 帮助对比 orm 与 sql 执行对照关系
	masterEngine.ShowSQL(false)
	masterEngine.SetTZLocation(conf.SysTimeLocation)

	return masterEngine
}