Go微服务框架go-kratos实战03:使用 gorm 实现增删改查操作
一、简介
在上一篇文章 go-kratos实战02 中,详细介绍了用 kratos 编写项目代码的步骤。这篇就在上篇基础上,再结合 Go 数据库操作库 gorm 一步一步来实现一个简单的增删改查操作。
首先假定你已经会使用 gorm 的基本操作。
安装 gorm:
$ go get -u gorm.io/gorm
go: downloading gorm.io/gorm v1.23.5
... ...
GORM 文档:https://gorm.io/zh_CN/docs/
Go,gorm 和 go-kratos 版本:
go v1.17.10 windows/amd64
go-kratos v2.2.1
gorm v1.23.5
二、新建 student 项目
在前面文章中,我们知道可以使用 kratos new
命令,用 kratos-layout 这个模板快速新建出一个项目。
$ kratos new student
Creating service student, layout repo is https://github.com/go-kratos/kratos-layout.git, please wait a moment.
From https://github.com/go-kratos/kratos-layout
cc5192f..6715fbc main -> origin/main
* [new tag] v2.3.0 -> v2.3.0
* [new tag] v2.2.2 -> v2.2.2
... ...
发现
kratos new
命令每次创建新项目都会使用最新版 go-kratos。看上面的信息 kratos 版本到了 v2.3.0。前面项目用的还是 v2.2.1,为了和前面项目版本保持一致,把 go.mod 里的 kratos 改成 v2.2.1 ,然后
运行
go mod tidy
命令,重新下载依赖包。
因为使用 kratos-layout 模板新建的 student 项目,为了使项目看起来干净点,需要修改和删除里面的文件。
比如 proto 文件:
三、整理 student 项目
这时候项目里的很多文件,变量名等都是以 greeter 为名字的,因为这个是模板自带的。先简单整理下。
删掉 helloworld/v1 文件夹,新建 student/v1 文件夹
在 internal 目录下的 greeter.go 文件都可以修改为 student.go ,里面的内容后面在逐一修改,或者直接删掉文件后在添加 student.go 文件。我这里直接修改好了,它是一个参考模板。
四、编写项目代码
4.1 用命令新建 student.proto
kratos proto add api/student/v1/student.proto
4.2 通过 student.proto 生成代码
第一步,给 student.proto 添加如下代码:
// 先引入 google/api/annotations.proto
import "google/api/annotations.proto";
// 在 service Student{} 增加如下代码:
rpc GetStudent (GetStudentRequest) returns (GetStudentReply) {
option (google.api.http) = {
get: "/student/{id}",
};
}
message GetStudentRequest {
int32 id = 1;
}
message GetStudentReply {
string name = 1;
int32 status = 2;
int32 id = 3;
}
第二步,通过 kratos proto client
生成 pb 相关代码:
kratos proto client api/student/v1/student.proto
第三步,通过 student.proto 生成 Service(服务) 代码:
$ kratos proto server api/student/v1/student.proto -t internal/service
internal/service/student.go
修改 internal/service/service.go 里依赖注入部分:
var ProviderSet = wire.NewSet(NewStudentService)
4.3 实例化 HTTP 和 gRPC
在 internal/server 目录下,修改 http.go, grpc.go, server.go。
http.go:
// 上面 import 中引入的 greeter
import (
v1 "student/api/student/v1"
... ...
)
// NewHTTPServer new a HTTP server.
func NewHTTPServer(c *conf.Server, student *service.StudentService, logger log.Logger) *http.Server {
... ...
srv := http.NewServer(opts...)
v1.RegisterStudentHTTPServer(srv, student)
return srv
}
grpc.go:
import (
v1 "student/api/student/v1"
... ...
)
// NewGRPCServer new a gRPC server.
func NewGRPCServer(c *conf.Server, student *service.StudentService, logger log.Logger) *grpc.Server {
... ...
srv := grpc.NewServer(opts...)
v1.RegisterStudentServer(srv, student)
return srv
}
4.4 编写获取学生信息代码
下面编写用学生 id 来获取学生信息。
第一步:在 internal/biz/student.go 里编写代码
前面第一篇文章讲过 biz 目录作用,起到业务组装作用,定义了 biz 的 repo 接口。
如果没有这个文件就新建一个,student.go 中代码如下:
package biz
import (
"context"
"time"
"github.com/go-kratos/kratos/v2/log"
)
// Student is a Student model.
type Student struct {
ID int32
Name string
Info string
Status int32
UpdatedAt time.Time
CreatedAt time.Time
}
// 定义 Student 的操作接口
type StudentRepo interface {
GetStudent(context.Context, int32) (*Student, error) // 根据 id 获取学生信息
}
type StudentUsecase struct {
repo StudentRepo
log *log.Helper
}
// 初始化 StudentUsecase
func NewStudentUsecase(repo StudentRepo, logger log.Logger) *StudentUsecase {
return &StudentUsecase{repo: repo, log: log.NewHelper(logger)}
}
// 通过 id 获取 student 信息
func (uc *StudentUsecase) Get(ctx context.Context, id int32) (*Student, error) {
uc.log.WithContext(ctx).Infof("biz.Get: %d", id)
return uc.repo.GetStudent(ctx, id)
}
用 wire 注入代码,修改 internal/biz/biz.go :
var ProviderSet = wire.NewSet(NewStudentUsecase)
第二步:在 internal/data/student.go 里编写代码
前面第一篇文章已经讲过 data 目录作用,对数据持久化的操作,业务数据访问,包含 DB、redis 等封装,实现了 biz 的 repo interface。biz 里定义了 repo interface。
如果没有这个文件就新建一个,student.go 代码如下:
package data
import (
"context"
"student/internal/biz"
"github.com/go-kratos/kratos/v2/log"
)
type studentRepo struct {
data *Data
log *log.Helper
}
// 初始化 studentRepo
func NewStudentRepo(data *Data, logger log.Logger) biz.StudentRepo {
return &studentRepo{
data: data,
log: log.NewHelper(logger),
}
}
func (r *studentRepo) GetStudent(ctx context.Context, id int32) (*biz.Student, error) {
var stu biz.Student
r.data.gormDB.Where("id = ?", id).First(&stu) // 这里使用了 gorm
r.log.WithContext(ctx).Info("gormDB: GetStudent, id: ", id)
return &biz.Student{
ID: stu.ID,
Name: stu.Name,
Status: stu.Status,
Info: stu.Info,
UpdatedAt: stu.UpdatedAt,
CreatedAt: stu.CreatedAt,
}, nil
}
上面代码里有个 r.data.gormDB, gormDB 这个东东从哪里来?就是下面要编写的 data/data.go,连接数据库。
第三步:编写 internal/data/data.go:
数据库的封装操作代码。
// 第 1 步引入 *gorm.DB
type Data struct {
// TODO wrapped database client
gormDB *gorm.DB
}
// 第 2 步初始化 gorm
func NewGormDB(c *conf.Data) (*gorm.DB, error) {
dsn := c.Database.Source
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
}
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxIdleConns(50)
sqlDB.SetMaxOpenConns(150)
sqlDB.SetConnMaxLifetime(time.Second * 25)
return db, err
}
// 第 3 步,初始化 Data
func NewData(logger log.Logger, db *gorm.DB) (*Data, func(), error) {
cleanup := func() {
log.NewHelper(logger).Info("closing the data resources")
}
return &Data{gormDB: db}, cleanup, nil
}
// 第 4 步,用 wire 注入代码,修改 原来的 NewSet
var ProviderSet = wire.NewSet(NewData, NewGormDB, NewStudentRepo)
生成的模板代码是在 NewData
里初始化 db,这里把 gormDB 独立封装,然后用 wire 注入。
第四步,编写 internal/service/student.go 代码
上面通过 student.proto 文件生成了一份 service/student.go 代码模板,具体代码还没有编写,下面就来编写 service 代码。
// 引入 biz.StudentUsecase
type StudentService struct {
pb.UnimplementedStudentServer
student *biz.StudentUsecase
log *log.Helper
}
// 初始化
func NewStudentService(stu *biz.StudentUsecase, logger log.Logger) *StudentService {
return &StudentService{
student: stu,
log: log.NewHelper(logger),
}
}
// 获取学生信息
func (s *StudentService) GetStudent(ctx context.Context, req *pb.GetStudentRequest) (*pb.GetStudentReply, error) {
stu, err := s.student.Get(ctx, req.Id)
if err != nil {
return nil, err
}
return &pb.GetStudentReply{
Id: stu.ID,
Status: stu.Status,
Name: stu.Name,
}, nil
}
4.5 修改配置文件
配置文件 student/configs/config.yaml。
修改 mysql 配置项 source,这里 source 要修改成 gorm 的 dsn 数据格式,driver 不变,
// https://gorm.io/zh_CN/docs/connecting_to_the_database.html#MySQL
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
data:
database:
driver: mysql
source: root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local
我使用的数据库名就是 test,所以就不用修改数据库名。
把 server.http.addr 端口修改为 8000 -> 8080。
如果修改了 conf.proto,请使用
make config
命令重新生成 conf.pb.go 文件。我这里没有修改,就不需要重新生成。
4.6 重新生成 wire_gen.go 文件
进入到 cmd/student 目录,然后用 wire
命令重新生成 wire_gen.go,
$ cd ./cmd/student
$ wire
wire: student/cmd/student: wrote D:\mygo\go-kratos-demos\student\cmd\student\wire_gen.go
五、数据库
在 mysql 里创建一个名为 test 的数据库,然后运行下面的 sql,创建数据表 students :
DROP TABLE IF EXISTS `students`;
CREATE TABLE `students` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
`info` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`status` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of students
-- ----------------------------
INSERT INTO `students` VALUES ('1', 'tom', 'a top student', '2022-06-02 15:28:55', '2022-06-02 15:27:01', '1');
INSERT INTO `students` VALUES ('3', 'jimmy', 'a good student', null, null, '0');
INSERT INTO `students` VALUES ('4', 'you', 'fea tea', null, null, '1');
INSERT INTO `students` VALUES ('6', 'ju', '', null, null, '1');
六、运行项目
在 cmd/student 目录, 运行命令 kratos run
$ kratos run
INFO msg=config loaded: config.yaml format: yaml
INFO msg=[gRPC] server listening on: [::]:9000
INFO msg=[HTTP] server listening on: 127.0.0.1:8080
使用 curlie - https://github.com/rs/curlie 测试:
$ curlie http://127.0.0.1:8080/student/1
HTTP/1.1 200 OK
{
"name": "tom",
"status": 1,
"id": 1
}
Content-Type: application/json
Date: Thu, 02 Jun 2022 08:04:49 GMT
Content-Length: 32
测试返回成功。
好了,获取学生信息的代码就编写完了。
其余部分,比如增加、修改等,自己可以试着写一写,熟能生巧嘛。
七、项目代码地址
go-kratos student 项目源代码地址:
上面所有代码以 github 上的代码为准。
八、参考
- https://go-kratos.dev/docs/getting-started/start kratos 新建模板项目
- https://go-kratos.dev/docs/getting-started/usage kratos cli 工具
- https://gorm.io/zh_CN/docs/connecting_to_the_database.html#MySQL gorm mysql数据库连接
- https://gorm.io/zh_CN/docs/query.html gorm 查询
- https://www.cnblogs.com/jiujuan/p/12676195.html gorm 基本操作
- https://github.com/rs/curlie curlie http 请求
Go微服务框架go-kratos实战03:使用 gorm 实现增删改查操作的更多相关文章
- AD 域服务简介(三)- Java 对 AD 域用户的增删改查操作
博客地址:http://www.moonxy.com 关于AD 域服务器搭建及其使用,请参阅:AD 域服务简介(一) - 基于 LDAP 的 AD 域服务器搭建及其使用 Java 获取 AD 域用户, ...
- 【OF框架】新建库表及对应实体,并实现简单的增删改查操作,封装操作标准WebApi
准备 搭建好项目框架及数据库,了解框架规范. 1.数据库表和实体一一对应,表名实体名名字相同,用小写,下划线连接.字段名用驼峰命名法,首字母大写. 2.实体放在Entities目录下,继承Entity ...
- 初识Hibernate框架,进行简单的增删改查操作
Hibernate的优势 优秀的Java 持久化层解决方案 (DAO) 主流的对象—关系映射工具产品 简化了JDBC 繁琐的编码 将数据库的连接信息都存放在配置文件 自己的ORM框架 一定要手动实现 ...
- 初识hibernate框架之一:进行简单的增删改查操作
Hibernate的优势 l 优秀的Java 持久化层解决方案 (DAO) l 主流的对象—关系映射工具产品 l 简化了JDBC 繁琐的编码 l 将数据库的连接信息都存放在配置文件 l 自己的ORM ...
- Node+Express+node-mysql 实战于演习 全套mysql(增删改查)
最近这段时间研究Node感觉不错,自己做了一个增删改查,虽然有些简陋,但是思想是想通的,其实所有项目都是增删改查,有助于初学者快速掌握Node 首先 本实例展示的是基于Node+Express+nod ...
- MongoDB学习笔记—03 增删改查操作
MongoDB的CURD操作分别通过函数insert().update().find().remove()进行 MongoDB文档新增与删除 MongoDB中关于文档的新增与删除比较简单.主要通过in ...
- 控制台程序实现利用CRM组织服务和SqlConnection对数据库中数据的增删改查操作
一.首先新建一个控制台程序.命名为TestCol. 二.打开App.config在里面加入,数据库和CRM连接字符串 <connectionStrings> <add name=&q ...
- 编码实战Web端联系人的增删改查
首先画出分析图 实现效果如图 项目下的包如图: 实体包 package com.contactSystem.entiey; public class Contact { private String ...
- jquery-easyui实现页面布局和增删改查操作(SSH2框架支持)转载
http://blessht.iteye.com/blog/1069749/ 已注册:ooip 关联的csdn 前几天心血来潮用jquery-easyui+spring.struts2.hiberna ...
随机推荐
- Java中使用最频繁及最通用的Java工具类
在Java中,工具类定义了一组公共方法,Java中使用最频繁及最通用的Java工具类. 一. org.apache.commons.io.IOUtils closeQuietly:关闭一个IO流.so ...
- EMS导入导出邮箱
Exchange支持EMS命令导出用户邮箱内容作为备份的功能.当重要用户的邮件误删除后,可以通过导出的邮箱恢复数据. 1.授权管理用户 Exchange默认安装完成后,内置"Mailbox ...
- 在vue中实现点击哪个哪个区域变化背景色和字体颜色,其他默认(uni-app框架中也可以使用)
template: 1 <view class="wrap"> 2 <view class="total" :class="{ se ...
- java-GUI编程之AWT组件
AWT中常用组件 基本组件 组件名 功能 Button Button Canvas 用于绘图的画布 Checkbox 复选框组件(也可当做单选框组件使用) CheckboxGroup 用于将多个Che ...
- Hash冲突以及解决
哈希函数:它把一个大范围的数字哈希(转化)成一个小范围的数字,这个小范围的数对应着数组的下标.使用哈希函数向数组插入数据后,这个数组就是哈希表. 冲突 当冲突产生时,一个方法是通过系统的方法找到数组的 ...
- Bootstrap Blazor 组件库 Row 布局组件(栅格系统)
原文链接:https://www.cnblogs.com/ysmc/p/16133351.html 在 Bootstrap 中,栅格相信大家都很熟悉,简直就是布局神器啊,Bootstrap Blazo ...
- Java语言学习day17--7月23日
1.面向对象思想2.类与对象的关系3.局部变量和成员变量的关系4.封装思想5.private,this关键字6.随机点名器 ###01面向对象和面向过程的思想 * A: 面向过程与面向对象都是我们编程 ...
- LevelDB 学习笔记2:合并
LevelDB 学习笔记2:合并 部分图片来自 RocksDB 文档 Minor Compaction 将内存数据库刷到硬盘的过程称为 minor compaction 产出的 L0 层的 sstab ...
- XCTF练习题---MISC---Ditf
XCTF练习题---MISC---Ditf flag:flag{Oz_4nd_Hir0_lov3_For3ver} 解题步骤: 1.观察题目,下载附件 2.这道题是安恒办的一场比赛题目,下载附件以后是 ...
- JS 一些概念
JS 内存机制 基本类型: String | Boolean | Undefined | Number | Nll | Symbol 数据保存在"栈"--先进后出. 数据类型: A ...