javaer to go之简单的ORM封装

简介: 身为一个做企业级开发的javaer,习惯使用hibernate、ibatis等ORM框架来操作数据库。虽然也发现golang也有ORM框架,像beego ORM等。 为了熟悉golang的一些特性,我还是觉得自己封装一个ORM。 <h1 id="1struct与interface

身为一个做企业级开发的javaer,习惯使用hibernate、ibatis等ORM框架来操作数据库。虽然也发现golang也有ORM框架,像beego ORM等。

为了熟悉golang的一些特性,我还是觉得自己封装一个ORM。

1、struct与interface简单说明

golang是一门面向过程的语言,所以它本身是没有像java那样的类与对象的概念。但golang中提供了struct与interface。甚至通过struct与interface结合,可以模拟类与对象的各种方式。

什么是interface,golang的interface与java的interface是不是一回事呢?

简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为。

一个类型如果拥有一接口的所有方法,那这个类型实现了这一接口。而不是像java那样使用implements关键字。

所有类型都实现了一个空接口,也就是我们可以通过空接口类型传入(返回)所有类型参数。类似java的Object是所有类的父类一样,空接口interface{}是golang中所有类型的父接口。

  • 简单的struct
type Object struct {
    Id              int       
    name          string
    Age   int  
}
  • 简单的interface
type IObject interface {
    SetName(name string)
    GetName() string
}
  • interface的实现
func (this *Object) SetName(name string)() {
    this.name = name
}

func (this *Object) GetName() string {
    return this.name
}

2、反射机制简单说明

与java一样,golang也提供了反射工具。

golang的reflect包主要有两个数据类型Type和Value。Type就是定义的类型的一个数据类型,Value是值的类型。
golang的反射就是把类型变量(对象),转成Type或Value,然后在运行时,对其操作的过程。

reflect的TypeOf与ValueOf可以把任意类型对象转成转成Type或Value。

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

    // TODO: Maybe allow contents of a Value to live on the stack.
    // For now we make the contents always escape to the heap.  It
    // makes life easier in a few places (see chanrecv/mapassign
    // comment below).
    escapes(i)

    return unpackEface(i)
}

3、struct实现Entity与Annotation

Java的ORM实现,如Hibernate,是通过xml配置文件或者annotation来把对象与表关联起来的。

当然,golang也可以使用xml的方式来做关联。但xml配置文件使用起来比较麻烦,但golang却没有annotation这样的东西。

还好golang的struct中,除了变量名和类型之外,还可以选择性的增加一些tag:tag可以在类型的后面,用双引号(double quote)或重音(backquote/grave accent)表示的字符串。这些符号能被用来做文档或重要的标签。

tag里面的内容在正常编程中没有作用。只有在使用反射的时候才有作用。我们可以在运行时,对tag进行解析,以达到类似注解说明的效果。

我们的Entity:

type Entity struct {
    Id   int       `table:"table_name" column:"id"`
    Time time.Time `column:"time"`
    Name string    `column:"day"`
    Age  string    `column:"age"`
}

上面的struct中,类型后面的就是tag。与Hibernate一样,我们的ORM希望我们的表都有主键。这里写的ORM,只支持简单的主键。

  • tag中的column对应的是表的列
  • tag中的table描述用于主键字段,它即代表着主键,也说明了实体映射的表。

有了tag的描述后,我们就可以把ORM的O与R mapping起来了。

4、单例模式的数据库连接

var sqlDB *sql.DB

func Open() *sql.DB {
    if sqlDB == nil {
        db, err := sql.Open("mysql", "用户名:密码@/数据库名?charset=utf8")
        if err != nil {
            fmt.Println(err.Error())
        }
        sqlDB = db
    }

    return sqlDB
}

5、数据库操作基类(BaseDao)的定义

type IBaseDao interface {
    Init()
    Save(data interface{}) error
    Update(data interface{}) error
    SaveOrUpdate(data interface{}) error
    SaveAll(datas list.List) error
    Delete(data interface{}) error
    Find(sql string) (*list.List, error)
    FindOne(sql string) (interface{}, error)
}

