GORM 基础 -- CRUD 接口

Posted chinusyan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GORM 基础 -- CRUD 接口相关的知识,希望对你有一定的参考价值。

1、Create

1.1 创建纪录

user := UserName: "Jinzhu", Age: 18, Birthday: time.Now()

result := db.Create(&user) // pass pointer of data to Create

user.ID             // 回填插入数据的主键
result.Error        // 返回的 error 信息
result.RowsAffected // 返回插入记录的个数

1.2 用选定的字段创建记录

创建一个记录并为指定的字段分配值

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

创建一个记录并忽略传递给要忽略的字段的值。

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

1.3 Batch Insert

为了有效地插入大量的记录,将一个切片传递给Create方法。GORM将生成一条SQL语句来插入所有数据并回填主键值,钩子方法(hook methods)也将被调用。

var users = []UserName: "jinzhu1", Name: "jinzhu2", Name: "jinzhu3"
db.Create(&users)

for _, user := range users 
  user.ID // 1,2,3

你可以在创建CreateInBatches时指定批大小,例如:

var users = []UserName: "jinzhu_1", ...., Name: "jinzhu_10000"

// batch size 100
db.CreateInBatches(users, 100)

批量插入也支持 UpsertCreate With Associations时,

CreateBatchSize选项初始化GORM,所有INSERT在创建记录和关联时都会遵守这个选项

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config
  CreateBatchSize: 1000,
)

db := db.Session(&gorm.SessionCreateBatchSize: 1000)

users = [5000]UserName: "jinzhu", Pets: []Petpet1, pet2, pet3...

db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)

1.4 创建钩子(Hooks)

GORM允许用户定义钩子实现BeforeSave, BeforeCreate, AfterSave, AfterCreate。这些钩子方法将在创建记录时被调用,有关生命周期的详细信息请参阅钩子

func (u *User) BeforeCreate(tx *gorm.DB) (err error) 
  u.UUID = uuid.New()

  if u.Role == "admin" 
    return errors.New("invalid role")
  
  return

如果你想跳过Hooks方法,你可以使用SkipHooks会话模式(Session mode),例如:

DB.Session(&gorm.SessionSkipHooks: true).Create(&user)

DB.Session(&gorm.SessionSkipHooks: true).Create(&users)

DB.Session(&gorm.SessionSkipHooks: true).CreateInBatches(users, 100)

1.5 Create From Map

GORM支持从map[string]interface[]map[string]interface创建,例如:

db.Model(&User).Create(map[string]interface
  "Name": "jinzhu", "Age": 18,
)

// batch insert from `[]map[string]interface`
db.Model(&User).Create([]map[string]interface
  "Name": "jinzhu_1", "Age": 18,
  "Name": "jinzhu_2", "Age": 20,
)

当从map创建时,钩子不会被调用,关联不会被保存,主键值也不会被返回填充

1.6 从 SQL Expression/Context Valuer 创建

GORM允许用SQL表达式插入数据,有两种方法来实现这个目标,从map[string]interface自定义数据类型创建,例如:

// Create from map
db.Model(User).Create(map[string]interface
  "Name": "jinzhu",
  "Location": clause.ExprSQL: "ST_PointFromText(?)", Vars: []interface"POINT(100 100)",
)
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));

// Create from customized data type
type Location struct 
  X, Y int


// Scan implements the sql.Scanner interface
func (loc *Location) Scan(v interface) error 
  // Scan a value into struct from database driver


func (loc Location) GormDataType() string 
  return "geometry"


func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr 
  return clause.Expr
    SQL:  "ST_PointFromText(?)",
    Vars: []interfacefmt.Sprintf("POINT(%d %d)", loc.X, loc.Y),
  


type User struct 
  Name     string
  Location Location


db.Create(&User
  Name:     "jinzhu",
  Location: LocationX: 100, Y: 100,
)
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

1.7 进阶

1.7.1 Create With Associations

在创建带有关联的数据时,如果其关联值不是零,则这些关联将被插入,并且将调用其Hooks方法。

