1. 程式人生 > 程式設計 >gorm的簡單使用和注意事項

gorm的簡單使用和注意事項

Gorm當前支援MySql,PostgreSql,Sqlite等主流資料庫

1.安裝

首先安裝資料庫驅動go get github.com/go-sql-driver/mysql 然後安裝gorm包go get github.com/jinzhu/gorm

2.使用小示例

package main

import (
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql" // 包裝
)

type User struct {
   Id int64
   UserId int64
   AddId int64
   Name string
   Address string
}

type
Address struct { Id int64 UserId int64 AddId int64 AddName string AddLocation string } func main() { db,err := gorm.Open("mysql","root:123456@/guolianlc?charset=utf8&parseTime=True&loc=Local") if err != nil { fmt.Println("connect db error: ",err) } defer db.Close() if
db.HasTable(&User{}) { db.AutoMigrate(&User{}) } else { db.CreateTable(&User{}) } //db.Set("gorm:table_options","ENGINE=InnoDB").CreateTable(&Address{}) db.AutoMigrate(&Address{}) db.Model(&User{}).AddForeignKey("add_id","addresses(id)","RESTRICT"
,"RESTRICT") db.Model(&User{}).AddForeignKey("add_id","RESTRICT") db.Model(&User{}).AddIndex("idx_user_add_id","add_id") db.Model(&User{}).AddUniqueIndex("idx_user_id","user_id") } 複製程式碼

3.表級別操作

  • AutoMigrate() db.AutoMigrate(&Address{}) AutoMigrate()執行後,會自動migrate對應的model.僅僅新增新增的欄位,不會進行修改已有的欄位型別,刪除欄位的操作
  • HasTable() 檢查表是否存在
  • CreateTable() db.Set("gorm:table_options","ENGINE=InnoDB").CreateTable(&Address{}) 建立表 預設情況下,表名為結構體名的複數形式,當然也可以禁用; db.SingularTable(true)
  • DropTable()/ DropTableIfExists() 刪除表
  • ModifyColumn() 修改列
  • DropColumn() 刪除列
  • AddForeignKey() 引數 : 1th:外來鍵欄位,2th:外來鍵表(欄位),3th:ONDELETE,4th:ONUPDATE db.Model(&User{}).AddForeignKey("add_id","addresses(id)","RESTRICT","RESTRICT") 兩個表中的欄位都必須存在,就像Users表中的add_id欄位,如果不存在,無法自動新增欄位,並自動建立外來鍵
  • AddIndex() / AddUniqueIndex 新增索引,新增唯一值索引
    db.Model(&User{}).AddForeignKey("add_id","user_id")
複製程式碼
  • RemoveIndex() 刪除索引

示例中最終創建出的表結構:

CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,`user_id` bigint(20) DEFAULT NULL,`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`add_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `idx_user_id` (`user_id`),KEY `idx_user_add_id` (`add_id`),CONSTRAINT `users_add_id_addresses_id_foreign` FOREIGN KEY (`add_id`) REFERENCES `addresses` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
複製程式碼

4.表結構設計以及gorm標籤的使用

go中使用結構體來作為表結構設計的載體,例項:

package main

import (
	"database/sql"
	"fmt"
	"time"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql" // 包裝
)

type User struct {
	gorm.Model
	UserId    int64 `gorm:"index"`
	Birthday  time.Time
	Age       int           `gorm:"column:age"`                     //可定製列表名
	Name      string        `gorm:"size:255;index:idx_name_add_id"` // string預設長度為255,使用這種tag重設。
	Num       int           `gorm:"AUTO_INCREMENT"`                 // 自增
	Email     string        `gorm:"type:varchar(100);unique_index"`
	AddressID sql.NullInt64 `gorm:"index:idx_name_add_id"`
	IgnoreMe  int           `gorm:"-"` // 忽略這個欄位
        Desction  string        `gorm:"size:2049;comment:'使用者描述欄位'"`
Status       string `gorm:"type:enum('published','pending','deleted');default:'pending'"`
}

//設定表名,預設是結構體的名的複數形式
func (User) TableName() string {
	return "VIP_USER"
}

func main() {
	db,"root:123456@/guolianlc?charset=utf8&parseTime=True&loc=Local")
	if err != nil {
		fmt.Println("connect db error: ",err)
	}
	defer db.Close()
	if db.HasTable(&User{}) {
		db.AutoMigrate(&User{})
	} else {
		db.CreateTable(&User{})
	}
}
複製程式碼