type BaseDao struct {
    EntityType reflect.Type
    sqlDB      *sql.DB
    tableName     string            //表名
    pk            string            //主键
    columnToField map[string]string //字段名:属性名
    fieldToColumn map[string]string //属性名:字段名
}

IBaseDao接口中,Init是接口实现时的初始化。除了Init外都是数据库的DML操作,就是所谓的增删改查。

BaseDao的EntityType属性类似Java中的泛型对象(如下面的clazz),用于获取操作实体对象的属性与标签。

public class BaseDao<T,PK extends Serializable> extends HibernateDaoSupport implements IBaseDao<T,PK> {

    private Class<T> clazz;

    public BaseDao() {
        ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
        clazz = (Class<T>) type.getActualTypeArguments()[0];
    }


    //...
}

6、BaseDao的初始化过程

BaseDao的初始化过程,就是通过解析struct的tag,来把struct的变量与表的列一一对应起来的过程。

  1. 我们可以在后面的使用中,通过columnToField键值对,获取列对应的变量名
  2. 通过fieldToColumn键值对,获取变量名对应的列
  3. 通过pk变量获取主键的列
//初始化
func (this *BaseDao) Init() {
    this.columnToField = make(map[string]string)
    this.fieldToColumn = make(map[string]string)

    types := this.EntityType

    for i := 0; i < types.NumField(); i++ {
        typ := types.Field(i)
        tag := typ.Tag

        if len(tag) > 0 {
            column := tag.Get("column")
            name := typ.Name
            this.columnToField[column] = name
            this.fieldToColumn[name] = column

            if len(tag.Get("table")) > 0 {
                this.tableName = tag.Get("table")
                this.pk = column
            }
        }
    }
}

7、增加

  • 预处理的insert语句封装以及占位符代表的列(有序)
func (this *BaseDao) insertPrepareSQL() (fieldNames list.List, sql string) {
    names := new(bytes.Buffer)
    values := new(bytes.Buffer)

    i := 0

    for column, fieldName := range this.columnToField {

        if i != 0 {
            names.WriteString(",")
            values.WriteString(",")
        }
        fieldNames.PushBack(fieldName)
        names.WriteString(column)
        values.WriteString("?")
        i++
    }
    sql = fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", this.tableName, names.String(), values.String())
    return
}
  • 占位符的数据(有序)
//预处理占位符的数据
func (this *BaseDao) prepareValues(data interface{}, fieldNames list.List) []interface{} {
    values := make([]interface{}, len(this.columnToField))
    object := reflect.ValueOf(data).Elem()

    i := 0
    for e := fieldNames.Front(); e != nil; e = e.Next() {
        name := e.Value.(string)
        field := object.FieldByName(name)
        values[i] = this.fieldValue(field)
        i++

    }

    return values
}

//reflect.Value获取值
func (this *BaseDao) fieldValue(v reflect.Value) interface{} {
    if !v.IsValid() {
        return nil
    }

    switch v.Kind() {
    case reflect.String:
        return v.String()
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        return v.Uint()
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return v.Int()
    case reflect.Float32, reflect.Float64:
        return v.Float()
    case reflect.Struct:
        switch v.Type().String() {
        case "time.Time":
            m := v.MethodByName("Format")
            rets := m.Call([]reflect.Value{reflect.ValueOf(timeFormate)})
            t := rets[0].String()
            return t
        default:
            return nil
        }
    default:
        return nil
    }
}
  • 增加记录
//增加单个记录
func (this *BaseDao) Save(data interface{}) error {
    columns, sql := this.insertPrepareSQL()

    stmt, err := Open().Prepare(sql)
    args := this.prepareValues(data, columns)
    fmt.Println(sql, " ", args)
    _, err = stmt.Exec(args...)
    if err != nil {
        panic(err.Error())
    }
    return err
}

