MJGA,让 Java 再次伟大,开箱即用 Spring Boot 怕不怕 Gin?

来源:juejin.cn/post/7245942451105562685

前言

隔壁组的云计算零零后女同事,后文简称 云女士 ,非说 Go 的 Gin 框架比 Springboot 更加的开箱即用,我心想在 Java 里面 Springboot 已经打遍天下无敌手,这份底蕴岂是 Gin 能比。

但是云女士突出一个执拗,非我要 PK 一把, PK 内容就是她使用 Gin,而我使用 Springboot 快速搭建一个简单的 Crud 工程,最后让其他同事来评判哪个更开箱即用。我毫不犹豫就答应了,作为搭建 Springboot 学习工程的资深 Crud 选手,咱这份底气还是有的。

云女士选择使用 Gin + Gorm 来搭建,而我原本想选择 Springboot + MyBatis,后面转念一想,这 MyBatis 要写 XML 文件,指不定就因为这个被云女士嘲笑了,所以我把 MyBatis 替换为了 MyBatis-Plus,这就足够的简洁了吧。

正文

准备事项

既然是 Crud 工程,自然要准备好操作的表,我和云女士通过如下语句在各自的数据库中创建好了如下两张表。

CREATE TABLE people (
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 p_name VARCHAR(255) NOT NULL,
 p_age INT(11) NOT NULL
)
CREATE TABLE book (
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 b_name VARCHAR(255) NOT NULL,
 b_price FLOAT NOT NULL
)

Gin快速搭建Crud工程

云女士的工程结构如下所示。

云女士的 go.mod 文件内容如下所示。

module gobase
go 1.17
require (
 github.com/gin-gonic/gin v1.6.0
 github.com/jinzhu/gorm v1.9.16
 github.com/sirupsen/logrus v1.9.3
 github.com/spf13/cast v1.5.1
)

云女士定义了两个结构体作为模型( Model ),book.go 文件内容如下所示。

package model
const (
 BookTableName = "book"
)
type Book struct {
 ID int64 `gorm:"column:id"`
 BookName string `gorm:"column:b_name"`
 BookPrice float64 `gorm:"column:b_price"`
}
func (b *Book) TableName() string {
 return BookTableName
}

people.go 文件内容如下所示。

package model
const (
 PeopleTableName = "people"
)
type People struct {
 ID int64 `gorm:"column:id"`
 PeopleName string `gorm:"column:p_name"`
 PeopleAge int64 `gorm:"column:p_age"`
}
func (p *People) TableName() string {
 return PeopleTableName
}

云女士补充道,TableName()方法是为模型指定对应的表名。

云女士为 book 表和 people 表分别定义了 Dao 接口,dao.go 文件内容如下所示。

package dao
import "gobase/model"
type BookDao interface {
 AddBook(book *model.Book) error
 UpdateBook(book *model.Book) error
 DeleteBook(book *model.Book) error
 ListBookById(id uint) (*model.Book, error)
}
type PeopleDao interface {
 AddPeople(book *model.People) error
 UpdatePeople(book *model.People) error
 DeletePeople(book *model.People) error
 ListPeopleById(id uint) (*model.People, error)
}

BookDao 接口对应的实现在book_dao_impl.go文件中,实现如下。

package dao
import (
 "github.com/jinzhu/gorm"
 "gobase/model"
)
type BookDaoImpl struct {
 DB *gorm.DB
}
func (b *BookDaoImpl) AddBook(book *model.Book) error {
 if createResult := b.DB.Create(book); createResult.Error != nil {
 return createResult.Error
 }
 return nil
}
func (b *BookDaoImpl) UpdateBook(book *model.Book) error {
 if saveResult := b.DB.Save(book); saveResult.Error != nil {
 return saveResult.Error
 }
 return nil
}
func (b *BookDaoImpl) DeleteBook(book *model.Book) error {
 if deleteResult := b.DB.Delete(book); deleteResult.Error != nil {
 return deleteResult.Error
 }
 return nil
}
func (b *BookDaoImpl) ListBookById(id uint) (*model.Book, error) {
 var book model.Book
 if listResult := b.DB.Where("id = ?", id).First(&book); listResult.Error != nil {
 return nil, listResult.Error
 }
 return &book, nil
}

PeopleDao 接口对应的实现在people_dao_impl.go文件中,实现如下。