插入一條測試語句後,查詢表結構如下:

image.png

gorm.Model為內建的結構體,結構如下:

// 基本模型的定義
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}
複製程式碼

創建出來的表結構為:

CREATE TABLE `VIP_USER` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,`created_at` timestamp NULL DEFAULT NULL,`updated_at` timestamp NULL DEFAULT NULL,`deleted_at` timestamp NULL DEFAULT NULL,`birthday` timestamp NULL DEFAULT NULL,`age` int(11) DEFAULT NULL,`num` int(11) DEFAULT NULL,`email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`address_id` bigint(20) DEFAULT NULL,`desction` varchar(2049) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '使用者描述欄位',`status` enum('published','pending','deleted') COLLATE utf8mb4_unicode_ci DEFAULT 'pending',UNIQUE KEY `uix_VIP_USER_email` (`email`),KEY `idx_VIP_USER_deleted_at` (`deleted_at`),KEY `idx_VIP_USER_user_id` (`user_id`),KEY `idx_name_add_id` (`name`,`address_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
複製程式碼

組合索引.png

資料插入時,僅僅插入業務資料即可,created_atupdated_at,deleted_at欄位不用手動設定值,gorm會幫我們自動維護這些欄位的值,當首次插入時,created_atupdated_at欄位的值是相同的,都為當前資料記錄插入的時間

  var user User = User{
             UserId: 1,Birthday: time.Now(),Age: 12,Name:"zhangsan",Num: 12,Email:"[email protected]",AddressID:sql.NullInt64{Int64 : int64(1),Valid : err == nil},Desction:"first",}
        if err := db.Model(&User{}).Create(&user).Error; err != nil{

        }
複製程式碼

資料插入後的記錄詳情.png

當資料執行刪除操作時,預設情況下執行的是軟刪除,僅僅設定deleted_at欄位的值,為執行刪除操作的時間

if err := db.Model(&User{}).Where("user_id=?",1).Delete(&User{}).Error; err != nil {
        }
複製程式碼

執行刪除操作.png

如果業務上需要,讀取包含軟刪除的資料,可以在查詢時加上

var usr = make([]*User,0)
        if err := db.Unscoped().Model(&User{}).Where("user_id=?",1).Find(&usr).Error; err != nil {}
        for _,usser := range usr {
            fmt.Println(usser)
        }
複製程式碼

Output:

&{{1 2019-05-15 13:34:23 +0800 CST 2019-05-15 13:34:23 +0800 CST 2019-05-15 13:42:13 +0800 CST} 1 2019-05-15 13:34:23 +0800 CST 12 zhangsan 12 [email protected] {1 true} 0 first}
複製程式碼

如果需要永久的刪除資料,也就是物理刪除,可以在Unscoped()的基礎上,執行Deleted()

 if err := db.Unscoped().Model(&User{}).Where("user_id=?",1).Delete(&User{}).Error; err != nil {}
複製程式碼

物理刪除.png

5.增刪改查

##增

if err := tx.Model(&model.Teatures{}).Create(teatureRecord).Error; err != nil {
				ErrMsg := fmt.Sprintf("%s",err)
				if strings.HasPrefix(ErrMsg,"Error 1062: Duplicate entry") {
					continue
				}
				logrus.Errorln("updateUser,sava user teature err: ",err)
				tx.Rollback()
				return
			}
複製程式碼

if err := tx.Where("created_at=?",int64Time).Delete(&model.User{}).Error; err != nil {
		tx.Rollback()
		logrus.Errorln("updateUser,delete user err: ",err)
		return
	}
複製程式碼

if err := common.Db.Model(&model.User{}).
						Where("created_at=? and usr_id=? and usr_name=? and usr_code=?",int64Time,UserId,UserName,UserCode).
						Updates(
							map[string]interface{}{
								......
							}).Error; err != nil {
						logrus.Errorln("UpdateUsers,update user record err: ",err)
						return
					}
複製程式碼

