ORM


title: ORM


ORM

ORM(Object Relational Map)对象关系映射,一般指持久化数据(数据库?)和实体对象的映射。

在MVC(Model View Controller)架构中就是个简洁的Model

自动生成SQL语句

ORM框架

O(object)对应程序中的实体对象,它可以是一个map类型的数据,也可以是struct类型的数据(如果是struct类型,则需要使用反射(reflect)来获取字段名)。

R(relation)对应数据库,因为关系型数据库利用实体间的关系连接数据。

M(Map)我们此次任务需要实现的就是这个映射(map),很多语言已经有了orm框架

实现ORM的思路

建立映射

ORM一切的基础在OR映射上,CRUD操作也不例外。任务中规定的所有增删改查方法都需要传入一个结构体或map(实例),这个实例就对应着要操作的数据库字段。而映射规则就是:

(1)一个结构体或map对应一张表。类名是单数,且首字母大写;表名是复数,且全部是小写。比如,表books对应类Book

(2)如果名字是不规则复数,则类名依照英语习惯命名,比如,表mice对应类Mouse,表people对应类Person(我暂时就不写这么复杂的了

(3)如果名字包含多个单词,那么类名使用首字母全部大写的骆驼拼写法,而表名使用下划线分隔的小写单词。比如,表book_clubs对应类BookClub,表line_items对应类LineItem

(4)每个表都必须有一个主键字段,通常是叫做id的整数字段。外键字段名约定为单数的表名 + 下划线 + id,比如item_id表示该字段对应items表的id字段。

(5)结构体或map的属性和字段名称一样

要利用反射获取结构体的变量名以及tag

连接数据库

连接到我的mysql数据库,返回一个db实例,这部操作放在所有增删改查的第一步。

增(这不算增)

CreateTable

CreateTable(Myobject interface{})(bool, error)

该方法会根据传入的实例创建一个表,表名根据映射规则与实例名对应,该方法会根据实例的内部变量创建一个空表,因此,内部变量的值可为空,字典对应的映射可为空。

如果表存在,返回一个bool值,如果这个表的字段与要创建的表不一致,返回一个错误。

func (b *orm )CreateTable(Myobject interface{})(bool, error)  {

	db := b.db
	TypeRef := reflect.TypeOf(Myobject).Elem()
	s :=  TypeRef.Name()
//获取表名
	TableName := strings.ToUpper(s[:0]) + s[1:] + "s"
    
//查询是否有这个表
	SqlQuery1 := "SELECT information_schema.SCHEMATA.SCHEMA_NAME FROM information_schema.SCHEMATA where SCHEMA_NAME=" + "'" + TableName + "'"
	if res , _ := db.Query(SqlQuery1); res != nil{
		return true, nil
	}

//创建表
//CREATE TABLE table_name (name type ,...PRIMARY KEY ( _id ))ENGINE=InnoDB DEFAULT CHARSET=utf8;
	var str1 , strkey string
	for i := 0; i < TypeRef.NumField(); i++ {
		// 获取每个成员的结构体字段类型
		fieldType := TypeRef.Field(i)
		// 输出成员名和tag
		Printf("name: %v  tag: '%v'
", fieldType.Name, fieldType.Tag)
		//还需做一个默认值map
		str1 +=fieldType.Tag.Get("column") + " , " + fieldType.Tag.Get("type")
		if fieldType.Tag.Get("primary_key") != "" {
			strkey =  fieldType.Tag.Get("primary_key")
		}
	}
	str1 += "PRIMARY KEY (" + strkey +")"
	SqlQuery2 := "CREATE TABLE " + TableName + "(" + str1 + ")ENGINE=InnoDB DEFAULT CHARSET=utf8;"
	if _, err := db.Exec(SqlQuery2); err != nil {
		return false , err
	}
	return true , nil
}

Create

Create(Myobject interface{})(bool, error)

与上一个方法类似,但同时会插入与实例内容对应的字段。相当于创建并插入。返回值与上一样

func (b *orm )Create(Myobject interface{})(bool, error) {
	db := b.db
	ValueRef := reflect.ValueOf(Myobject).Elem()
	TypeRef := ValueRef.Type()
	s :=  TypeRef.Name()
	TableName := strings.ToUpper(s[:0]) + s[1:] + "s"
	SqlQuery1 := "SELECT information_schema.SCHEMATA.SCHEMA_NAME FROM information_schema.SCHEMATA where SCHEMA_NAME=" + "'" + TableName + "'"
	if res , _ := db.Query(SqlQuery1); res != nil{
		return true, nil
	}
//CREATE TABLE table_name (name type ,...PRIMARY KEY ( _id ))ENGINE=InnoDB DEFAULT CHARSET=utf8;
	var str1 , strkey string
	var name , value []string
	for i := 0; i < TypeRef.NumField(); i++ {
		// 获取每个成员的结构体字段类型
		fieldType := TypeRef.Field(i)
		//还需做一个默认值map
		str1 +=fieldType.Tag.Get("column") + " , " + fieldType.Tag.Get("type")
		if fieldType.Tag.Get("primary_key") != "" {
			strkey =  fieldType.Tag.Get("primary_key")
		}
        
		//获取字段名以及插入的值
		name = append(name, fieldType.Tag.Get("column"))
		value = append(value, ValueRef.Field(i).String())
	}
	str1 += "PRIMARY KEY (" + strkey +")"
	SqlQuery2 := "CREATE TABLE " + TableName + "(" + str1 + ")ENGINE=InnoDB DEFAULT CHARSET=utf8;"
	if _, err := db.Exec(SqlQuery2); err != nil {
		return false , err
	}
//INSERT INTO %s(%s) VALUES(%s)
	SqlQuery3 := Sprintln(`insert into %s (%s) values %s`, TableName , strings.Join(name,","), strings.Join(value,","))
	if res , _ := db.Query(SqlQuery3); res != nil{
		return true, nil
	}
	return  true, nil
}

DeleteAll

DeleteAll(whereExpression string, params...interface{}, Myobject interface{}) error

根据传入的Myobject找到对应的表,然后删除符合条件的所有字段,即批量删除

如果表不存在,返回一个错误

Delete

Delete(Myobject interface{}) error

单个删除,默认通过Myobject的主键删除

返回值同上

Update

Update(Myobject) (bool,error)

利用主键寻找字段

整体更新传入的字段,更新成功返回一个bool值,找不到该表或者字段不吻合,则返回一个错误

FindOne

FindOne(whereExpression string , params...interface{},Myobject interface{})(interface{},error)

根据查询条件和实例找到目标表中的一个字段,返回对应生成的实例(map)。

如果找不到就返回一个错误。

func (b *orm)FindOne(Myobject interface{}) error {
	db := b.db
    //取值和取类型,传入的Myobject应该是接口类型的指针
	TypeRef := reflect.TypeOf(Myobject)
	ValueRef := reflect.ValueOf(Myobject)
    
    //判断传入的值是否有效
	if TypeRef.Kind() != reflect.Ptr {
		return errors.New("should transport a ptr type")
	}
	if ValueRef.Elem().CanAddr() == false || ValueRef.Elem().CanSet() == false {
		return  errors.New("such ptr cannot be modified")
	}
    
    //构造SQL
	TableName := GetTableName(Myobject)
	SqlQuery := Sprintln("SELECT * FORM %s WHERE (%s) LIMIT 1",TableName , b.wheres)
	var addr []interface{}
    //ValueRef.Elem()为接口类型,这里要遍历接口的元素的地址,并存入addr数组
	for i := 0; i < ValueRef.Elem().NumField(); i++ {
        t := ValueRef.Field(i).Addr().interface()//地址也要注意以接口类型返回
		addr = append(addr, t)
	}
	rows, err := db.Query(SqlQuery);
	if  err != nil{
		return err
	}
    //将查询的值赋给addr,也相当于传回了Myobject
	if  err := rows.Scan(addr...);err != nil{
		return err
	}

	return nil
}

FindAll

FindAll(whereExpression string , params...interface{},Myobject interface{})([]interface{},error)

根据查询条件和实例找到目标表中的所有字段,返回对应生成的实例数组(map)。

如果找不到就返回一个错误。

func (b *orm)Find(Myobject []interface{}) error {
	db := b.db
    //虽然Myobject是接口数组,但这里没有改动
	TypeRef := reflect.TypeOf(Myobject)
	ValueRef := reflect.ValueOf(Myobject)
	if TypeRef.Kind() != reflect.Ptr {
		return errors.New("should transport a ptr type")
	}
	if ValueRef.Elem().CanAddr() == false || ValueRef.Elem().CanSet() == false {
		return  errors.New("such ptr cannot be modified")
	}
	TableName := GetTableName(Myobject)
	SqlQuery := Sprintln("SELECT * FORM %s WHERE (%s) ",TableName , b.wheres)
	rows, err := db.Query(SqlQuery)
	if  err != nil{
		return err
	}
    
//返回多值,Myobject是接口数组,因此要多些一层反射
	v := ValueRef.Elem()//v是传入的数组
	i := 0
	for rows.Next() {
		t := v.Field(i) //t是数组元素,即结构体
		i += 1
		var addr []interface{}
		for j :=0 ; j < t.Elem().NumField(); j ++ {
		 	tt := t.Field(i).Addr().Interface()//tt为结构体的元素的地址
			 addr = append(addr, tt)
		}
		if  err := rows.Scan(addr...);err != nil{
			return err
		}
	}

	return nil
}

进阶

链式操作(where)

type orm struct {
	db *sql.DB
	wheres string
}
//传入where的参数应该如下
//where("id = ? and price in (?)",myObject.ID,[]int {1,2})
//故只需做问号替换
func (b *orm)where(expression string, params...interface{}) *orm {
	var res string
	var  j = 0 
	for i := 0 ; i < len(expression) ; i ++ {
		res += string(expression[i])
		if expression[i] == '?' {
			res +=   format(reflect.ValueOf(params[j]))
			j += 1
		}
	}
	b.wheres = res
	return  b
}

//format用来调整sql where字句字符串格式,用的是别人的代码
func format(v reflect.Value) string {
	//断言出time类型直接转unix时间戳
	if t, ok := v.Interface().(time.Time); ok {
		return Sprintf("FROM_UNIXTIME(%d)", t.Unix())
	}
	switch v.Kind() {
	case reflect.String:
		return Sprintf(`'%s'`, v.Interface())
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
		return Sprintf(`%d`, v.Interface())
	case reflect.Float32, reflect.Float64:
		return Sprintf(`%f`, v.Interface())
	//如果是切片类型,遍历元素,递归格式化成"(, , , )"形式
	case reflect.Slice:
		var values []string
		for i := 0; i < v.Len(); i++ {
			values = append(values, format(v.Index(i)))
		}
		return Sprintf(`%s`, strings.Join(values, ","))
	//接口类型剥一层递归
	case reflect.Interface:
		return format(v.Elem())
	}
	return ""
}

想法

我感觉把ORM这个映射做成一个结构体来替代db,也即把这些CRUD方法实现在Myobject上不是更能体现ORM的意思吗,会不会更直观呢?然后Myobject里就包含了数据库对应的表名,和字段结构体。

不熟go语言。不熟反射,不熟。。。太难了

原文地址:https://www.cnblogs.com/nothatdinger/p/13611648.html