【七天从零实现ORM】Day06:用户CRUD操作API

memo012 · · 365 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

![在这里插入图片描述](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资料。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

365 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传