Go服务平台项目(一)数据库表的设计与Gendry库的使用

一、数据库表的设计

在项目过程中操作数据库尽量用mysql命令行进行操作,不过为了提高效率,在建表和改字段信息的时候可以借用可视化工具,查数据和字段用命令行。 在这里插入图片描述

整个项目共有八个数据表,user表和advisor表记录用户和顾问的model字段数据,orders表、service表和review表分别记录订单、服务和订单评论三个实体的数据,adv_coin表和user_coin表用于记录用户和顾问的金币流水记录。以下是数据库设计中需要注意的几个地方:

1.主键Id的设计

从数据库理论而言,每个实体都要设计一个主键Id字段,用于唯一标识该实体的记录。例如用户表设置uid,顾问表设置aid,服务表设置sid。
对于id字段,一般情况下都会给自增,并且会设置自增起点。为了区分各个实体的记录,通常设置不同的区分性强的自增起点。例如在这个项目中,我给uid设置自增起点为60000,aid为80000,sid为50000。对于数据体系庞大的项目,要考虑多位数的id设置。

2.字段的命名

数据库字段的命名采用下划线的方式,如果该字段与该实体是强相关的属性关系,就可以不用在前面加标识符。比如user表中用户的姓名字段,不用命名为user_name,直接用name即可。但如果该字段与该实体没有属性关系,则要在前面加上标识符,例如orders表中的用户名称,用user_name会更合适。

3.字段的非空设置

一般情况下尽量保证表的字段都要求非空,除非该字段的数据确实可以忽略或者只在后续数据操作中添加。
在这里插入图片描述

例如订单表中的answer字段,由于创建订单时没有问题的回答,只有相应顾问接单回复了才会有回答的数据,所以该字段不能设为非空。除类似情况,其他字段都设为了非空。

4.特殊字段处理

项目中经常会遇到一些枚举或者对格式有要求的字段。如性别字段在model中设置了Int枚举,所以表中类型设为Int。如顾问的平均评分字段要求为一位小数且最大为10,类型就可用decimal(2,1)。(decimal(x,y),x表示总位数,y标识小数点后位数)

二、在项目中配置数据库

设计完数据库后,接下来就要在项目中配置数据库了。通常情况下会把数据库的配置信息(端口号、用户名、密码)写在配置文件或者常量文件中,但由于本项目体量不大,我就直接写在了数据库初始化函数中。
首先,我们需要在项目中单独创建一个文件夹来放置数据库配置文件,我这里命名为common,然后创建db.go文件,在里面写数据库初始化和获取数据库指针的函数。

db.go :GO-GIN / common / db.go

package common

import (...)

var DB *sql.DB

func InitDB() *sql.DB {
    
    

	scanner.SetTagName("key")
	
	// 此处 root:password 为自己数据库的用户名:密码, demo为连接的数据库名, 3306为端口号(默认)
	db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/demo?charset=utf8")
	if err != nil {
    
    
		fmt.Println(err)
	} else {
    
    
		fmt.Println("数据库连接成功!")
	}

	DB = db
	return db
}

func GetDB() *sql.DB {
    
    
	return DB
}

配置好后,在main.go文件中添加:

main.go :GO-GIN / main.go

func main() {
    
    
	r := gin.Default()
	
	// 初始化数据库
	common.InitDB()

	// 创建DB实例
	db := common.GetDB()

	// 延迟关闭数据库
	defer db.Close()
	
	// 初始化路由
	r = router.InitRouter(r)
	r.Run(":8000")
}

这样数据库的基本配置就完成了。

三、gendry的使用

Gendry是一个用于辅助操作数据库的Go包。基于go-sql-driver/mysql,它提供了一系列的方法来为你调用标准库database/sql中的方法准备参数。(官方链接跳转)

Gendery主要分为3个独立的部分:

1.manager:

主要用来初始化连接池(也就是sql.DB对象),设置各种参数,因此叫manager。你可以设置任何go-sql-driver/mysql驱动支持的参数。

2.builder:

builder顾名思义,就是构建生成sql语句。手写sql虽然直观简单,但是可维护性差,最主要的是硬编码容易出错。builder不是一个ORM,它只是提供简单的API帮你生成sql语句。

3.scanner:

执行了数据库操作之后,要把返回的结果集和自定义的struct进行映射。Scanner提供一个简单的接口通过反射来进行结果集和自定义类型的绑定。scanner进行反射时会使用结构体的tag,默认使用的tagName是ddb:“xxx”,你也可以自定义。
本项目设置的scannar映射tag为“key”,在InitDB()函数中进行配置:

scanner.SetTagName("key")

对于某个字段,设置的key就是对应的数据表中的字段名。scanner函数会据此映射到struct上。

四、函数的封装与使用

函数封装习惯保存在单独文件夹中,此项目命名为utils。创建gen.go文件,保存gendry语句的封装。

gen.go :GO-GIN / utils / gen.go

