來源: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最新版)
覺得不錯,別忘了隨手點贊+轉發哦!