Golang实现自己的orm框架实例探索

 更新时间:2024年01月24日 09:32:12   作者:绍纳 nullbody笔记  
这篇文章主要为大家介绍了Golang实现自己的orm框架实例探索,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

本项目相当于gorm的精简版,通过学习本项目可以更好的理解orm框架每个函数都在做什么,以及为什么这么定义框架功能。

通过本项目学到什么?

  • 定义自己的日志库logger
  • 如何将结构体转化成数据库的表信息(字段/字段类型/表名)
  • 如何实现常用的CRUD?
  • 如何实现事务?
  • 如何实现钩子?

Golang orm框架实现

该项目的核心数据结构为 Session

type Session struct {
	db *sql.DB
	tx *sql.Tx

	// table
	tableMeta *table.TableMeta

	dial dialect.Dialect
	// clause

	clause clause.Clause

	// sql query
	sql strings.Builder
	// sql param
	sqlParam []interface{}
}

db: 负责执行sql语句

tx: 通过事务执行sql语句

tableMeta: Golang的结构体转成的 表信息

clause: sql语句构造器(负责生成CRUD SQL 字符串 )

dial: 将Golang数据类型转成 数据库的类型

sql/sqlParam: 待执行的sql和参数

定义自己的日志库logger

特色:就是不同的日志级别有不同的颜色

如何将结构体转化成数据库的表信息(字段/字段类型/表名)

// Parse 将结构体转化成 表/字段/字段类型
func Parse(dest interface{}, d dialect.Dialect) *TableMeta {
	// 是否为nil
	if dest == nil {
		returnnil
	}
	// (*User)(nil)
	value := reflect.ValueOf(dest)
	if value.Kind() == reflect.Ptr && value.IsNil() {
		value = reflect.New(value.Type().Elem()) // 取出类型,构造新对象
	}
	meta := &TableMeta{
		Model:    value.Interface(), // 保存对象
		fieldMap: make(map[string]*Field),
	}
	destValue := reflect.Indirect(value)
	destType := destValue.Type()
	// 结构体类型名:就是表名
	m, ok := value.Interface().(Tabler)
	if ok {
		meta.TableName = m.TableName()
	} else {
		meta.TableName = destType.Name()
	}
	for i := 0; i < destType.NumField(); i++ {
		fieldType := destType.Field(i)
		// 非匿名 && 可导出
		if !fieldType.Anonymous && fieldType.IsExported() {
			// 成员变量:就是字段名
			// 成员变量类型:就是字段类型
			field := &Field{
				FieldName: fieldType.Name,
				FieldType: d.ConvertType2DBType(destValue.Field(i)),
				FieldTag:  fieldType.Tag.Get("easyorm"),
			}
			meta.Fields = append(meta.Fields, field)
			meta.FieldsName = append(meta.FieldsName, field.FieldName)
			meta.fieldMap[field.FieldName] = field
		}
	}
	return meta
}

如何实现常用的CRUD

这里以新增为例:一般的调用方式为 s.Insert(&User{},&User{})

整个代码其实就是基于传入的values,通过 reflect的相关函数提取出其中的字段值,最终的目的在于拼接成 insert into $table ($field) values (?,?),(?,?) 标准的SQL语句,传给底层的驱动执行

// s.Insert(&User{},&User{})
func (s *Session) Insert(values ...interface{}) (int64, error) {
	iflen(values) == 0 {
		return0, errors.New("param is empty")
	}
	// init table
	s.Model(values[0])
	// clause insert:负责生成 insert into $tableName ($FieldName) 字符串
	s.clause.Set(clause.INSERT, s.TableMeta().TableName, s.TableMeta().FieldsName)

	valueSlice := []interface{}{}

	for _, value := range values {
		s.CallMethod(BeforeInsert, value)
        // 提取结构体中对象字段的值,作为sql语言的参数
		valueSlice = append(valueSlice, s.TableMeta().ExtractFieldValue(value))
	}
	// clause values:负责生成 values (?,?,?),(?,?,?)
	s.clause.Set(clause.VALUES, valueSlice...)
    //s.clause.Build: 负责将 insert into $tableName ($FieldName)  拼接上 values (?,?,?),(?,?,?) 形成完整的sql语句
	sql, sqlParam := s.clause.Build(clause.INSERT, clause.VALUES)
	// exec sql : 实际执行sql
	result, err := s.Raw(sql, sqlParam...).Exec()
	if err != nil {
		return0, err
	}
	s.CallMethod(AfterInsert, nil)
	return result.RowsAffected()
}

如何实现事务?

通过SessionBegin方法开启事务,在回调函数 f TxFunc中进行具体的【数据库业务逻辑】,最后基于  f TxFunc是否执行返回 error来决定是Commit还是 Rollback

