You are viewing the archived documentation of TiDB, which no longer receives updates.

View latest LTS version docs

Build a Simple CRUD App with TiDB and GORM

GORM is a popular open-source ORM library for Golang.

This document describes how to use TiDB and GORM to build a simple CRUD application.

Step 1. Launch your TiDB cluster

The following introduces how to start a TiDB cluster.

Use a TiDB Serverless cluster

For detailed steps, see Create a TiDB Serverless cluster.

Use a local cluster

For detailed steps, see Deploy a local test cluster or Deploy a TiDB Cluster Using TiUP.

Step 2. Get the code

git clone https://github.com/pingcap-inc/tidb-example-golang.git

The following instructions take v1.23.5 as an example.

To adapt TiDB transactions, write a toolkit util according to the following code:

package util import ( "gorm.io/gorm" ) // TiDBGormBegin start a TiDB and Gorm transaction as a block. If no error is returned, the transaction will be committed. Otherwise, the transaction will be rolled back. func TiDBGormBegin(db *gorm.DB, pessimistic bool, fc func(tx *gorm.DB) error) (err error) { session := db.Session(&gorm.Session{}) if session.Error != nil { return session.Error } if pessimistic { session = session.Exec("set @@tidb_txn_mode=pessimistic") } else { session = session.Exec("set @@tidb_txn_mode=optimistic") } if session.Error != nil { return session.Error } return session.Transaction(fc) }

Change to the gorm directory:

cd gorm

The structure of this directory is as follows:

. ├── Makefile ├── go.mod ├── go.sum └── gorm.go

gorm.go is the main body of the gorm. Compared with go-sql-driver/mysql, GORM avoids differences in database creation between different databases. It also implements a lot of operations, such as AutoMigrate and CRUD of objects, which greatly simplifies the code.

Player is a data entity struct that is a mapping for tables. Each property of a Player corresponds to a field in the player table. Compared with go-sql-driver/mysql, Player in GORM adds struct tags to indicate mapping relationships for more information, such as gorm:"primaryKey;type:VARCHAR(36);column:id".

package main import ( "fmt" "math/rand" "github.com/google/uuid" "github.com/pingcap-inc/tidb-example-golang/util" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" ) type Player struct { ID string `gorm:"primaryKey;type:VARCHAR(36);column:id"` Coins int `gorm:"column:coins"` Goods int `gorm:"column:goods"` } func (*Player) TableName() string { return "player" } func main() { // 1. Configure the example database connection. db := createDB() // AutoMigrate for player table db.AutoMigrate(&Player{}) // 2. Run some simple examples. simpleExample(db) // 3. Explore more. tradeExample(db) } func tradeExample(db *gorm.DB) { // Player 1: id is "1", has only 100 coins. // Player 2: id is "2", has 114514 coins, and 20 goods. player1 := &Player{ID: "1", Coins: 100} player2 := &Player{ID: "2", Coins: 114514, Goods: 20} // Create two players "by hand", using the INSERT statement on the backend. db.Clauses(clause.OnConflict{UpdateAll: true}).Create(player1) db.Clauses(clause.OnConflict{UpdateAll: true}).Create(player2) // Player 1 wants to buy 10 goods from player 2. // It will cost 500 coins, but player 1 cannot afford it. fmt.Println("\nbuyGoods:\n => this trade will fail") if err := buyGoods(db, player2.ID, player1.ID, 10, 500); err == nil { panic("there shouldn't be success") } // So player 1 has to reduce the incoming quantity to two. fmt.Println("\nbuyGoods:\n => this trade will success") if err := buyGoods(db, player2.ID, player1.ID, 2, 100); err != nil { panic(err) } } func simpleExample(db *gorm.DB) { // Create a player, who has a coin and a goods. if err := db.Clauses(clause.OnConflict{UpdateAll: true}). Create(&Player{ID: "test", Coins: 1, Goods: 1}).Error; err != nil { panic(err) } // Get a player. var testPlayer Player db.Find(&testPlayer, "id = ?", "test") fmt.Printf("getPlayer: %+v\n", testPlayer) // Create players with bulk inserts. Insert 1919 players totally, with 114 players per batch. bulkInsertPlayers := make([]Player, 1919, 1919) total, batch := 1919, 114 for i := 0; i < total; i++ { bulkInsertPlayers[i] = Player{ ID: uuid.New().String(), Coins: rand.Intn(10000), Goods: rand.Intn(10000), } } if err := db.Session(&gorm.Session{Logger: db.Logger.LogMode(logger.Error)}). CreateInBatches(bulkInsertPlayers, batch).Error; err != nil { panic(err) } // Count players amount. playersCount := int64(0) db.Model(&Player{}).Count(&playersCount) fmt.Printf("countPlayers: %d\n", playersCount) // Print 3 players. threePlayers := make([]Player, 3, 3) db.Limit(3).Find(&threePlayers) for index, player := range threePlayers { fmt.Printf("print %d player: %+v\n", index+1, player) } } func createDB() *gorm.DB { dsn := "root:@tcp(127.0.0.1:4000)/test?charset=utf8mb4" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) if err != nil { panic(err) } return db } func buyGoods(db *gorm.DB, sellID, buyID string, amount, price int) error { return util.TiDBGormBegin(db, true, func(tx *gorm.DB) error { var sellPlayer, buyPlayer Player if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Find(&sellPlayer, "id = ?", sellID).Error; err != nil { return err } if sellPlayer.ID != sellID || sellPlayer.Goods < amount { return fmt.Errorf("sell player %s goods not enough", sellID) } if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Find(&buyPlayer, "id = ?", buyID).Error; err != nil { return err } if buyPlayer.ID != buyID || buyPlayer.Coins < price { return fmt.Errorf("buy player %s coins not enough", buyID) } updateSQL := "UPDATE player set goods = goods + ?, coins = coins + ? WHERE id = ?" if err := tx.Exec(updateSQL, -amount, price, sellID).Error; err != nil { return err } if err := tx.Exec(updateSQL, amount, -price, buyID).Error; err != nil { return err } fmt.Println("\n[buyGoods]:\n 'trade success'") return nil }) }

Step 3. Run the code

The following content introduces how to run the code step by step.

Step 3.1 Modify parameters for TiDB Cloud

If you are using a TiDB Serverless cluster, modify the value of the dsn in gorm.go:

dsn := "root:@tcp(127.0.0.1:4000)/test?charset=utf8mb4"

Suppose that the password you set is 123456, and the connection parameters you get from the cluster details page are the following:

  • Endpoint: xxx.tidbcloud.com
  • Port: 4000
  • User: 2aEp24QWEDLqRFs.root

In this case, you can modify the dsn as follows:

dsn := "2aEp24QWEDLqRFs.root:123456@tcp(xxx.tidbcloud.com:4000)/test?charset=utf8mb4&tls=true"

Step 3.2 Run the code

To run the code, you can run make build and make run respectively:

make build # this command executes `go build -o bin/gorm-example` make run # this command executes `./bin/gorm-example`

Or you can use the native commands:

go build -o bin/gorm-example ./bin/gorm-example

Or run the make command directly, which is a combination of make build and make run.

Step 4. Expected output

GORM Expected Output