---
# System prepended metadata

title: 05. Gorm Framwork V2(Demo by MySQL)
tags: [golang練習]

---

# 05. Gorm Framwork V2(Demo by MySQL)
###### tags: `golang練習`
## TL;DR:
1. DB不要開起來等
2. 若一開始沒資料, Migrate時, FK constraint會錯誤, 需手動於&gorm.Config設置關閉
3. 不要輕易做Migration的動作，需有資料庫人員認可或直接給他們操作（大公司）
4. MySQL的DB連線不一定要做關閉動作，但Redis一定要
5. DB等連線都可以使用pool開
## DB
```go
// 資料庫連接資料
type Db_message struct {
	Username      string
	Password      string
	Addr          string
	Database      string
	Max_lifetime  int
	Max_openconns int
	Max_idleconns int
}

func Db_init(config *tools.Config_data, fk_constraint bool) (db_conn *gorm.DB, err error) {
	// gorm資料庫連接
	dsn := fmt.Sprintf("%s:%s@tcp(%s)/?charset=utf8mb4&parseTime=True&loc=Local", config.Mysql.Username, config.Mysql.Password, config.Mysql.Addr)
	db_conn, err = gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		return nil, err
	}

	// 若無則新建資料庫(schema)
	err = db_conn.Exec("CREATE DATABASE IF NOT EXISTS " + config.Mysql.Database).Error
	if err != nil {
		return nil, err
	}

	// 重新連接指定schema
	dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", config.Mysql.Username, config.Mysql.Password, config.Mysql.Addr, config.Mysql.Database)
	db_conn, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger:                                   logger.Default.LogMode(logger.Silent),
		DisableForeignKeyConstraintWhenMigrating: !fk_constraint,
	})
	if err != nil {
		return nil, err
	}

	// mysql資料庫本體
	db, err := db_conn.DB()
	if err != nil {
		return nil, err
	}

	// 確保資料庫安全關閉
	db.SetConnMaxLifetime(time.Duration(config.Mysql.MaxLifetime) * time.Second)
	// 閒置連接數（官方建議跟SetMaxOpenConns一致）
	db.SetMaxIdleConns(config.Mysql.MaxIdleconns)
	// 限制資料庫連接數
	db.SetMaxOpenConns(config.Mysql.MaxOpenconns)

	return db_conn, nil
}

func Db_migrate(db_conn *gorm.DB) error {
	all_tables := []interface{}{&Account{}, &Todolist{}}
	// 更新資料庫資料
	err := db_conn.AutoMigrate(all_tables...)
	if err != nil {
		return err
	}

	// 確認table存在
	migrator := db_conn.Migrator()
	for _, v := range all_tables {
		has := migrator.HasTable(v)
		if !has {
			return err
		}

	}

	// 關閉DB
	err = Db_close(db_conn)
	if err != nil {
		return err
	}
	return nil
}

func Db_conn_web(c *gin.Context) (*gorm.DB, error) {
	configs_raw, b := c.Get("configs")
	if !b {
		err := errors.New("can't get configs")
		return nil, err
	}
	configs := configs_raw.(*tools.Config_data)
	db_conn, err := Db_init(configs, false)
	if err != nil {
		return nil, err
	}
	return db_conn, nil
}

func Db_close(db_conn *gorm.DB) error {
	db, err := db_conn.DB()
	if err != nil {
		return err
	}
	err = db.Close()
	if err != nil {
		return err
	}
	return nil
}
```
---
## DB tables
```go
/// Accounts table
type Account struct {
	gorm.Model
	Username  string     `gorm:"type:varchar(30) NOT NULL;"`
	Password  string     `gorm:"type:varchar(64) NOT NULL;"`
	Todolists []Todolist `gorm:"foreignKey:AccountID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

// Todolist table
type Todolist struct {
	gorm.Model
	Subject   string `gorm:"type:varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;"`
	Status    int    `gorm:"type:int NOT NULL;"`
	AccountID uint
}
```
1. **FK tags**
 
>On Delete和On Update都有Restrict, No Action, Cascade, Set Null屬性。
>
>ON DELETE:
restrict(約束):當在父表（即外來鍵的來源表）中刪除對應記錄時，首先檢查該記錄是否有對應外來鍵，如果有則不允許刪除。
no action:意思同restrict.即如果存在從資料，不允許刪除主資料。
cascade(級聯):當在父表（即外來鍵的來源表）中刪除對應記錄時，首先檢查該記錄是否有對應外來鍵，如果有則也刪除外來鍵在子表（即包含外來鍵的表）中的記錄。
set null:當在父表（即外來鍵的來源表）中刪除對應記錄時，首先檢查該記錄是否有對應外來鍵，如果有則設定子表中該外來鍵值為null（不過這就要求該外來鍵允許取null）
>
>ON UPDATE:
restrict(約束):當在父表（即外來鍵的來源表）中更新對應記錄時，首先檢查該記錄是否有對應外來鍵，如果有則不允許更新。
no action:意思同restrict.
cascade(級聯):當在父表（即外來鍵的來源表）中更新對應記錄時，首先檢查該記錄是否有對應外來鍵，如果有則也更新外來鍵在子表（即包含外來鍵的表）中的記錄。
set null:當在父表（即外來鍵的來源表）中更新對應記錄時，首先檢查該記錄是否有對應外來鍵，如果有則設定子表中該外來鍵值為null（不過這就要求該外來鍵允許取null）。
>
>注：NO ACTION和RESTRICT的區別：只有在及個別的情況下會導致區別，前者是在其他約束的動作之後執行，後者具有最高的優先權執行。

2. **This FK is one to many type**

```

```