//增加多个记录
func (this *BaseDao) SaveAll(datas list.List) error {
    if datas.Len() == 0 {
        return nil
    }
    columns, sql := this.insertPrepareSQL()

    stmt, err := Open().Prepare(sql)
    if err != nil {
        panic(err.Error())
    }

    for e := datas.Front(); e != nil; e = e.Next() {
        args := this.prepareValues(e.Value, columns)
        fmt.Println(sql, " ", args)
        _, err = stmt.Exec(args...)
        if err != nil {
            panic(err.Error())
        }
    }

    return err
}

8、修改

  • 预处理的update语句封装以及占位符代表的列
//实体转update sql语句
func (this *BaseDao) updatePrepareSQL() (fieldNames list.List, sql string) {
    //UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
    sets := new(bytes.Buffer)

    i := 0

    for column, fieldName := range this.columnToField {
        if strings.EqualFold(column, this.pk) {
            continue
        }
        if i != 0 {
            sets.WriteString(",")
        }

        fieldNames.PushBack(fieldName)
        sets.WriteString(column)
        sets.WriteString("=?")

        i++
    }
    fieldNames.PushBack(this.columnToField[this.pk])
    sql = fmt.Sprintf("UPDATE %s SET %s WHERE %s=?", this.tableName, sets.String(), this.pk)
    return
}
  • 更新记录
    和增加记录一样,获取到update语句及占位符的列名后,还需要根据列名获取到列的值(占位符的数据)。
//更新一个实体
func (this *BaseDao) Update(data interface{}) error {
    columns, sql := this.updatePrepareSQL()

    stmt, err := Open().Prepare(sql)
    args := this.prepareValues(data, columns)

    fmt.Println(sql, " ", args)
    _, err = stmt.Exec(args...)
    if err != nil {
        panic(err.Error())
    }
    return err
}

9、增加或修改

根据实体对象的ID是否有值来判断是保存还是更新。

//保存或更新一个实体,根据主键是否有值
func (this *BaseDao) SaveOrUpdate(data interface{}) error {

    if this.isPkValue(data) {
        return this.Update(data)
    } else {
        return this.Save(data)
    }

}

//主键是否有值
func (this *BaseDao) isPkValue(data interface{}) bool {
    object := reflect.ValueOf(data).Elem()
    pkName := this.columnToField[this.pk]
    pkValue := object.FieldByName(pkName)

    if !pkValue.IsValid() {
        return false
    }

    switch pkValue.Kind() {
    case reflect.String:
        if len(pkValue.String()) > 0 {
            return true
        }
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        if pkValue.Uint() != 0 {
            return true
        }
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        if pkValue.Int() != 0 {
            return true
        }
    }

    return false
}

10、删除

  • delete语句封装

根据主键来删除数据

//实体转delete sql语句
func (this *BaseDao) deleteSQL(data interface{}) string {
    //DELETE FROM 表名称 WHERE 列名称 = 值
    object := reflect.ValueOf(data).Elem()
    fieldValue := object.FieldByName(this.pk)
    pkValue := this.valueToString(fieldValue)
    return fmt.Sprintf("DELETE FROM  %s WHERE %s=%s", this.tableName, this.pk, pkValue)
}

//reflect.Value转字符串
func (this *BaseDao) valueToString(v reflect.Value) string {
    values := new(bytes.Buffer)
    switch v.Kind() {
    case reflect.String:
        values.WriteString("'")
        values.WriteString(v.String())
        values.WriteString("'")
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        values.WriteString(fmt.Sprintf("%d", v.Uint()))
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        values.WriteString(fmt.Sprintf("%d", v.Int()))
    case reflect.Float32, reflect.Float64:
        values.WriteString(fmt.Sprintf("%f", v.Float()))
    case reflect.Struct:
        switch v.Type().String() {
        case "time.Time":
            m := v.MethodByName("Format")
            rets := m.Call([]reflect.Value{reflect.ValueOf(timeFormate)})
            t := rets[0].String()
            values.WriteString("'")
            values.WriteString(t)
            values.WriteString("'")
        default:
            values.WriteString("null")
        }
    default:
        values.WriteString("null")
    }

    return values.String()
}
  • 删除记录
