Простое использование и меры предосторожности горма

MySQL
Простое использование и меры предосторожности горма

В настоящее время Gorm поддерживает MySql, PostgreSql, Sqlite и другие основные базы данных.

1. Установка

Сначала установите драйвер базы данныхgo get github.com/go-sql-driver/mysqlЗатем установите пакет gormgo 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", "addresses(id)", "RESTRICT", "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()После запуска соответствующая модель будет перенесена автоматически, будут добавлены только новые поля, а существующие типы полей не будут изменены или поля будут удалены.
  • HasTable()Проверить, существует ли таблица
  • CreateTable() db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&Address{})создать таблицу По умолчанию имя таблицы представляет собой форму множественного числа от имени структуры, но, конечно, его можно отключить;db.SingularTable(true)
  • DropTable()/ DropTableIfExists()удалить таблицу
  • ModifyColumn()Изменить столбец
  • DropColumn()удалить столбец
  • AddForeignKey()Параметры: 1-й: поле внешнего ключа, 2-й: таблица внешнего ключа (поле), 3-й: ONDELETE, 4-й: ONUPDATEdb.Model(&User{}).AddForeignKey("add_id", "addresses(id)", "RESTRICT", "RESTRICT")Поля в обеих таблицах должны существовать, как и поле add_id в таблице Users.Если его нет, поля не могут быть добавлены автоматически, а внешние ключи будут созданы автоматически.
  • AddIndex() / AddUniqueIndexдобавить индекс, добавить индекс уникального значения
    db.Model(&User{}).AddForeignKey("add_id", "addresses(id)", "RESTRICT", "RESTRICT")
    db.Model(&User{}).AddIndex("idx_user_add_id", "add_id")
    db.Model(&User{}).AddUniqueIndex("idx_user_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, 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{})
	}
}

После вставки тестового оператора структура таблицы запроса выглядит следующим образом:

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,
  `user_id` bigint(20) DEFAULT NULL,
  `birthday` timestamp NULL DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci 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',
  PRIMARY KEY (`id`),
  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_atиupdated_at,deleted_atПоля не нужно задавать вручную значения, горм автоматически поддержит значения этих полей для нас.При вставке в первый раз,created_atиupdated_atЗначение поля одинаковое, оба являются временем вставки текущей записи данных.

  var user User = User{
             UserId: 1,
             Birthday: time.Now(),
             Age: 12,
             Name:"zhangsan",
             Num: 12,
             Email:"zhangsan@alibaba.com",
             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 zhangsan@alibaba.com {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
					}

В этом примере данный синтаксис изменения использует словарь карты, конечно, вы также можете передать структуру словаря базы данных, но следует отметить, что: В практических приложениях, в нашем фоне редактирования статьи, после удаления поля, такого как аннотация статьи, при отправке обновления обнаруживается, что изменение не вступило в силу.Проверьте оператор sql, преобразованный в фоновый горм, и обнаруживаем, что абстрактное поле не было обновлено, потому что, когда поле структуры имеет нулевое значение, оно передается в метод обновлений, и поле не отображается, а метод 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=?", int64Time).Delete(&model.User{}).Error; err != nil {
		tx.Rollback()
		logrus.Errorln("updateUser , delete user err: ", 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)
				if strings.HasPrefix(ErrMsg, "Error 1062: Duplicate entry") {
					continue
				}
				logrus.Errorln("updateUser, sava user teature err: ", 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(), первая запись, сделанная после воспоминаний, является самой последней записью, но, глядя на метод First из gorm, мы видим, что:

// 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

объясните, что используется сортировка файлов:

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, failed get today large increase stock...")
		return nil, 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)