type CreditCard struct 
  gorm.Model
  Number   string
  UserID   uint


type User struct 
  gorm.Model
  Name       string
  CreditCard CreditCard


db.Create(&User
  Name: "jinzhu",
  CreditCard: CreditCardNumber: "411111111111"
)
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

您可以使用SelectOmit 跳过保存关联,例如:

db.Omit("CreditCard").Create(&user)

// skip all associations
db.Omit(clause.Associations).Create(&user)

1.7.2 默认值

你可以为使用default标签为字段定义默认值,例如:

type User struct 
  ID   int64
  Name string `gorm:"default:galeone"`
  Age  int64  `gorm:"default:18"`

然后,将零值字段插入数据库时将使用默认值

任何零值,如0"false将不会保存到数据库中那些字段定义的默认值,你可能想使用指针类型或Scanner/Valuer来避免这种情况,例如:

type User struct 
  gorm.Model
  Name string
  Age  *int           `gorm:"default:18"`
  Active sql.NullBool `gorm:"default:true"`

你必须为数据库中有默认值或虚拟/生成值的字段设置默认标记,如果你想在迁移时跳过默认值定义,你可以使用default:(-),例如:

type User struct 
  ID        string `gorm:"default:uuid_generate_v3()"` // db func
  FirstName string
  LastName  string
  Age       uint8
  FullName  string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`

当使用虚拟/生成(virtual/generated)值时,你可能需要禁用它的创建/更新权限,检查字段级权限

1.8 Upsert / On Conflict

GORM为不同的数据库提供了兼容的Upsert支持

import "gorm.io/gorm/clause"

// Do nothing on conflict
db.Clauses(clause.OnConflictDoNothing: true).Create(&user)

// Update columns to default value on `id` conflict
db.Clauses(clause.OnConflict
  Columns:   []clause.ColumnName: "id",
  DoUpdates: clause.Assignments(map[string]interface"role": "user"),
).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; mysql

// Use SQL expression
db.Clauses(clause.OnConflict
  Columns:   []clause.ColumnName: "id",
  DoUpdates: clause.Assignments(map[string]interface"count": gorm.Expr("GREATEST(count, VALUES(count))")),
).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));

// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict
  Columns:   []clause.ColumnName: "id",
  DoUpdates: clause.AssignmentColumns([]string"name", "age"),
).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL

// Update all columns to new value on conflict except primary keys and those columns having default values from sql func
db.Clauses(clause.OnConflict
  UpdateAll: true,
).Create(&users)
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; MySQL

高级查询中查看FirstOrInit, FirstOrCreate

更多细节请检出原始SQL和SQL Builder

2、Query

2.1 检索单个对象

GORM提供了FirstTakeLast方法来从数据库中检索单个对象,它在查询数据库时添加了LIMIT 1条件,如果没有找到记录,它将返回错误ErrRecordNotFound

// Get the first record ordered by primary key
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// Get one record, no specified order
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// Get last record, ordered by primary key desc
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // returns count of records found
result.Error        // returns error or nil

// check error ErrRecordNotFound
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免ErrRecordNotFound错误,你可以像db.Limit(1).Find(&user)一样使用Find, Find方法同时接受结构数据和切片数据

使用 Find而不限制单个对象db.Find(&user)将查询整个表并只返回第一个非性能和不确定性的对象

FirstLast方法将根据主键顺序分别查找第一个和最后一个记录。只有当指向目标结构的指针作为参数传递给方法时,或者当使用db.Model()指定模型时,它们才有效。此外,如果相关模型没有定义主键,则模型将按第一个字段排序。例如:

var user User
var users []User

// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// works because model is specified using `db.Model()`
result := map[string]interface
db.Model(&User).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// doesn't work
result := map[string]interface
db.Table("users").First(&result)

// works with Take
result := map[string]interface
db.Table("users").Take(&result)

// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct 
  Code string
  Name string

db.First(&Language)
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

用主键检索多个对象

如果主键是数字,则可以通过内联条件使用主键检索对象。当使用字符串时,需要特别注意避免SQL注入;详细信息请查看安全部分。

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int1,2,3)
// SELECT * FROM users WHERE id IN (1,2,3);

如果主键是一个字符串(例如,像uuid),查询将被写成如下:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

当目标对象有一个主键时,主键将被用于构建条件,例如:

var user = UserID: 10
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(UserID: 10).First(&result)
// SELECT * FROM users WHERE id = 10;

2.2 检索所有对象

// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

2.3 条件(Conditions)

2.3.1 String Conditions

// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string"jinzhu", "jinzhu 2").Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

如果已经设置了对象的主键,则条件查询不会覆盖主键的值,而是将其用作’ and '条件。例如:

var user = UserID: 10
db.Where("id = ?", 20).First(&user)
// SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1

查询将给出record not found错误。因此,在你想使用变量(如user)从数据库获取新值之前,将主键属性(如id)设置为nil

2.3.2 Struct & Map Conditions

// Struct
db.Where(&UserName: "jinzhu", Age: 20).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface"name": "jinzhu", "age": 20).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// Slice of primary keys
db.Where([]int6420, 21, 22).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22

当使用struct查询时,GORM只会查询非零字段,这意味着如果你的字段值为0'',false或其他零值,它将不会被用来构建查询条件,例如:

db.Where(&UserName: "jinzhu", Age: 0).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";

要在查询条件中包含零值,您可以使用map,它将包括所有键值作为查询条件,例如:

db.Where(map[string]interface"Name": "jinzhu", "Age": 0).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

有关详细信息,请参见Specify Struct search fields

2.3.3 指定Struct搜索字段

当使用struct进行搜索时,你可以在查询条件中通过指定使用struct中的特定值来将相关的字段名或dbname传递给Where(),例如:

db.Where(&UserName: "jinzhu", "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

db.Where(&UserName: "jinzhu", "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;

kratos学习从零使用kratos创建一个CRUD的demo,是gorm做数据库操作,经过几天研究,调试代码开源放到github上,还是非常方便的。服务也实现了CRUD的http接口。

目录

前言


本文的原文连接是:
https://blog.csdn.net/freewebsys/article/details/124262158

未经博主允许不得转载。
博主地址是:http://blog.csdn.net/freewebsys

1,关于kratos


Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关框架及工具。

https://go-kratos.dev/docs/

网络上面的demo并不全面,从头来一点点进行研究学习。
学到老活到老。

2,使用


golang 需要使用1.18 的最新版本进行开发吧。
然后安装kratos 工具:

国内下载:
https://golang.google.cn/dl/

语法中文学习:
https://www.runoob.com/go/go-tutorial.html

golang install 加速:

https://goproxy.io/zh/

#kratos 基础工具命令
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest

#golang grpc 特别好的工具,可以直接调用grpc 服务
 go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

kratos new kratos-crud
cd kratos-crud

然后使用 make 工具,提供几个常用的功能,先把环境创建好。

make 

Usage:
 make [target]

Targets:
init                   init env
config                 generate internal proto
api                    generate api proto
build                  build
generate               generate
all                    generate all
help                   show help

protobuffer 文档:
https://developers.google.cn/protocol-buffers/docs/proto3

在 ubuntu系统上安装protoc 工具,默认的工具是3.0.0的版本太低了。
否则报错误找不到

conf/conf.proto:22:5: "google.protobuf.Duration" is not defined.

重新安装 prototc 解决问题,估计是新的库中才有google的proto文件定义。

源码下载地址: 
git clone https://github.com/protocolbuffers/protobuf.git

安装依赖的库:
sudo install -y  autoconf automake libtool curl make g++ unzip  

安装:
./autogen.sh
./configure
make  && sudo make install
# 然后使用这个版本就可以了。
protoc --version
libprotoc 3.20.1-rc1

然后在执行 make all 就可以了:

go mod vendor
make all

有的时候 wire 不好用,可以直接修改成 wire 命令:


# makefile 的部分代码修改下命令:
.PHONY: generate
# generate
generate:
	go mod tidy
	go mod vendor
	cd cmd/kratos-crud/ && wire

项目启动特别简单:使用kratos 命令就行

$ kratos run
INFO msg=config loaded: config.yaml format: yaml
INFO msg=[gRPC] server listening on: [::]:9000
INFO msg=[HTTP] server listening on: [::]:8000

可以看到grpc 服务在9000 端口,http 在8000 端口

4,使用 grpcul进行服务查看调用


可以使用上面的工具命令进行安装:
#golang grpc 特别好的工具,可以直接调用grpc 服务
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
然后就可以使用了。进行grpc 服务接口的查看,调用。


#查看改地址端口服务
$ grpcurl -plaintext 127.0.0.1:9000 list
grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
helloworld.v1.Greeter
kratos.api.Metadata

#查看函数
$ grpcurl -plaintext 127.0.0.1:9000 list helloworld.v1.Greeter
helloworld.v1.Greeter.SayHello

#查看函数描述
$ grpcurl -plaintext 127.0.0.1:9000 describe helloworld.v1.Greeter.SayHello
helloworld.v1.Greeter.SayHello is a method:
rpc SayHello ( .helloworld.v1.HelloRequest ) returns ( .helloworld.v1.HelloReply ) 
  option (.google.api.http) =  get:"/helloworld/name"  ;


#查看入参
$ grpcurl -plaintext 127.0.0.1:9000 describe .helloworld.v1.HelloRequest
helloworld.v1.HelloRequest is a message:
message HelloRequest 
  string name = 1;


#调用函数,并返回
$ grpcurl -d '"name": "zhangsan"' -plaintext 127.0.0.1:9000 helloworld.v1.Greeter.SayHello

  "message": "Hello zhangsan"

5,创建数据CRUD使用gorm


https://gorm.io/zh_CN/docs/index.html

创建数据库,使用 gorm 操作数据库CRUD

使用root账号创建数据库和用户使用 demo demo 账号登录。

CREATE DATABASE IF NOT EXISTS demo DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
grant all privileges on demo.* to demo@'%' identified by 'demo';

# 创建 userInfo 的用户表:
CREATE TABLE `user_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(225) NOT NULL,
  `password` varchar(225) DEFAULT NULL,
  `age` tinyint(4) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


