ORM Realized by Reflect
Core Technology of ORM—Reflection
Tutorial, Reflection Guide: https://zhuanlan.zhihu.com/p/411313885
code: https://github.com/Greenery-S/go-database/tree/master/orm
1 Reflection
What is Reflection
- Reflection is the ability to inspect and modify an object’s type information and memory structure, update variables, and call methods during runtime (not compile time).
When to Use Reflection
- When a function’s parameter type is
interface{}, and you need to determine the original type at runtime to handle different types accordingly. For example,json.Marshal(v interface{}). - When you need to dynamically decide which function to call at runtime based on certain conditions, such as executing the appropriate operator function based on a configuration file.
- It is recommended to use reflection during the initialization phase. Avoid using it frequently in API calls due to performance concerns.
2 Usage Examples
3 Drawbacks of Reflection
- Code readability and maintainability are poor.
- Type errors cannot be detected during compilation, making comprehensive testing challenging. Some bugs may only be discovered after prolonged runtime in production, potentially causing severe consequences.
- Reflection performance is poor, typically one to two orders of magnitude slower than regular code. Avoid using reflection in performance-critical or frequently called code blocks.
4 Basic Data Types of Reflection
reflect.Type – Retrieve type-related information using reflect.Type
type Type interface {
MethodByName(string) (Method, bool) // Retrieve method by name
Name() string // Get the struct name
PkgPath() string // Package path
Size() uintptr // Memory size
Kind() Kind // Data type
Implements(u Type) bool // Check if it implements an interface
Field(i int) StructField // Retrieve the i-th field
FieldByIndex(index []int) StructField // Retrieve nested field by index path
FieldByName(name string) (StructField, bool) // Retrieve field by name
Len() int // Container length
NumIn() int // Number of input parameters
NumOut() int // Number of return parameters
}
reflect.Value – Retrieve and modify values within the original data type using reflect.Value
type Value struct {
// The type represented by this value
typ *rtype
// Pointer to the original data
ptr unsafe.Pointer
}
5 Retrieving Field Information
typeUser := reflect.TypeOf(User{})
for i := 0; i < typeUser.NumField(); i++ { // Number of fields
field := typeUser.Field(i)
fmt.Printf("%s offset %d anonymous %t type %s exported %t json tag %s\n",
field.Name, // Field name
field.Offset, // Memory offset from the struct's start address; string type occupies 16 bytes
field.Anonymous, // Is it an anonymous field
field.Type, // Data type, of type reflect.Type
field.IsExported(), // Is it visible outside the package (i.e., starts with an uppercase letter)
field.Tag.Get("json")) // Retrieve the tag defined after the field in ``
}
6 Principles of ORM Implementation
type User struct {
Id int `gorm:"column:id;primaryKey"`
Gender string `gorm:"column:sex"`
Name string `gorm:"-"`
FamilyName string
}
- Ignore fields marked with
gorm:"-" - Retrieve the content after
gormfromfield.Tag.Get("gorm") - Remove the prefix
"column:" - Split the string by
;and take the first part - Fields without an explicit
gormtag will be converted to snake case, corresponding to the MySQL table column
func GetGormFields(stc interface{}) []string {
value := reflect.ValueOf(stc)
typ := value.Type()
columns := make([]string, 0, value.NumField())
for i := 0; i < value.NumField(); i++ {
fieldType := typ.Field(i)
// Skip fields not mapped to ORM
if fieldType.Tag.Get("gorm") == "-" {
continue
}
// Convert camel case to snake case if there is no gorm tag
name := util.Camel2Snake(fieldType.Name)
if len(fieldType.Tag.Get("gorm")) > 0 {
content := fieldType.Tag.Get("gorm")
if strings.HasPrefix(content, "column:") {
content = content[7:]
pos := strings.Index(content, ";")
if pos > 0 {
name = content[:pos]
} else if pos < 0 {
name = content
}
}
}
columns = append(columns, name)
}
return columns
}