package dao
import (
 "github.com/jinzhu/gorm"
 "gobase/model"
)
type PeopleDaoImpl struct {
 DB *gorm.DB
}
func (b *PeopleDaoImpl) AddPeople(people *model.People) error {
 if createResult := b.DB.Create(people); createResult.Error != nil {
 return createResult.Error
 }
 return nil
}
func (b *PeopleDaoImpl) UpdatePeople(people *model.People) error {
 if saveResult := b.DB.Save(people); saveResult.Error != nil {
 return saveResult.Error
 }
 return nil
}
func (b *PeopleDaoImpl) DeletePeople(people *model.People) error {
 if deleteResult := b.DB.Delete(people); deleteResult.Error != nil {
 return deleteResult.Error
 }
 return nil
}
func (b *PeopleDaoImpl) ListPeopleById(id uint) (*model.People, error) {
 var people model.People
 if listResult := b.DB.Where("id = ?", id).First(&people); listResult.Error != nil {
 return nil, listResult.Error
 }
 return &people, nil
}

要操作数据库,肯定需要数据库连接,云女士将数据库连接的管理实现在了mysql_connection_pool.go文件中,内容如下所示。

package mysql
import (
 "fmt"
 "github.com/jinzhu/gorm"
 "gobase/dao"
 "log"
 "time"
)
const (
 UserName = "root"
 PassWord = "root"
 Host = "192.168.101.8"
 Port = 3306
 Database = "gotest"
 MaxLifetime = 60 * time.Second
 MaxIdletime = 30 * time.Second
 MaxOpenconns = 6
 MaxIdleconns = 2
 Dialect = "mysql"
)
type DataSouce struct {
 db *gorm.DB
}
func NewDataSource() *DataSouce {
 var db *gorm.DB
 dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Asia%%2FShanghai",
 UserName, PassWord, Host, Port, Database)
 db, err := gorm.Open(Dialect, dsn)
 if err != nil {
 log.Fatal(err.Error())
 }
 db.DB().SetConnMaxLifetime(MaxLifetime)
 db.DB().SetConnMaxIdleTime(MaxIdletime)
 db.DB().SetMaxOpenConns(MaxOpenconns)
 db.DB().SetMaxOpenConns(MaxIdleconns)
 return &DataSouce{
 db: db,
 }
}
// BookDao 操作book表
func (d *DataSouce) BookDao() dao.BookDao {
 return &dao.BookDaoImpl{
 DB: d.db,
 }
}
// PeopleDao 操作people表
func (d *DataSouce) PeopleDao() dao.PeopleDao {
 return &dao.PeopleDaoImpl{
 DB: d.db,
 }
}

云女士将路由写在了webservice.go文件中,内容如下。

package adapter
import (
 "github.com/gin-gonic/gin"
 "gobase/mysql"
)
func Init() error {
 dataSouce := mysql.NewDataSource()
 bookController := NewBookController(dataSouce)
 propleController := NewPropleController(dataSouce)
 engine := gin.Default()
 routerGroupBook := engine.Group("/book")
 routerGroupBook.POST("/add", bookController.AddBook)
 routerGroupBook.POST("/update", bookController.UpdateBook)
 routerGroupBook.POST("/delete", bookController.DeleteBook)
 routerGroupBook.POST("/list", bookController.ListBookById)
 routerGroupPeople := engine.Group("/people")
 routerGroupPeople.POST("/add", propleController.AddPeople)
 routerGroupPeople.POST("/update", propleController.UpdatePeople)
 routerGroupPeople.POST("/delete", propleController.DeletePeople)
 routerGroupPeople.POST("/list", propleController.ListPeopleById)
 return engine.Run()
}

其实除了绑定路由,云女士还在Init()函数中进行了简单的服务注入,也就是创建数据库连接池,然后将数据库连接池给到对应的 web 服务。

云女士将操作 book 表对应的 web 服务写在了book_controller.go文件中,其实现如下所示。

