【七天从零实现ORM|Day06:用户CRUD操作API】学完迈入腾讯阿里大厂

763 阅读5分钟

在这里插入图片描述

今天是【7天从零实现TORM框架】的第五天,主要任务是:

  • torm中数据库配置信息API编写。
  • 利用 client 类包装用CRUD操作API,代码200行左右。

若对Go中反射的使用不了解的话,我写了三篇关于反射的文章,给小伙伴提供参考,足以应对本项目中所使用的反射知识点。

源代码:在【迈莫coding】中回复关键字「 torm 」获取github地址链接
后续会为【七天从零实现TORM框架】录制视频,文章+视频+代码

数据库配置操作API

高级语言与数据库进行交互时,首先避免不了对数据库的配置,所以首先编写数据库的配置信息API,代码存储在根目录下的 client.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 方法用于数据库配置信息组装

代码演示

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语句,完成对应功能,其设计代码如下:

// 新增数据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

代码测试

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)
}

结果展示

=== 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

代码目录

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资料。