发现在插入数据的时候偶尔比较慢,使用连接池解决:

2022/04/24 21:23:12 /media/test/NewDisk1/go/src/kratos-crud/internal/data/user_info_repo.go:24 SLOW SQL >= 200ms
[489.453ms] [rows:1] INSERT INTO `user_info` (`user_name`,`password`,`age`,`phone`,`address`) VALUES ('','',0,'','')

设置连接池,解决链接问题,但在更新的时候偶尔还是会有,已经比之前好多了。

	sqlDB.SetMaxIdleConns(int(c.Database.MaxIdleConns))
	// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
	sqlDB.SetMaxOpenConns(int(c.Database.MaxOpenConns))
	// SetMaxOpenConns sets the maximum number of open connections to the database.
	sqlDB.SetConnMaxLifetime(time.Second * time.Duration(c.Database.ConnMaxLifetime))
	// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.

// 配置10 100 600 加大链接数量。

首先定义 biz 的接口:

package biz

import (
	"context"
	v1 "kratos-crud/api/helloworld/v1"
	"time"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
)

var (
	// ErrUserNotFound is user not found.
	ErrUserNotFound = errors.NotFound(v1.ErrorReason_USER_NOT_FOUND.String(), "user not found")
)

/**
CREATE TABLE `user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(225) NOT NULL,
`password` varchar(225) DEFAULT NULL,
`age` tinyint(4) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
*/
// UserInfo model.
type UserInfo struct 
	Id        int64
	UserName  string
	Password  string
	Age       uint32
	Phone     string
	Address   string
	CreatedAt time.Time `gorm:"not null"`
	UpdatedAt time.Time `gorm:"not null"`


