在 Go 專案使用 GORM 操作 PostgreSQL 資料庫

前言

由於 PostgreSQL 的 numeric 型別可以處理非常大的數字,因此在處理加密貨幣金額時,可以使用 PostgreSQL 來儲存。

以 Solidity 的 uint256 型別為例,可以使用數字型別的 numeric(78,0) 來儲存。

常用的數字型別如下:

Type Storage Size Range
smallint 2 bytes -32768 到 +32767
integer 4 bytes -2147483648 到 +2147483647
bigint 8 bytes -9223372036854775808 到 +9223372036854775807
numeric variable 小數點前 131,072 位,小數點後 16,383 位

安裝套件

安裝套件。

1
2
3
4
go get github.com/joho/godotenv
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/jackc/pgtype

連線

database.go 檔建立一個 DB() 方法,以建立連線並初始化一個 DB 實例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package database

import (
"fmt"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
"os"
)

func DB() *gorm.DB {
dsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_USERNAME"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_DATABASE"),
)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
return db
}

定義模型

model/log.go 檔定義資料模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package model

import (
"github.com/jackc/pgtype"
"time"
)

type Log struct {
TransactionHash string `gorm:"not null;type:char(66);primaryKey;" json:"transactionHash"`
BlockNumber uint64 `gorm:"not null;" json:"blockNumber"`
EventName string `gorm:"not null;type:varchar(255);" json:"eventName"`
Amount pgtype.Numeric `gorm:"not null;type:numeric(78,0);" json:"amount"`
CreatedAt time.Time `gorm:"not null;" json:"createdAt"`
UpdatedAt time.Time `gorm:"not null;" json:"updatedAt"`
}
  • TransactionHash 為 64 個固定字元再加上 0x 前綴,因此使用 char(66) 來儲存。
  • EventName 為一個簡短的可變字串,因此使用 varchar(255) 來儲存。
  • Amount 為一個龐大數字(uint256),因此使用 numeric(78,0) 來儲存。

數字轉型

轉換 big.Int 型別到 pgtype.Numeric 型別。

1
2
3
4
5
6
7
8
9
10
11
import (
"github.com/jackc/pgtype"
)

func ToNumeric(v interface{}) *pgtype.Numeric {
n := pgtype.Numeric{}
if err := n.Set(v); err != nil {
log.Fatal(err)
}
return &n
}

新增資料

新增多資料。

1
database.DB().Create(logs)

批次新增資料。

1
database.DB().CreateInBatches(logs, 100)

型別擴充

如果需要將 Numeric 序列化或反序列化,需要改用 shopspring-numeric 包。

1
2
3
4
5
6
7
8
9
10
11
package model

import (
"github.com/jackc/pgtype/ext/shopspring-numeric"
)

type Log struct {
// ...
Amount pgtype.Numeric `gorm:"not null;type:numeric(78,0);" json:"amount"`
// ...
}

遷移

database.go 檔建立一個 Migrate() 方法,以新增資料表。

1
2
3
4
5
6
7
func Migrate() {
if err := DB().AutoMigrate(
&model.Log{},
); err != nil {
log.Fatal(err)
}
}

參考資料