1.查找函数 - 通用 - 绑定结构体

import (
	"database/sql"
	"strconv"
	...
	qb "github.com/didi/gendry/builder"
	"github.com/didi/gendry/scanner"
	_ "github.com/go-sql-driver/mysql"
	"go.uber.org/zap"
)

func GenSelectOne(obj interface{
    
    }, table string, where map[string]interface{
    
    }, selectField []string) error {
    
    
	
	var db = common.GetDB()

	cond, vals, _ := qb.BuildSelect(table, where, selectField)

	rows, err := db.Query(cond, vals...)

	if err != nil {
    
    
		common.Log.Info(config.ErrSelect.Msg, zap.Error(err))
		return err
	}

	defer rows.Close()

	if err := scanner.Scan(rows, obj); err != nil {
    
    
		common.Log.Info(config.ErrScan.Msg, zap.Error(err))
		return err
	}

	return err
}

首先拿到数据库对象,然后使用gendry-builderBuildSelect函数构建查询语句cond和查询参数vals,然后再用mysql的原生查询语句db.Query进行查询,得到结果 rows。(注意:对rows操作完后要关闭该rows,具体原因见官网)。最后用gendry-scannar中的Scan函数,将结果绑定到传入的结构体obj中。

2.更新函数 - 通用 - 绑定结构体

func GenUpdateNew(obj interface{
    
    }, table string, where map[string]interface{
    
    }) error {
    
    
	
	var db = common.GetDB()
	
	update := map[string]interface{
    
    }{
    
    }
	
	//将传入的struct转map,用于更新
	MapByReflect(update, obj)
	
	cond, vals, _ := qb.BuildUpdate(table, where, update)

	_, err := db.Exec(cond, vals...)

	if err != nil {
    
    
		common.Log.Info(config.ErrUpdate.Msg, zap.Error(err))
	}

	return err
}

更新函数中会将传入的struct转换成一个map,然后放到更新语句中来完成更新。转化函数会在后续的文章中详细讲解。

3.插入函数 - 通用 - 绑定结构体

// 插入
func GenInsertNew(obj interface{
    
    }, table string) error {
    
    
	
	var db = common.GetDB()
	
	data := map[string]interface{
    
    }{
    
    }
	
	var insertData []map[string]interface{
    
    }

	MapByReflect(data, obj)

	insertData = append(insertData, data)

	cond, vals, _ := qb.BuildInsert(table, insertData)

	_, err := db.Exec(cond, vals...)

	if err != nil {
    
    
		common.Log.Info(config.ErrInsert.Msg, zap.Error(err))
	}

	return err
}

4.查找函数 - 定制 - 绑定结构体列表

// 多选 - 顾问列表
func GenSelectAdvisor(objArray *[]model.Advisor, table string, where map[string]interface{
    
    }, selectField []string) error {
    
    
	
	var db = common.GetDB()

	cond, vals, _ := qb.BuildSelect(table, where, selectField)

	rows, err := db.Query(cond, vals...)

	if err != nil {
    
    
		common.Log.Info(config.ErrSelect.Msg, zap.Error(err))
		return err
	}

	defer rows.Close()
	
	if err := scanner.Scan(rows, objArray); err != nil {
    
    
		common.Log.Info(config.ErrScan.Msg, zap.Error(err))
		return err
	}

	return err
}

当我们需要查询多个实体时,就要传入一个struct数组。但经过多次尝试,发现无法统一定义入参为[]interface,只能对入参和函数进行定制。上述查询函数为定制的查询顾问列表函数,传入一个顾问结构体数组并绑定。其他类似函数只需要修改一下入参的结构体类型即可。

5.函数的使用

user.go :GO-GIN / service / user.go

import (
	"xxx/go-gin/common"
	"xxx/go-gin/config"
	"xxx/go-gin/model"
	"xxx/go-gin/utils"
	"go.uber.org/zap"
)

// 用户获取顾问列表
func GetAdvisorList(advisorList *[]model.Advisor) error {
    
    
	where := map[string]interface{
    
    }{
    
    }

	if err := utils.GenSelectAdvisor(advisorList, "advisor", where, config.AdvSelectAll); err != nil {
    
    
		common.Log.Info(config.ErrSelect.Msg, zap.Error(err))
		return err
	}
	return nil
}

数据库操作的语句使用在Service层,只返回一个error。在Service层只需要对error进行日志打印,然后返回到Controller层就行。具体逻辑会在后续文章中详细展开讲。

五、总结与反思

数据库的设计和操作语句的编写封装是整个项目的基础,很多细节都需要注意,不然到后面又回来返工修改,效率会大大降低。
同时,数据流最好依赖于struct结构体,把操作的数据与相应的struct进行绑定,然后进行数据传递和下一步处理,这样比用map转来转去的效率高很多。

猜你喜欢

转载自blog.csdn.net/weixin_43363720/article/details/125346515
今日推荐