//删除一个实体
func (this *BaseDao) Delete(data interface{}) error {
    _, err := Open().Exec(this.deleteSQL(data))
    return err
}

11、查询

  • 通过select语句获取到记录
rows, err := Open().Query(sql)
if err != nil {
    fmt.Println(err.Error())
}
  • 获取列名
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
    panic(err.Error())
}
  • 根据列名的顺序,把一行的值封装到一个EntityType对象中
//对一条查询结果进行封装
func (this *BaseDao) parseQuery(columns []string, values []interface{}) interface{} {
    obj := reflect.New(this.EntityType).Interface()
    typ := reflect.ValueOf(obj).Elem()

    for i, col := range values {
        if col != nil {
            name := this.columnToField[columns[i]]
            field := typ.FieldByName(name)

            this.parseQueryColumn(field, string(col.([]byte)))
        }
    }

    return obj
}

//单个属性赋值
func (this *BaseDao) parseQueryColumn(field reflect.Value, s string) {
    switch field.Kind() {
    case reflect.String:
        field.SetString(s)
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        v, _ := strconv.ParseUint(s, 10, 0)
        field.SetUint(v)
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        v, _ := strconv.ParseInt(s, 10, 0)
        field.SetInt(v)
    case reflect.Float32:
        v, _ := strconv.ParseFloat(s, 32)
        field.SetFloat(v)
    case reflect.Float64:
        v, _ := strconv.ParseFloat(s, 64)
        field.SetFloat(v)
    case reflect.Struct:
        switch field.Type().String() {
        case "time.Time":
            v, _ := time.Parse(timeFormate, s)
            field.Set(reflect.ValueOf(v))
        }
    default:

    }
}
  • 把多个EntityType对象放到一个集合中
data := list.New() //创建一个新的list

for rows.Next() {
    err = rows.Scan(scanArgs...)
    if err != nil {
        panic(err.Error())
    }

    obj := this.parseQuery(columns, values)
    data.PushBack(obj)
}
  • 查询单条记录
    封装sql的时候加上limit限制,返回数据时,返回集合中的第一条(也是唯一的一条)。
if isOne && !strings.Contains(sql, "limit") {
    sql = sql + " limit 1"
}
var data interface{}
if datas.Len() > 0 {
    data = datas.Front().Value
}
  • 完整的查询代码
//根据SQL查询多条记录
func (this *BaseDao) Find(sql string) (*list.List, error) {
    return this.query(sql, false)
}

//根据SQL查询一条记录,如果找到不数据,data会返回nil
func (this *BaseDao) FindOne(sql string) (interface{}, error) {
    datas, err := this.query(sql, true)

    var data interface{}
    if datas.Len() > 0 {
        data = datas.Front().Value
    }
    return data, err

}

//根据SQL查询
func (this *BaseDao) query(sql string, isOne bool) (*list.List, error) {
    if isOne && !strings.Contains(sql, "limit") {
        sql = sql + " limit 1"
    }
    rows, err := Open().Query(sql)
    if err != nil {
        fmt.Println(err.Error())
    }

    defer rows.Close()
    columns, err := rows.Columns()
    if err != nil {
        panic(err.Error())
    }

    //构造scanArgs、values两个数组,scanArgs的每个值指向values相应值的地址
    values := make([]interface{}, len(columns))
    scanArgs := make([]interface{}, len(values))
    for i := range values {
        scanArgs[i] = &values[i]
    }

    data := list.New() //创建一个新的list

    for rows.Next() {
        err = rows.Scan(scanArgs...)
        if err != nil {
            panic(err.Error())
        }

        obj := this.parseQuery(columns, values)
        data.PushBack(obj)
    }
    return data, err
}