package adapter
import (
 "github.com/gin-gonic/gin"
 "github.com/sirupsen/logrus"
 "github.com/spf13/cast"
 "gobase/model"
 "gobase/mysql"
 "net/http"
)
type BookController struct {
 dataSource *mysql.DataSouce
}
func NewBookController(dataSource *mysql.DataSouce) BookController {
 return BookController{
 dataSource: dataSource,
 }
}
func (b *BookController) AddBook(ctx *gin.Context) {
 var book model.Book
 if err := ctx.ShouldBind(&book); err != nil {
 logrus.Error("读取Book信息失败")
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 bookDao := b.dataSource.BookDao()
 err := bookDao.AddBook(&book)
 if err != nil {
 logrus.Error("添加Book失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, gin.H{
 "message": "success",
 })
}
func (b *BookController) UpdateBook(ctx *gin.Context) {
 var book model.Book
 if err := ctx.ShouldBind(&book); err != nil {
 logrus.Error("读取Book信息失败")
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 bookDao := b.dataSource.BookDao()
 err := bookDao.UpdateBook(&book)
 if err != nil {
 logrus.Error("更新Book失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, gin.H{
 "message": "success",
 })
}
func (b *BookController) DeleteBook(ctx *gin.Context) {
 var book model.Book
 if err := ctx.ShouldBind(&book); err != nil {
 logrus.Error("读取Book信息失败")
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 bookDao := b.dataSource.BookDao()
 err := bookDao.DeleteBook(&book)
 if err != nil {
 logrus.Error("删除Book失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, gin.H{
 "message": "success",
 })
}
func (b *BookController) ListBookById(ctx *gin.Context) {
 id := cast.ToUint(ctx.Query("id"))
 bookDao := b.dataSource.BookDao()
 book, err := bookDao.ListBookById(id)
 if err != nil {
 logrus.Error("查询Book失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, book)
}

云女士将操作 people 表对应的 web 服务写在了people_controller.go文件中,其实现如下所示。

package adapter
import (
 "github.com/gin-gonic/gin"
 "github.com/sirupsen/logrus"
 "github.com/spf13/cast"
 "gobase/model"
 "gobase/mysql"
 "net/http"
)
type PeopleController struct {
 dataSource *mysql.DataSouce
}
func NewPropleController(dataSource *mysql.DataSouce) PeopleController {
 return PeopleController{
 dataSource: dataSource,
 }
}
func (p *PeopleController) AddPeople(ctx *gin.Context) {
 var people model.People
 if err := ctx.ShouldBind(&people); err != nil {
 logrus.Error("读取People信息失败")
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 peopleDao := p.dataSource.PeopleDao()
 err := peopleDao.AddPeople(&people)
 if err != nil {
 logrus.Error("添加People失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, gin.H{
 "message": "success",
 })
}
func (p *PeopleController) UpdatePeople(ctx *gin.Context) {
 var people model.People
 if err := ctx.ShouldBind(&people); err != nil {
 logrus.Error("读取People信息失败")
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 peopleDao := p.dataSource.PeopleDao()
 err := peopleDao.UpdatePeople(&people)
 if err != nil {
 logrus.Error("更新People失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, gin.H{
 "message": "success",
 })
}
func (p *PeopleController) DeletePeople(ctx *gin.Context) {
 var people model.People
 if err := ctx.ShouldBind(&people); err != nil {
 logrus.Error("读取People信息失败")
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 peopleDao := p.dataSource.PeopleDao()
 err := peopleDao.DeletePeople(&people)
 if err != nil {
 logrus.Error("删除People失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, gin.H{
 "message": "success",
 })
}
func (p *PeopleController) ListPeopleById(ctx *gin.Context) {
 id := cast.ToUint(ctx.Query("id"))
 peopleDao := p.dataSource.PeopleDao()
 people, err := peopleDao.ListPeopleById(id)
 if err != nil {
 logrus.Error("查询People失败", err)
 ctx.JSON(http.StatusInternalServerError, gin.H{
 "message": "failed",
 })
 return
 }
 ctx.JSON(http.StatusOK, people)
}

最后,云女士简单的展示了一下对 book 表和 prople 表的 Crud 操作。

book 表和 people 表的增删改成功时返回内容如下所示。

book 表和 people 表的查询成功时返回内容如下所示。

Spring boot 快速搭建Crud工程

Spring Boot 基础就不介绍了,推荐看这个实战项目:

https://github.com/javastacks/spring-boot-best-practice

云女士基于 Gin 和 Gorm 搭建的 Crud 工程,我看完后内心扑哧一笑:不过如此。

那现在该轮到我表演了。首先给出整个工程结构图如下所示。

POM 文件内容如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.4.1</version>
 </parent>
 <groupId>com.lee.javabase</groupId>
 <artifactId>javabase</artifactId>
 <version>1.0-SNAPSHOT</version>
 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-jdbc</artifactId>
 </dependency>
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-boot-starter</artifactId>
 <version>3.1.0</version>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.16</version>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 </dependency>
 </dependencies>
</project>

同样,定义 book 表和 people 表对应的实体类 Book 和 People,如下所示。

@Getter
@Setter
public class Book {
 @TableField("id")
 private int id;
 @TableField("b_name")
 private String bookName;
 @TableField("b_price")
 private float bookPrice;
}
@Getter
@Setter
public class People {
 @TableField("id")
 private int id;
 @TableField("p_name")
 private String peopleName;
 @TableField("p_age")
 private int peopleAge;
}

然后定义定义接口,如下所示。

@Mapper
public interface BookMapper extends BaseMapper<Book> {
}
@Mapper
public interface PeopleMapper extends BaseMapper<People> {
}

最后是对应的 Controller 实现, BookController 实现如下。

@Slf4j
@RestController
@RequestMapping("/book")
public class BookController {
 @Autowired
 private BookMapper bookMapper;
 @PostMapping("/add")
 public ResponseEntity<String> addBook(@RequestBody Book book) {
 try {
 bookMapper.insert(book);
 return new ResponseEntity<>("添加图书成功", HttpStatus.OK);
 } catch (Exception e) {
 log.error("添加图书失败", e);
 return new ResponseEntity<>("添加图书失败", HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
 @PostMapping("/update")
 public ResponseEntity<String> updateBook(@RequestBody Book book) {
 try {
 bookMapper.updateById(book);
 return new ResponseEntity<>("更新图书成功", HttpStatus.OK);
 } catch (Exception e) {
 log.error("更新图书失败", e);
 return new ResponseEntity<>("更新图书失败", HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
 @PostMapping("/delete")
 public ResponseEntity<String> deleteBook(@RequestParam("id") int id) {
 try {
 bookMapper.deleteById(id);
 return new ResponseEntity<>("删除图书成功", HttpStatus.OK);
 } catch (Exception e) {
 log.error("删除图书失败", e);
 return new ResponseEntity<>("删除图书失败", HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
 @PostMapping("/list")
 public ResponseEntity<Book> listBook(@RequestParam("id") int id) {
 try {
 Book book = bookMapper.selectById(id);
 return new ResponseEntity<>(book, HttpStatus.OK);
 } catch (Exception e) {
 log.error("查询图书失败", e);
 return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
}

PeopleController 实现如下所示。

@Slf4j
@RestController
@RequestMapping("/people")
public class PeopleController {
 @Autowired
 private PeopleMapper peopleMapper;
 @PostMapping("/add")
 public ResponseEntity<String> addPeople(@RequestBody People people) {
 try {
 peopleMapper.insert(people);
 return new ResponseEntity<>("添加人物成功", HttpStatus.OK);
 } catch (Exception e) {
 log.error("添加人物失败", e);
 return new ResponseEntity<>("添加人物失败", HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
 @PostMapping("/update")
 public ResponseEntity<String> updatePeople(@RequestBody People people) {
 try {
 peopleMapper.updateById(people);
 return new ResponseEntity<>("更新人物成功", HttpStatus.OK);
 } catch (Exception e) {
 log.error("更新人物失败", e);
 return new ResponseEntity<>("更新人物失败", HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
 @PostMapping("/delete")
 public ResponseEntity<String> deletePeople(@RequestParam("id") int id) {
 try {
 peopleMapper.deleteById(id);
 return new ResponseEntity<>("删除人物成功", HttpStatus.OK);
 } catch (Exception e) {
 log.error("删除人物失败", e);
 return new ResponseEntity<>("删除人物失败", HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
 @PostMapping("/list")
 public ResponseEntity<People> listPeople(@RequestParam("id") int id) {
 try {
 People people = peopleMapper.selectById(id);
 return new ResponseEntity<>(people, HttpStatus.OK);
 } catch (Exception e) {
 log.error("查询人物失败", e);
 return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
 }
 }
}

启动应用程序, book 表的 Crud 操作结果如下所示。

prople 表的 Crud 操作结果如下所示。

总结

我宣布,Springboot 就是快速搭建 Crud 工程的

其实,在基于 Gin 和 Gorm 搭建 Crud 工程时,云女士还是写得复杂了一点,但是我有幸看过她们云平台的项目的代码,云女士写得也没毛病,虽然是个简化版,但也是严格遵从她们项目的代码结构来实现的。

说回 Springboot,毫无疑问,无论是天然自带 Tomcat 或 Jetty ,还是和三方框架整合的各种 Starter 包,Springboot 都将开箱即用做到了极致,但是转念又一想,其实 Springboot 和 Gin 严格来说做比较没啥意义,就像 Java 和 Go 的比较一样,我觉得也没啥意义,各自的优势区间不一样,并且各自也都在相关的领域叱咤风云。

各位看官,你们觉得呢。

更多文章推荐:

1.Spring Boot 3.x 教程,太全了!

2.2,000+ 道 Java面试题及答案整理(2024最新版)

3.免费获取 IDEA 激活码的 7 种方式(2024最新版)

觉得不错,别忘了随手点赞+转发哦!

作者:Java技术栈原文地址:https://www.cnblogs.com/javastack/p/18574261

%s 个评论

要回复文章请先登录注册