func (UserInfo) TableName() string 
	return "user_info"


// UserInfoRepo is a Greater repo.
type UserInfoRepoIf interface 
	Save(context.Context, *UserInfo) (*UserInfo, error) // save or update
	Delete(context.Context, int64) error
	FindByID(context.Context, int64) (*UserInfo, error)
	FindAll(context.Context) ([]*UserInfo, error)


// UserInfoUsecase is a UserInfo usecase.
type UserInfoUsecase struct 
	repo UserInfoRepoIf
	log  *log.Helper


// NewUserInfoUsecase new a UserInfo usecase.
func NewUserInfoUsecase(repo UserInfoRepoIf, logger log.Logger) *UserInfoUsecase 
	return &UserInfoUsecaserepo: repo, log: log.NewHelper(logger)


// SaveUserInfo
func (uc *UserInfoUsecase) SaveUserInfo(ctx context.Context, user *UserInfo) (*UserInfo, error) 
	uc.log.WithContext(ctx).Infof("CreateUserInfo: %v", user.UserName)
	return uc.repo.Save(ctx, user)


// DeleteUserInfo
func (uc *UserInfoUsecase) DeleteUserInfo(ctx context.Context, id int64) error 
	uc.log.WithContext(ctx).Infof("DeleteUserInfo: %v", id)
	return uc.repo.Delete(ctx, id)