//对一条查询结果进行封装
func (this *BaseDao) parseQuery(columns []string, values []interface{}) interface{} {
    obj := reflect.New(this.EntityType).Interface()
    typ := reflect.ValueOf(obj).Elem()

    for i, col := range values {
        if col != nil {
            name := this.columnToField[columns[i]]
            field := typ.FieldByName(name)

            this.parseQueryColumn(field, string(col.([]byte)))
        }
    }

    return obj
}

//单个属性赋值
func (this *BaseDao) parseQueryColumn(field reflect.Value, s string) {
    switch field.Kind() {
    case reflect.String:
        field.SetString(s)
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        v, _ := strconv.ParseUint(s, 10, 0)
        field.SetUint(v)
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        v, _ := strconv.ParseInt(s, 10, 0)
        field.SetInt(v)
    case reflect.Float32:
        v, _ := strconv.ParseFloat(s, 32)
        field.SetFloat(v)
    case reflect.Float64:
        v, _ := strconv.ParseFloat(s, 64)
        field.SetFloat(v)
    case reflect.Struct:
        switch field.Type().String() {
        case "time.Time":
            v, _ := time.Parse(timeFormate, s)
            field.Set(reflect.ValueOf(v))
        }
    default:

    }
}

12、完整的ORM代码

package db

