![在这里插入图片描述](https://img-blog.csdnimg.cn/20210127214321803.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDY2MDY2,size_16,color_FFFFFF,t_70#pic_center)
今天是【7天从零实现TORM框架】的第五天,主要任务是:
- torm中数据库配置信息API编写。
- 利用 client 类包装用CRUD操作API,代码200行左右。
若对Go中反射的使用不了解的话,我写了三篇关于反射的文章,给小伙伴提供参考,足以应对本项目中所使用的反射知识点。
- go反射第一弹:[https://mp.weixin.qq.com/s/F8yZyqC5UwoewsX0THqy1w](https://mp.weixin.qq.com/s/F8yZyqC5UwoewsX0THqy1w)
- go反射第二弹:[https://mp.weixin.qq.com/s/lgZykTL8ls6aG0OMNSbZMw](https://mp.weixin.qq.com/s/lgZykTL8ls6aG0OMNSbZMw)
- go反射第三弹:[https://mp.weixin.qq.com/s/vFt06c9herwTrx1LTxNaKg](https://mp.weixin.qq.com/s/vFt06c9herwTrx1LTxNaKg)
>源代码:在【迈莫coding】中回复关键字「 torm 」获取github地址链接.
后续会为【七天从零实现TORM框架】录制视频,文章+视频+代码
# 数据库配置操作API
高级语言与数据库进行交互时,首先避免不了对数据库的配置,所以首先编写数据库的配置信息API,代码存储在根目录下的 client.go 文件中,代码如下所示:
```go
// client.go
package session
import (
"context"
"database/sql"
"fmt"
"reflect"
log "github.com/sirupsen/logrus"
)
// 数据库配置信息
type Settings struct {
DriverName string
User string
Password string
Database string
Host string
Options map[string]string
MaxOpenConns int
MaxIdleConns int
LoggingEnabled bool
}
type Client struct {
db *sql.DB
session *Session
}
// 数据库配置组装
func (s *Settings) DataSourceName() string {
queryString := ""
for key, value := range s.Options {
queryString += key + "=" + value + "&"
}
ustr := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", s.User, s.Password, s.Host, s.Database, queryString)
return ustr
}
// 初始化
func NewClient(settings Settings) (c *Client, err error) {
db, err := sql.Open(settings.DriverName, settings.DataSourceName())
if err != nil {
log.Error(err)
return
}
// Send a ping to make sure the database connection is alive.
if err = db.Ping(); err != nil {
log.Error(err)
return
}
c = &Client{db: db}
c.session = NewSession(db)
log.Info("Connect database success")
return
}
// close
func (c *Client) Close() {
if err := c.db.Close(); err != nil {
log.Error("Failed to close database")
}
log.Info("Close database success")
}
```
**代码说明**
- 第13~23行: settings 结构体存储数据库配置信息
- 第31~38行: DataSourceName 方法用于数据库配置信息组装
# 代码演示
```go
package session
import (
"context"
"testing"
log "github.com/sirupsen/logrus"
)
func Newclient() (client *Client, err error) {
// 定义mysql配置信息
setting := Settings{
DriverName: "mysql",
User: "root",
Password: "12345678",
Database: "po",
Host: "127.0.0.1:3306",
Options: map[string]string{"charset": "utf8mb4"},
}
// 调用client.go文件中的NewClient()方法
return NewClient(setting)
}
func TestSession_Insert(t *testing.T) {
_, _ := Newclient()
}
```
# CRUD操作API
torm提供了给用户一套API,可以通过简单的语法就可以构建出sql语句,完成对应功能,其设计代码如下:
```go
// 新增数据API
func (s *Client) Insert(ctx context.Context, statement *Statement) (int64, error) {
sql := statement.clause.sql
vars := statement.clause.params
result, err := s.session.Raw(sql, vars...).Exec()
if err != nil {
return 0, err
}
return result.RowsAffected()
}
// 查询语句API
func (s *Client) FindOne(ctx context.Context, statement *Statement, dest interface{}) (err error) {
// step0:
if reflect.TypeOf(dest).Kind() != reflect.Ptr || reflect.ValueOf(dest).IsNil() {
return fmt.Errorf("dest is not a ptr or nil")
}
destSlice := reflect.Indirect(reflect.ValueOf(dest))
destValue := reflect.ValueOf(dest).Elem()
if destValue.Kind() != reflect.Struct {
return fmt.Errorf("dest is not a struct")
}
// step1: 拼接完整SQL语句
createFindSQL(statement)
// step2: 进行与数据库交互
rows := s.session.Raw(statement.clause.sql, statement.clause.params...).QueryRow()
destType := reflect.TypeOf(dest).Elem()
// step3: 获取结果存储的结构体信息
schema := StructForType(destType)
// 获取指针指向的元素信息
destVal := reflect.New(destType).Elem()
// 结构体字段
var values []interface{
// 遍历结构体字段名
for _, name := range schema.FieldNames {
values = append(values, destVal.FieldByName(name).Addr().Interface())
}
// step4: 获取结果集
if err := rows.Scan(values...); err != nil {
log.Info(err)
return err
}
// step5: 结果集赋值
destSlice.Set(destVal)
return nil
}
func (s *Client) FindAll(ctx context.Context, statement *Statement, dest interface{}) (err error) {
// step0: 参数类型校验
log.Info(reflect.TypeOf(dest).Kind())
if reflect.TypeOf(dest).Kind() != reflect.Ptr || reflect.ValueOf(dest).IsNil() {
return fmt.Errorf("dest is not a ptr or nil")
}
destSlice := reflect.ValueOf(dest).Elem()
destType := destSlice.Type().Elem()
// step1:拼接完整SQL语句
createFindSQL(statement)
// step2:进行与数据库交互
rows, err := s.session.Raw(statement.clause.sql, statement.clause.params...).Query()
if err != nil {
return err
}
if destType.Kind() == reflect.Ptr {
destType = destType.Elem()
}
// step3:获取结果集存储的结构信息
schema := StructForType(destType)
// step4:循环获取符合查询条件的结果集,将其赋值给参数dest
for rows.Next() {
// 获取指针指向的元素信息
dest := reflect.New(destType).Elem()
// 结构体字段
var values []interface{
// 每次获取最终结果存储的地址信息
for _, name := range schema.FieldNames {
values = append(values, dest.FieldByName(name).Addr().Interface())
}
// 每次遍历将返回结果赋值给values
if err := rows.Scan(values...); err != nil {
return err
}
// 结果集赋值
destSlice.Set(reflect.Append(destSlice, dest))
}
return nil
}
// 删除操作 API
func (s *Client) Delete(ctx context.Context, statement *Statement) (int64, error) {
createDeleteSQL(statement)
log.Info(statement.clause.params)
res, err := s.session.Raw(statement.clause.sql, statement.clause.params...).Exec()
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// 更新操作 API
func (s *Client) Update(ctx context.Context, statement *Statement) (int64, error) {
createUpdateSQL(statement)
log.Info(statement.clause.params)
res, err := s.session.Raw(statement.clause.sql, statement.clause.params...).Exec()
if err != nil {
return 0, err
}
return res.RowsAffected()
}
func createUpdateSQL(statement *Statement) {
createConditionSQL(statement)
statement.clause.Build(Update, Where, Condition)
}
func createDeleteSQL(statement *Statement) {
statement.clause.Set(Delete, statement.clause.tablename)
createConditionSQL(statement)
statement.clause.Build(Delete, Where, Condition)
}
func createFindSQL(statement *Statement) {
statement.clause.Set(Select, statement.clause.cselect, statement.clause.tablename)
createConditionSQL(statement)
statement.clause.Build(Select, Where, Condition)
}
// 拼接完整SQL语句
func createConditionSQL(statement *Statement) {
if statement.clause.condition != "" {
statement.clause.Set(Where, "where")
statement.clause.SetCondition(Condition, statement.clause.condition, statement.clause.params)
}
}
```
**Insert 实现功能原理**
- 用了两个变量获取 clause 类中的sql语句和params,然后将其交给 raw.go
文件中 Exec 方法进行新增
**FindOne 实现功能原理**
- 首先调用 createFindSQL 方法创建完整sql语句
- 通过 QueryRow 方法进行单条sql语句查询
- 获取到结果存储的结构体信息
- 调用 Scan 方法将数据库返回的结果赋值给 values 字段
- 将结果集返回
**FindAll 实现功能原理**
- 首先进行参数类型校验,比如返回结果集 dest 的类型是否为指针,指针指向的元素是和否为结构体
- 调用createFindSQL 方法创建完整sql语句
- 通过 Query 方法进行sql语句查询
- 循环获取符合查询条件的结果集,将其赋值给参数dest
# 代码测试
```go
package session
import (
"context"
"testing"
log "github.com/sirupsen/logrus"
)
func Newclient() (client *Client, err error) {
setting := Settings{
DriverName: "mysql",
User: "root",
Password: "12345678",
Database: "po",
Host: "127.0.0.1:3306",
Options: map[string]string{"charset": "utf8mb4"},
}
return NewClient(setting)
}
func TestSession_Insert(t *testing.T) {
user := &Users{
Name: "迈莫coding",
Age: 1,
}
statement := NewStatement()
statement = statement.SetTableName("memo").
InsertStruct(user)
client, _ := Newclient()
client.Insert(context.Background(), statement)
}
func TestSession_FindOne(t *testing.T) {
statement := NewStatement()
statement = statement.SetTableName("user").
AndEqual("user_name", "迈莫").
Select("user_name,age")
client, err := Newclient()
if err != nil {
log.Error(err)
return
}
user := &User{}
_ = client.FindOne(context.Background(), statement, user)
log.Info(user)
}
func TestSession_FindAll(t *testing.T) {
statement := NewStatement()
statement = statement.SetTableName("user").
Select("user_name,age")
client, _ := Newclient()
var user []User
_ = client.FindAll(context.Background(), statement, &user)
log.Info(user)
}
func TestSession_Delete(t *testing.T) {
statement := NewStatement()
statement = statement.SetTableName("memo").
AndEqual("name", "迈莫coding")
client, _ := Newclient()
client.Delete(context.Background(), statement)
}
func TestSession_Update(t *testing.T) {
user := &Users{
Name: "迈莫",
Age: 1,
}
statement := NewStatement()
statement = statement.SetTableName("user").
UpdateStruct(user).
AndEqual("user_name", "迈莫")
client, _ := Newclient()
client.Update(context.Background(), statement)
}
```
**结果展示**
```go
=== RUN TestSession_FindOne
time="2021-01-16T18:32:50+08:00" level=info msg="Connect database success"
time="2021-01-16T18:32:50+08:00" level=info msg="select user_name,age from user WHERE `user_name`=? [迈莫]"
time="2021-01-16T18:32:51+08:00" level=info msg="&{迈莫 1}"
--- PASS: TestSession_FindOne (0.05s)
PASS
Process finished with exit code 0
```
# 代码目录
```go
torm
|--raw.go // 底层与数据库交互语句
|--raw_test.go
|--schema.go // 对象表结构映射
|--schema_test.go
|--generators.go // 关键词sql语句
|--clause.go // 条件组件库
|--clause_test.go
|--statement.go // 条件组件库操作API
|--statement_test.go
|--client.go // 用户CRUD操作API
|--client_test.go
|--go.mod
```
今天的任务完成了,回顾一下,主要完成了两大功能,都是面向用户的API设计功能,一个是数据库配置API,一个用户CRUD操作API。
> 文章也会持续更新,可以微信搜索「 迈莫coding 」第一时间阅读,回复『1024』领取学习go资料。
有疑问加站长微信联系(非本文作者))