// FindUserInfoByID
func (uc *UserInfoUsecase) FindUserInfoByID(ctx context.Context, id int64) (*UserInfo, error) 
	uc.log.WithContext(ctx).Infof("FindUserInfoByID: %v", id)
	return uc.repo.FindByID(ctx, id)


// FindAllUserInfo
func (uc *UserInfoUsecase) FindAllUserInfo(ctx context.Context) ([]*UserInfo, error) 
	uc.log.WithContext(ctx).Infof("FindAllUserInfo ")
	return uc.repo.FindAll(ctx)


然后对 user 表进行CRUD

package data

import (
	"context"
	"kratos-crud/internal/biz"

	"github.com/go-kratos/kratos/v2/log"
)

type userInfoRepo struct 
	data *Data
	log  *log.Helper


// NewUserInfoRepo .
func NewUserInfoRepo(data *Data, logger log.Logger) biz.UserInfoRepoIf 
	return &userInfoRepo
		data: data,
		log:  log.NewHelper(logger),
	


func (repo *userInfoRepo) Save(ctx context.Context, userInfo *biz.UserInfo) (*biz.UserInfo, error) 
	repo.log.Debug(" Save result  :", userInfo) // 返回 error

	if userInfo.Id > 0  //更新	userInfo := biz.UserInfo
		var oldUserInfo biz.UserInfo
		result1 := repo.data.db.First(&oldUserInfo, "id = ? ", userInfo.Id) // 通过id进行数据查询
		repo.log.Debug("result.Error :", result1.Error)                     // 返回 error
		repo.log.Debug("save userInfo :", userInfo)
		if result1.Error != nil  // 有错误返回
			return userInfo, result1.Error
		 else 
			oldUserInfo.UserName = userInfo.UserName
			oldUserInfo.Password = userInfo.Password
			oldUserInfo.Age = userInfo.Age
			oldUserInfo.Phone = userInfo.Phone
			oldUserInfo.Address = userInfo.Address

			result := repo.data.db.Save(&oldUserInfo)      // 通过数据的指针来创建
			repo.log.Debug("result.Error :", result.Error) // 返回 error
			repo.log.Debug("save userInfo :", userInfo)
			return userInfo, result1.Error
		

	 else  // 创建
		result := repo.data.db.Create(userInfo)        // 通过数据的指针来创建
		repo.log.Debug("result.Error :", result.Error) // 返回 error
		repo.log.Debug("save userInfo :", userInfo)
		return userInfo, result.Error
	


func (repo *userInfoRepo) Update(ctx context.Context, userInfo *biz.UserInfo) error 
	repo.log.Debug("Update   :", userInfo) // 返回

	return nil


func (repo *userInfoRepo) Delete(ctx context.Context, id int64) error 
	repo.log.Debug("Delete  By Id :", id)                // 返回
	result := repo.data.db.Delete(&biz.UserInfoId: id) // 通过id删除数据
	repo.log.Debug("result.Error :", result.Error)       // 返回 error
	return result.Error


