zoukankan      html  css  js  c++  java
  • 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语言。不熟反射,不熟。。。太难了

  • 相关阅读:
    学习 TList 类的实现[1]
    Dll 使用 PChar 参数的小例子
    学习使用资源文件[11]
    【转载】OGRE 内存管理
    【转载】Ogre的内存分配策略
    【转载】.NET面试题系列[0]
    【转载】深入探讨透视投影坐标变换
    【转载】四元数-Quaterion
    【转载】齐次坐标
    【转载】深入研究Windows内部原理绝对经典的资料
  • 原文地址:https://www.cnblogs.com/nothatdinger/p/13611648.html
Copyright © 2011-2022 走看看