// TxFunc will be called between tx.Begin() and tx.Commit()
type TxFunc func(*session.Session) (interface{}, error)
// Transaction executes sql wrapped in a transaction, then automatically commit if no error occurs
func (engine *Engine) Transaction(f TxFunc) (result interface{}, err error) {
	s := engine.NewSession()
	if err := s.Begin(); err != nil {
		returnnil, err
	}
	deferfunc() {
		if p := recover(); p != nil {
			_ = s.Rollback()
			panic(p) // re-throw panic after Rollback
		} elseif err != nil {
			_ = s.Rollback() // err is non-nil; don't change it
		} else {
			err = s.Commit() // err is nil; if Commit returns error update err
		}
	}()
	return f(s)
}

如何实现钩子?

其实就是利用reflect.MethodByName,读取 value 对象的函数,然后Call执行该函数,本质就是回调。利用钩子在获取到数据以后,用户可以在回调中对数据进行统一修改,对执行结果进行收口

// CallMethod calls the registered hooks
func (s *Session) CallMethod(method string, value interface{}) {
	fm := reflect.ValueOf(s.TableMeta().Model).MethodByName(method)
	if value != nil {
		fm = reflect.ValueOf(value).MethodByName(method)
	}
	param := []reflect.Value{reflect.ValueOf(s)}
	if fm.IsValid() {
		if v := fm.Call(param); len(v) > 0 {
			if err, ok := v[0].Interface().(error); ok {
				logger.Error(err)
			}
		}
	}
}

项目拖过地址: https://github.com/gofish2020/easyorm 

以上就是Golang实现自己的orm框架实例探索的详细内容,更多关于Golang orm框架的资料请关注脚本之家其它相关文章!

相关文章

  • golang 字符串拼接方法对比分析

    golang 字符串拼接方法对比分析

    这篇文章主要为大家介绍了golang 字符串拼接方法对比分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 教你一招完美解决vscode安装go插件失败问题

    教你一招完美解决vscode安装go插件失败问题

    VSCode是我们开发go程序的常用工具,但是安装VSCode成功后,创建一个.go文件居然提示错误了,所以下面下面这篇文章主要给大家介绍了如何通过一招完美解决vscode安装go插件失败问题的相关资料,需要的朋友可以参考下
    2022-07-07
  • Go指针数组和数组指针的具体使用

    Go指针数组和数组指针的具体使用

    go语言跟c语言一样,指针数组和数组指针概念容易搞混,本文主要介绍了Go指针数组和数组指针的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Go语言中定时任务库Cron使用方法介绍

    Go语言中定时任务库Cron使用方法介绍

    cron的意思计划任务,说白了就是定时任务。我和系统约个时间,你在几点几分几秒或者每隔几分钟跑一个任务(job),今天通过本文给大家介绍下Go语言中定时任务库Cron使用方法,感兴趣的朋友一起看看吧
    2022-03-03
  • golang结构化日志log/slog包之LogValuer的用法简介

    golang结构化日志log/slog包之LogValuer的用法简介

    这篇文章主要为大家详细介绍了golang结构化日志log/slog包中 LogValuer 和日志记录函数的正确包装方法,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-10-10
  • 使用Golang实现加权负载均衡算法的实现代码

    使用Golang实现加权负载均衡算法的实现代码

    这篇文章主要介绍了使用Golang实现加权负载均衡算法的实现代码,详细说明权重转发算法的实现,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • Go语言中普通函数与方法的区别分析

    Go语言中普通函数与方法的区别分析

    这篇文章主要介绍了Go语言中普通函数与方法的区别,以实例形式对比分析了普通函数与方法使用时的区别与相关技巧,需要的朋友可以参考下
    2015-02-02
  • Go语言学习之数组的用法详解

    Go语言学习之数组的用法详解

    数组是相同数据类型的一组数据的集合,数组一旦定义长度不能修改,数组可以通过下标(或者叫索引)来访问元素。本文将通过示例详细讲解Go语言中数组的使用,需要的可以参考一下
    2022-04-04
  • Go语言获取文件的名称、前缀、后缀

    Go语言获取文件的名称、前缀、后缀

    这篇文章主要介绍了Go语言获取文件的名称、前缀、后缀,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • 一文带你使用Golang实现SSH客户端

    一文带你使用Golang实现SSH客户端

    SSH 全称为 Secure Shell,是一种用于安全地远程登录到网络上的其他计算机的网络协议,本文主要为大家详细介绍了如何使用 Golang 实现 SSH 客户端,需要的可以参考下
    2023-11-11

最新评论