func (repo *userInfoRepo) FindByID(ctx context.Context, id int64) (*biz.UserInfo, error) 
	repo.log.Debug("FindByID   :", id) // 返回
	userInfo := biz.UserInfo

	result := repo.data.db.First(&userInfo, "id = ? ", id) // 通过id进行数据查询
	repo.log.Debug("result.Error :", result.Error)         // 返回 error
	return &userInfo, result.Error


func (repo *userInfoRepo) FindAll(ctx context.Context) ([]*biz.UserInfo, error) 
	repo.log.Debug("FindAll   :") //
	var userInfoList []*biz.UserInfo

	result := repo.data.db.Find(&userInfoList)     // 通过数据查询
	repo.log.Debug("result.Error :", result.Error) // 返回 error
	return userInfoList, result.Error


可以使用GORM 对数据进行CRUD,其中save 方法比较特殊。判断id,然后执行create 或者save 执行更新操作。

6,做CRUD接口


首先定义protoc 接口:

syntax = "proto3";

package demo.v1;

import "google/api/annotations.proto";

option go_package = "kratos-crud/api/demo/v1;v1";
option java_multiple_files = true;
option java_package = "api.demo.v1";
option java_outer_classname = "UserInfoV1";

// The UserInfo service definition.
service UserInfoService 

  rpc Save(UserInfo) returns (CommReply) 
    option (google.api.http) = 
      post: "/userInfo/save",
      body: "*"

    ;
  

  rpc Delete(IdRequest) returns (CommReply) 
    option (google.api.http) = 
      post: "/userInfo/delete",
      body: "*"
    ;
  

  rpc Get(IdRequest) returns (UserInfoReply) 
    option (google.api.http) = 
      get: "/userInfo/get/id"
    ;
  

    rpc List(ListRequest) returns (ListUserInfoReply) 
    option (google.api.http) = 
      post: "/userInfo/list",
      body: "*"
    ;
  


// https://developers.google.cn/protocol-buffers/docs/proto3
// 定义一个公用类型
message UserInfo 
  int64 id = 1;
  string userName = 2;
  string password = 3;
  uint32 age = 4;
  string phone = 5;
  string address = 6;


message IdRequest 
  int64 id = 1;


message ListRequest 
  string name = 1;


// return replay 
message CommReply 
  int64 code = 1;
  string message = 2;


message UserInfoReply 
  int64 code = 1;
  string message = 2;
  UserInfo userInfo = 3;


message ListUserInfoReply 
  int64 code = 1;
  string message = 2;
  repeated UserInfo userInfoList = 3;

其中保存,删除,查询使用POST方法,查询单个使用GET方法。当然删除也可以使用delete方法。

然后是service实现如下:

package service

import (
	"context"
	v1 "kratos-crud/api/helloworld/v1"
	"kratos-crud/internal/biz"
	"strconv"

	"github.com/go-kratos/kratos/v2/log"
)

// UserInfoService is a UserInfo service.
type UserInfoService struct 
	v1.UnimplementedUserInfoServiceServer
	uc  *biz.UserInfoUsecase
	log *log.Helper


// NewUserInfoService new a UserInfo service.
func NewUserInfoService(uc *biz.UserInfoUsecase, logger log.Logger) *UserInfoService 

	log := log.NewHelper(logger)

	return &UserInfoServiceuc: uc, log: log


// Save
func (s *UserInfoService) Save(ctx context.Context, in *v1.UserInfo) (*v1.CommReply, error) 
	s.log.Info(in.GetUserName())
	userInfo := biz.UserInfo
	userInfo.Id = in.GetId()
	userInfo.UserName = in.GetUserName()
	userInfo.Password = in.GetPassword()
	userInfo.Age = in.GetAge()
	userInfo.Phone = in.GetPhone()
	userInfo.Address = in.GetAddress()

	g, err := s.uc.SaveUserInfo(ctx, &userInfo)
	if err != nil 
		return nil, err
	
	return &v1.CommReplyMessage: "Hello " + g.UserName, nil