import (
    "bytes"
    "container/list"
    "database/sql"
    "fmt"
    "reflect"
    "strconv"
    "strings"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

const (
    timeFormate = "2006-01-02 15:04:05"
)

var sqlDB *sql.DB

func Open() *sql.DB {
    if sqlDB == nil {
        db, err := sql.Open("mysql", "root:test@/time_table?charset=utf8")
        if err != nil {
            fmt.Println(err.Error())
        }
        sqlDB = db
    }

    return sqlDB
}

type IBaseDao interface {
    Init()
    Save(data interface{}) error
    Update(data interface{}) error
    SaveOrUpdate(data interface{}) error
    SaveAll(datas list.List) error
    Delete(data interface{}) error
    Find(sql string) (*list.List, error)
    FindOne(sql string) (interface{}, error)
}

type BaseDao struct {
    EntityType    reflect.Type
    sqlDB         *sql.DB
    tableName     string            //表名
    pk            string            //主键
    columnToField map[string]string //字段名:属性名
    fieldToColumn map[string]string //属性名:字段名
}

//初始化
func (this *BaseDao) Init() {
    this.columnToField = make(map[string]string)
    this.fieldToColumn = make(map[string]string)

    types := this.EntityType

    for i := 0; i < types.NumField(); i++ {
        typ := types.Field(i)
        tag := typ.Tag

        if len(tag) > 0 {
            column := tag.Get("column")
            name := typ.Name
            this.columnToField[column] = name
            this.fieldToColumn[name] = column

            if len(tag.Get("table")) > 0 {
                this.tableName = tag.Get("table")
                this.pk = column
            }
        }
    }
}

//增加单列
func (this *BaseDao) Save(data interface{}) error {
    columns, sql := this.insertPrepareSQL()

    stmt, err := Open().Prepare(sql)
    args := this.prepareValues(data, columns)
    fmt.Println(sql, " ", args)
    _, err = stmt.Exec(args...)
    if err != nil {
        panic(err.Error())
    }
    return err
}

//集合保存
func (this *BaseDao) SaveAll(datas list.List) error {
    if datas.Len() == 0 {
        return nil
    }
    columns, sql := this.insertPrepareSQL()

    stmt, err := Open().Prepare(sql)
    if err != nil {
        panic(err.Error())
    }

    for e := datas.Front(); e != nil; e = e.Next() {
        args := this.prepareValues(e.Value, columns)
        fmt.Println(sql, " ", args)
        _, err = stmt.Exec(args...)
        if err != nil {
            panic(err.Error())
        }
    }

    return err
}

//更新一个实体
func (this *BaseDao) Update(data interface{}) error {
    columns, sql := this.updatePrepareSQL()

    stmt, err := Open().Prepare(sql)
    args := this.prepareValues(data, columns)

    fmt.Println(sql, " ", args)
    _, err = stmt.Exec(args...)
    if err != nil {
        panic(err.Error())
    }
    return err
}

//实体转update sql语句
func (this *BaseDao) updatePrepareSQL() (fieldNames list.List, sql string) {
    //UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
    sets := new(bytes.Buffer)

    i := 0

    for column, fieldName := range this.columnToField {
        if strings.EqualFold(column, this.pk) {
            continue
        }
        if i != 0 {
            sets.WriteString(",")
        }

        fieldNames.PushBack(fieldName)
        sets.WriteString(column)
        sets.WriteString("=?")

        i++
    }
    fieldNames.PushBack(this.columnToField[this.pk])
    sql = fmt.Sprintf("UPDATE %s SET %s WHERE %s=?", this.tableName, sets.String(), this.pk)
    return
}

//保存或更新一个实体,根据主键是否有值
func (this *BaseDao) SaveOrUpdate(data interface{}) error {

    if this.isPkValue(data) {
        return this.Update(data)
    } else {
        return this.Save(data)
    }

}

//主键是否有值
func (this *BaseDao) isPkValue(data interface{}) bool {
    object := reflect.ValueOf(data).Elem()
    pkName := this.columnToField[this.pk]
    pkValue := object.FieldByName(pkName)

    if !pkValue.IsValid() {
        return false
    }

    switch pkValue.Kind() {
    case reflect.String:
        if len(pkValue.String()) > 0 {
            return true
        }
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        if pkValue.Uint() != 0 {
            return true
        }
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        if pkValue.Int() != 0 {
            return true
        }
    }

    return false
}

//预处理占位符的数据
func (this *BaseDao) prepareValues(data interface{}, fieldNames list.List) []interface{} {
    values := make([]interface{}, len(this.columnToField))
    object := reflect.ValueOf(data).Elem()

    i := 0
    for e := fieldNames.Front(); e != nil; e = e.Next() {
        name := e.Value.(string)
        field := object.FieldByName(name)
        values[i] = this.fieldValue(field)
        i++

    }

    return values
}

//reflect.Value获取值
func (this *BaseDao) fieldValue(v reflect.Value) interface{} {
    if !v.IsValid() {
        return nil
    }

    switch v.Kind() {
    case reflect.String:
        return v.String()
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        return v.Uint()
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return v.Int()
    case reflect.Float32, reflect.Float64:
        return v.Float()
    case reflect.Struct:
        switch v.Type().String() {
        case "time.Time":
            m := v.MethodByName("Format")
            rets := m.Call([]reflect.Value{reflect.ValueOf(timeFormate)})
            t := rets[0].String()
            return t
        default:
            return nil
        }
    default:
        return nil
    }
}

func (this *BaseDao) insertPrepareSQL() (fieldNames list.List, sql string) {
    names := new(bytes.Buffer)
    values := new(bytes.Buffer)

    i := 0

    for column, fieldName := range this.columnToField {

        if i != 0 {
            names.WriteString(",")
            values.WriteString(",")
        }
        fieldNames.PushBack(fieldName)
        names.WriteString(column)
        values.WriteString("?")
        i++
    }
    sql = fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", this.tableName, names.String(), values.String())
    return
}

//删除一个实体
func (this *BaseDao) Delete(data interface{}) error {
    _, err := Open().Exec(this.deleteSQL(data))
    return err
}

//实体转delete sql语句
func (this *BaseDao) deleteSQL(data interface{}) string {
    //DELETE FROM 表名称 WHERE 列名称 = 值
    object := reflect.ValueOf(data).Elem()
    fieldValue := object.FieldByName(this.pk)
    pkValue := this.valueToString(fieldValue)
    return fmt.Sprintf("DELETE FROM  %s WHERE %s=%s", this.tableName, this.pk, pkValue)
}

//reflect.Value转字符串
func (this *BaseDao) valueToString(v reflect.Value) string {
    values := new(bytes.Buffer)
    switch v.Kind() {
    case reflect.String:
        values.WriteString("'")
        values.WriteString(v.String())
        values.WriteString("'")
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        values.WriteString(fmt.Sprintf("%d", v.Uint()))
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        values.WriteString(fmt.Sprintf("%d", v.Int()))
    case reflect.Float32, reflect.Float64:
        values.WriteString(fmt.Sprintf("%f", v.Float()))
    case reflect.Struct:
        switch v.Type().String() {
        case "time.Time":
            m := v.MethodByName("Format")
            rets := m.Call([]reflect.Value{reflect.ValueOf(timeFormate)})
            t := rets[0].String()
            values.WriteString("'")
            values.WriteString(t)
            values.WriteString("'")
        default:
            values.WriteString("null")
        }
    default:
        values.WriteString("null")
    }

    return values.String()
}

//根据SQL查询多条记录
func (this *BaseDao) Find(sql string) (*list.List, error) {
    return this.query(sql, false)
}

//根据SQL查询一条记录,如果找到不数据,data会返回nil
func (this *BaseDao) FindOne(sql string) (interface{}, error) {
    datas, err := this.query(sql, true)

    var data interface{}
    if datas.Len() > 0 {
        data = datas.Front().Value
    }
    return data, err

}

//根据SQL查询
func (this *BaseDao) query(sql string, isOne bool) (*list.List, error) {
    if isOne && !strings.Contains(sql, "limit") {
        sql = sql + " limit 1"
    }
    rows, err := Open().Query(sql)
    if err != nil {
        fmt.Println(err.Error())
    }

    defer rows.Close()
    columns, err := rows.Columns()
    if err != nil {
        panic(err.Error())
    }

    //构造scanArgs、values两个数组,scanArgs的每个值指向values相应值的地址
    values := make([]interface{}, len(columns))
    scanArgs := make([]interface{}, len(values))
    for i := range values {
        scanArgs[i] = &values[i]
    }

    data := list.New() //创建一个新的list

    for rows.Next() {
        err = rows.Scan(scanArgs...)
        if err != nil {
            panic(err.Error())
        }

        obj := this.parseQuery(columns, values)
        data.PushBack(obj)
    }
    return data, err
}

//对一条查询结果进行封装
func (this *BaseDao) parseQuery(columns []string, values []interface{}) interface{} {
    obj := reflect.New(this.EntityType).Interface()
    typ := reflect.ValueOf(obj).Elem()

    for i, col := range values {
        if col != nil {
            name := this.columnToField[columns[i]]
            field := typ.FieldByName(name)

            this.parseQueryColumn(field, string(col.([]byte)))
        }
    }

    return obj
}

//单个属性赋值
func (this *BaseDao) parseQueryColumn(field reflect.Value, s string) {
    switch field.Kind() {
    case reflect.String:
        field.SetString(s)
    case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        v, _ := strconv.ParseUint(s, 10, 0)
        field.SetUint(v)
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        v, _ := strconv.ParseInt(s, 10, 0)
        field.SetInt(v)
    case reflect.Float32:
        v, _ := strconv.ParseFloat(s, 32)
        field.SetFloat(v)
    case reflect.Float64:
        v, _ := strconv.ParseFloat(s, 64)
        field.SetFloat(v)
    case reflect.Struct:
        switch field.Type().String() {
        case "time.Time":
            v, _ := time.Parse(timeFormate, s)
            field.Set(reflect.ValueOf(v))
        }
    default:

    }
}

12、ORM使用

  • 定义一个Entity
/*entity*/
type Chair struct {
    Id              int       `table:"device_chair" column:"id"`
    Time            time.Time `column:"time"`
    Day             string    `column:"day"`
    Origin          string    `column:"origin"`
    ParentSerial    string    `column:"parent_serial"`
    Serial          string
    RunningNumber   string  `column:"running_number"`
    PointA          float32 `column:"point_a"`
    PointB          float32 `column:"point_b"`
    PointC          float32 `column:"point_c"`
    PointD          float32 `column:"point_d"`
    PointE          float32 `column:"point_e"`
    PointF          float32 `column:"point_f"`
    Voltage         float32 `column:"voltage"`
    SeatingPosition uint8   `column:"seating_position"`
}
  • 通过组合的方式实体BaseDao的继承
/*dao*/
type IChairDao interface {
    db.IBaseDao
}

type chairDao struct {
    db.BaseDao
}
  • 初始化Dao
    这个过程中记得要给BaseDao传入EntityType的值,并调用Init方法进行初始化工作。
var chairDaoImpl IChairDao

func ChairDao() IChairDao {
    if chairDaoImpl == nil {
        chairDaoImpl = &chairDao{db.BaseDao{EntityType: reflect.TypeOf(new(Chair)).Elem()}}
        chairDaoImpl.Init()
    }

    return chairDaoImpl
}
  • Dao使用
    这里使用增加数据为例子
    chair := new(Chair)
    chair.Id = 129986
    chair.SeatingPosition = 10
    chair.Day = chair.Time.Format("2006-01-02")
    err = ChairDao().Save(chair)

13、未完成

  1. 多模式主键的支持
  2. 外键关联的支持
  3. 事务的支持
目录
相关文章
|
6月前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
SQL 关系型数据库 Go
Go语言微服务框架 - 12.ORM层的自动抽象与自定义方法的扩展
随着接口参数校验功能的完善,我们能快速定位到接口层面的参数问题;而应用服务的分层代码,也可以通过log的trace-id发现常见的业务逻辑问题。 但在最底层与数据库的操作,也就是对GORM的使用,经常会因为我们不了解ORM的一些细节,导致对数据的CRUD失败,或者没有达到预期效果。这时,我们希望能在ORM这一层也有一个通用的解决方案,来加速问题的排查。
83 0
|
关系型数据库 MySQL Go
Go语言微服务框架 - 8.Gormer迭代-定制专属的ORM代码生成工具
我们对比一下GORM库提供的`gorm.Model`,它在新增、修改时,会自动修改对应的时间,这个可以帮我们减少很多重复性的代码编写。这里,我就针对现有的gormer工具做一个示例性的迭代。
96 0
|
安全 Go
Go语言封装艺术:代码安全与结构清晰
Go语言封装艺术:代码安全与结构清晰
80 0
|
5月前
|
存储 中间件 Go
在go语言服务中封装路由和示例
【6月更文挑战第23天】本文介绍golang后端按协议处理、中间件(一次性与每次请求执行)划分、以及服务架构Controller、Logic/Service、DAO/Repository和Routers划分。代码仓库在GitHub上提供。使用框架简化了交互和处理。后续章节深入探讨服务构建。
161 5
在go语言服务中封装路由和示例
|
3月前
|
编译器 Go
Go语言中的闭包:封装数据与功能的强大工具
Go语言中的闭包:封装数据与功能的强大工具
|
3月前
|
Ubuntu Go Docker
[go]封装go的docker镜像
[go]封装go的docker镜像
|
6月前
|
关系型数据库 MySQL 数据库连接
实战演练:使用Go语言和ORM框架与数据库进行交互
【2月更文挑战第13天】本文将通过一个实战演练,展示如何使用Go语言和ORM(对象关系映射)框架与数据库进行交互。我们将选择一个流行的ORM框架,如GORM,来完成这个任务。通过实际编码,我们将演示如何连接数据库、执行CRUD操作、处理错误和异常,并展示ORM框架如何简化数据库操作过程。
|
6月前
|
SQL 关系型数据库 MySQL
Go语言中的ORM框架介绍
【2月更文挑战第13天】本文将介绍ORM(对象关系映射)框架在Go语言中的应用。ORM框架能够简化数据库操作,将数据库表映射为Go结构体,并提供了一系列方法来执行CRUD(创建、读取、更新、删除)操作。我们将探讨几个流行的Go语言ORM框架,包括GORM、SQLBoiler和Squirrel,并比较它们的特性和用法。
|
12月前
|
SQL 安全 Go
Go ORM 干啥的?
Go ORM 干啥的?