本示例中,給出的更改語法使用的是map字典,當然你也可以傳入資料庫字典結構體,但是需要注意的是: 在實際應用中,我們的文章編輯後臺,在刪除某個欄位後,比如文章的摘要,進行更新提交時,發現更改並未生效,檢視後臺gorm轉化成的sql語句,發現並沒有更新摘要欄位,這是因為,當結構體的某個欄位為零值的時候,傳入到updates方法中,並沒有顯示該欄位,而udpates方法是根據該結構體有值的欄位進行更新的,沒有值的欄位,並沒有做任何操作,所以上述進行的更新也未起作用,這些細節需要格外注意

	dbUsers := make([]*model.User,0)
	if err := common.Db.Model(&model.User{}).Where("created_at=?",int64Time).Find(&dbUsers).Error; err != nil {
		logrus.Errorln(err)
		return
	}
複製程式碼

6.事務操作

        tx := common.Db.Begin()
	// 1.刪除資料

	if err := tx.Where("created_at=?",err)
		return
	}
	// 2. 插入新資料
	for useOrder,user := range users {
		for teatureOrder,teature := range user.Teatures {
			var teatureRecord = &model.Teatures{
				......
			}
			
			if err := tx.Model(&model.Teatures{}).Create(teatureRecord).Error; err != nil {
				ErrMsg := fmt.Sprintf("%s",err)
				tx.Rollback()
				return
			}
		}
	}
	// 提交事務操作
	tx.Commit()
複製程式碼

7.注意事項

在實際使用過程中,可能有需要需要注意的地方,雖然業務上可以實現相應的功能但是,執行效率上還是得注意優化的,比如:

query := common.AllianceDb.Model(&model.LargeIncrPlateStock{}).Order("time_stamp desc").First(lastestLarge)
	if query.Error != nil {
		if query.RecordNotFound() {
			return nil,nil
		}
		logrus.Errorln("GetNewLargeIncrStocks,failed get today large increase stock...")
		return nil,query.Error
	}
複製程式碼

業務上,我想要獲取當前最新的一條記錄。使用了Order()方法和First()方法組合,倒敘後取第一條記錄即是最新的一條記錄,但是,檢視gorm的First方法可以看出:

// First find first record that match given conditions,order by primary key
func (s *DB) First(out interface{},where ...interface{}) *DB {
	newScope := s.NewScope(out)
	newScope.Search.Limit(1)
	return newScope.Set("gorm:order_by_primary_key","ASC").
		inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
}
複製程式碼

其使用了主鍵的升序排序,這樣有什麼影響呢,其轉換後的sql語句如下:

SELECT * FROM `large_incr_plate_stocks`   ORDER BY time_stamp desc,`large_incr_plate_stocks`.`id` ASC LIMIT 1
複製程式碼

explain發現使用的是檔案排序:

mysql> explain SELECT * FROM `large_incr_plate_stocks`   ORDER BY time_stamp desc,`large_incr_plate_stocks`.`id` ASC LIMIT 1
    -> ;
+----+-------------+-------------------------+------------+------+---------------+------+---------+------+-------+----------+----------------+
| id | select_type | table                   | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra          |
+----+-------------+-------------------------+------------+------+---------------+------+---------+------+-------+----------+----------------+
|  1 | SIMPLE      | large_incr_plate_stocks | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 22852 |   100.00 | Using filesort |
+----+-------------+-------------------------+------------+------+---------------+------+---------+------+-------+----------+----------------+
1 row in set,1 warning (0.00 sec)
複製程式碼

可以通過倒敘特定欄位後,查詢列表取第一條記錄來達到相同的效果:

query := common.AllianceDb.Model(&model.LargeIncrPlateStock{}).Order("time_stamp desc").Limit(1).Find(&lastestLarge)
	if query.Error != nil {
		logrus.Errorln("GetNewLargeIncrStocks,query.Error
	}
	if len(lastestLarge) == 0 {
		return nil,nil
	}
複製程式碼

explain:

mysql> explain  SELECT * FROM `large_incr_plate_stocks`   ORDER BY time_stamp desc LIMIT 1;
+----+-------------+-------------------------+------------+-------+---------------+----------------------------------------+---------+------+------+----------+-------+
| id | select_type | table                   | partitions | type  | possible_keys | key                                    | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------------------------+------------+-------+---------------+----------------------------------------+---------+------+------+----------+-------+
|  1 | SIMPLE      | large_incr_plate_stocks | NULL       | index | NULL          | idx_large_incr_plate_stocks_time_stamp | 9       | NULL |    1 |   100.00 | NULL  |
+----+-------------+-------------------------+------------+-------+---------------+----------------------------------------+---------+------+------+----------+-------+
1 row in set,1 warning (0.00 sec)
複製程式碼