// Delete
func (s *UserInfoService) Delete(ctx context.Context, in *v1.IdRequest) (*v1.CommReply, error) 
	err := s.uc.DeleteUserInfo(ctx, in.GetId())
	if err != nil 
		return nil, err
	
	return &v1.CommReplyMessage: "Delete " + strconv.FormatInt(in.GetId(), 10), nil


// Get
func (s *UserInfoService) Get(ctx context.Context, in *v1.IdRequest) (*v1.UserInfoReply, error) 
	userInfo, err := s.uc.FindUserInfoByID(ctx, in.GetId())
	if err != nil 
		return nil, err
	
	// 对象转换
	userInfoReply := &v1.UserInfo
		Id:       userInfo.Id,
		UserName: userInfo.UserName,
		Password: userInfo.Password,
		Age:      userInfo.Age,
		Phone:    userInfo.Phone,
		Address:  userInfo.Address,
	
	return &v1.UserInfoReplyMessage: "Get " + userInfo.UserName, UserInfo: userInfoReply, nil


// List
func (s *UserInfoService) List(ctx context.Context, in *v1.ListRequest) (*v1.ListUserInfoReply, error) 
	userInfoList, err := s.uc.FindAllUserInfo(ctx)
	if err != nil 
		return nil, err
	
	// log.Info(userList)
	var userInfoReplyList []*v1.UserInfo
	// 循环转换对象。
	for _, userInfo := range userInfoList 
		userInfoReplyList = append(userInfoReplyList,
			&v1.UserInfo
				Id:       userInfo.Id,
				UserName: userInfo.UserName,
				Password: userInfo.Password,
				Age:      userInfo.Age,
				Phone:    userInfo.Phone,
				Address:  userInfo.Address,
			)
	
	return &v1.ListUserInfoReplyMessage: "List UserInfo", UserInfoList: userInfoReplyList, nil


这里使用了一个特殊方法进行测试,就是vscode的 rest api 插件,保存一个 .rest 文件,然后直接执行就可以了:


### 增加数据
POST http://localhost:8000/userInfo/save HTTP/1.1
content-type: application/json


    "userName":"Hendry",
    "password":"pwd123456",
    "age":25,
    "phone":"13811223344",
    "address":"北京王府井1号"


### 删除数据
POST http://localhost:8000/userInfo/delete HTTP/1.1
content-type: application/json


    "id":1


### 更新
POST http://localhost:8000/userInfo/save HTTP/1.1
content-type: application/json


    "id":2,
    "userName":"HendryNew",
    "password":"pwd123456New",
    "age":35,
    "phone":"13811223344New",
    "address":"北京王府井1号New"


### 按Id查询
GET http://localhost:8000/userInfo/get/2 HTTP/1.1
content-type: application/json

### 查询全部
POST http://localhost:8000/userInfo/list HTTP/1.1
content-type: application/json


    "id":200


使用情况:

7,总结


经过几天研究,终于完成了kratos的CRUD操作,解决若干问题。并上传了代码到github上。
发现网上的代码都是很简单的CRUD,并不全面。
完善了CURD的demo。

本文的原文连接是:
https://blog.csdn.net/freewebsys/article/details/124262158

博主地址是:https://blog.csdn.net/freewebsys

以上是关于GORM 基础 -- CRUD 接口的主要内容,如果未能解决你的问题,请参考以下文章

Gorm框架学习---CRUD接口之查询

Go开源世界主流成熟ORM框架gorm实践分享

kratos学习从零使用kratos创建一个CRUD的demo,是gorm做数据库操作,经过几天研究,调试代码开源放到github上,还是非常方便的。服务也实现了CRUD的http接口。

kratos学习从零使用kratos创建一个CRUD的demo,是gorm做数据库操作,经过几天研究,调试代码开源放到github上,还是非常方便的。服务也实现了CRUD的http接口。

GORM:通用方式的crud操作符

GORM CRUD 5 分钟快速上手