go go go 出發咯 - go web開發入門系列(四) 數據庫ORM框架集成與解讀

go go go 出發咯 - go web開發入門系列(四) 數據庫ORM框架集成與解讀


往期回顧

  • go go go 出發咯 - go web開發入門系列(一) helloworld
  • go go go 出發咯 - go web開發入門系列(二) Gin 框架實戰指南
  • go go go 出發咯 - go web開發入門系列(三) 項目基礎框架搭建與解讀

前言

在上一篇文章中,我們從零開始,搭建了一個生產級的 Go Web 應用框架。我們深入探討了分層架構、依賴注入和面向接口編程,并最終構建了一個結構清晰、職責分明的“手動擋”應用——我們擁有對每一行 SQL 的完全控制權。

這種控制力在需要極致性能優化的場景下非常寶貴。但對于大多數標準的增刪改查(CRUD)操作來說,手動編寫和映射每一條 SQL 顯得有些繁瑣。這正是 ORM(對象關系映射)框架大顯身手的舞臺。

本文將作為上一篇的進階,向您展示如何將 Go 生態中最流行的 ORM 框架 GORM,無縫地集成到我們現有的分層架構中。我們的目標是:在不改動任何 Service 和 Handler 層代碼的前提下,用 GORM 完全替換掉手寫 SQL 的 Repository 層,體驗開發效率的飛躍。

架構回顧:解耦是替換的基石

讓我們再次回顧一下我們的分層架構,正是這個清晰的結構,使得替換數據訪問層成為可能。

/awesomeProject
| ├── cmd/ # 入口文件 
│ 	└── server/ 
│ 		└── main.go # 主程序入口 
├── configs/ # 配置文件 
│ 	└── config.dev.yaml 
├── internal/ # 內部模塊 
│ ├── config/ # 配置加載 
│ ├── database/ # 數據庫連接 
│ ├── models/ # 數據模型 
│ ├── repository/ # 數據訪問層 
│ └── service/ # 業務邏輯層 
├── transport/ # 傳輸層 
...

GORM (全功能 ORM 框架)

GORM 介紹

GORM 是 Go 語言中最流行的全功能 ORM (Object-Relational Mapping) 框架。它的設計哲學最接近您熟悉的 HibernateMyBatis-Plus,旨在通過“約定優于配置”和鏈式調用,將開發者從手寫 SQL 中解放出來。

核心理念: 將數據庫操作完全對象化。你操作的是 Go 的結構體對象,GORM 負責在背后生成并執行對應的 SQL 語句。

特點:

鏈式 API: 提供非常流暢的鏈式調用方法 (db.Where(…).First(…))。

自動化: 自動處理創建、查詢、更新、刪除 (CRUD) 操作。

高級功能: 支持自動遷移(根據結構體創建/修改表)、鉤子(在創建/更新前后執行特定函數)、預加載(Eager Loading)、事務等。

優點:

  • 開發效率極高,尤其適合快速構建原型和標準的 CRUD 應用。

  • 代碼量顯著減少,可讀性強(對于熟悉 ORM 的人而言)。

缺點:“魔法”太多,可能會隱藏底層 SQL 的性能問題。

  • 對于復雜的查詢,其鏈式 API 可能變得復雜,或者不得不退回手寫 SQL。

  • 學習曲線相對較陡,需要理解其內部的約定和工作方式。

GORM 代碼集成示例

本節我們將繼續在次框架上,進行實現商品(product)相關的CRUD操作,并給與外部調用,對于商品(product)整個鏈路過程將采用ORM的方式,便于和之前實現的用戶(User)相對比學習。

第1步:安裝 GORM 及驅動

GORM 的工作需要兩個核心組件:GORM 核心庫和對應數據庫的驅動。

# 安裝 GORM 核心庫
go get -u gorm.io/gorm# 安裝 GORM 的 MySQL 驅動適配器
go get -u gorm.io/driver/mysql

還記得在上一節中我們連接 mysql 數據庫時引入的依賴 go get -u github.com/go-sql-driver/mysql 嗎?

Q:" go get -u github.com/go-sql-driver/mysql ; go get -u gorm.io/gorm ; go get -u gorm.io/driver/mysql " 這三個依賴不沖突嗎?

A:

gorm.io/gorm (ORM 框架本身)

  • 這是 GORM 的核心庫,提供了所有 .Create(), .First(), .Where() 等鏈式調用方法。
  • 它是一個高層抽象,負責將對象操作轉換為 SQL 思想。但它自己并不知道如何與具體的數據庫(如 MySQL 或 PostgreSQL)對話。

gorm.io/driver/mysql (GORM 的 MySQL 適配器)

  • 這個庫是連接 GORM 核心框架和底層數據庫驅動的“橋梁”或“適配器”。
  • 它告訴 GORM:“當你需要操作 MySQL 時,應該使用這種方式來配置和傳遞指令。”

github.com/go-sql-driver/mysql (底層的數據庫驅動)

  • 這是真正負責與 MySQL 服務器進行網絡通信、執行 SQL 語句的“工人”。
  • gorm.io/driver/mysql 這個“適配器”在內部會依賴并調用這個底層的驅動來完成實際工作。

gorm.io/driver/mysql 在底層依賴了 go-sql-driver/mysql,Go 的模塊工具會自動處理這個依賴關系。

第2步:創建領域模型 (Model)

我們在 internal/models 下創建產品(product)結構體,對比 SpringBoot 作為數據庫實體映射

GORM 可以通過嵌入 gorm.Model 來為我們的結構體自動添加 ID, CreatedAt, UpdatedAt, DeletedAt 等常用字段。

// internal/models/product.go
package modelsimport "gorm.io/gorm"type product struct {gorm.Model         // 嵌入gorm.Model,自動獲得ID和時間戳字段Name       string  `gorm:"size:255;not null"` // 使用 GORM 標簽定義列屬性Price      float64 `gorm:"type:decimal(10,2)"`Stock      int     `gorm:"default:0"`
}//上述結構體等價于//type product struct {
//  ID        uint           `gorm:"primaryKey"`
//  CreatedAt time.Time
//  UpdatedAt time.Time
//  DeletedAt gorm.DeletedAt `gorm:"index"`
//  Name       string  `gorm:"size:255;not null"` // 使用 GORM 標簽定義列屬性
//  Price      float64 `gorm:"type:decimal(10,2)"`
//  Stock      int     `gorm:"default:0"`
//}
//
第3步:創建 GORM 數據庫連接

internal/database/ 下創建 gorm.go 來初始化 GORM 的數據庫連接。

//internal/database/gorm.go
package databaseimport ("awesomeProject/internal/config""gorm.io/driver/mysql""gorm.io/gorm"
)// NewGormConnection 負責根據配置創建GORM數據庫連接池
func NewGormConnection(dbConfig config.DatabaseConfig) (*gorm.DB, error) {// dsn 來自于我們的配置文件dsn := dbConfig.DSN// 使用GORM的MySQL驅動來打開數據庫連接db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {return nil, err}// 獲取底層的 *sql.DB 對象來設置連接池參數sqlDB, err := db.DB()if err != nil {return nil, err}// 設置從配置中讀取的連接池參數sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns)sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns)// 可以選擇在這里 Ping 數據庫以驗證連接if err = sqlDB.Ping(); err != nil {return nil, err}return db, nil
}
第4步:實現關于"產品"的 GORM Repository

internal/repository/ 下創建 ProductRepository.go 使用GORM 來操作數據庫,生成對于product的CURD 方法

//internal/repository/ProductRepository.go
package repositoryimport ("awesomeProject/internal/models""context""gorm.io/gorm"
)// ProductRepository 定義了產品數據的所有操作,便于解耦
type ProductRepository interface {Create(ctx context.Context, product *models.Product) errorFindByID(ctx context.Context, id int64) (*models.Product, error)FindAll(ctx context.Context) ([]*models.Product, error)Update(ctx context.Context, product *models.Product) errorDelete(ctx context.Context, id int64) error
}// 構造方法
type gormMySqlProductRepository struct {db *gorm.DB // 應用的是grom 框架 這里持有的是 *gorm.DB 而不是 *sql.DB
}func (g gormMySqlProductRepository) Create(ctx context.Context, product *models.Product) error {result := g.db.WithContext(ctx).Create(product)return result.Error
}func (g gormMySqlProductRepository) FindByID(ctx context.Context, id int64) (*models.Product, error) {var product models.Productresult := g.db.WithContext(ctx).First(&product, id)if result.Error != nil {if result.Error == gorm.ErrRecordNotFound {return nil, nil}return nil, result.Error}return &product, nil}func (g gormMySqlProductRepository) FindAll(ctx context.Context) ([]*models.Product, error) {var products []*models.Productresult := g.db.WithContext(ctx).Find(&products)if result.Error != nil {if result.Error == gorm.ErrRecordNotFound {return nil, nil}return nil, result.Error}return products, nil
}func (g gormMySqlProductRepository) Update(ctx context.Context, product *models.Product) error {result := g.db.WithContext(ctx).Save(product)if result.Error != nil {return result.Error}return nil
}func (g gormMySqlProductRepository) Delete(ctx context.Context, id int64) error {result := g.db.WithContext(ctx).Delete(&models.Product{}, id)return result.Error
}// NewProductRepository 創建一個新的 ProductRepository 實例
func NewProductRepository(db *gorm.DB) ProductRepository {return &gormMySqlProductRepository{db: db}
}

Q: "gorm 官方文檔中顯示可以使用db.create直接操作數據庫,比如: 新增數據直接使用db.create(bean),但是上述代碼中使用的是 g.db.WithContext(ctx).create 這是為什么 "?

A:雖然直接使用 r.db.Create(product) 在功能上可以成功插入數據,但使用 r.db.WithContext(ctx).Create(product) 是一種更專業、更具彈性的最佳實踐

詳細解釋一下 WithContext(ctx) 帶來的三大好處:

1. 請求取消傳播 (Cancellation Propagation)

  • 場景: 一個用戶向您的服務器發送了創建產品的請求,但這個請求需要執行一個耗時很長的數據庫操作。在操作完成前,用戶不耐煩地關閉了瀏覽器,或者網絡中斷了。
  • 不使用 WithContext 的情況: 您的服務器對此一無所知。即使請求的另一端已經沒人等待了,數據庫操作依然會繼續執行,直到完成。這白白浪費了寶貴的數據庫連接和服務器資源。
  • 使用 WithContext 的情況: Gin 會為每個 HTTP 請求創建一個 context (ctx)。當用戶斷開連接時,Gin 會“取消”這個 ctxWithContext(ctx) 會將這個“取消”信號傳遞給 GORM,GORM 再傳遞給底層的數據庫驅動。驅動程序收到信號后,可以提前終止那個正在執行的、已經沒有意義的數據庫查詢,從而立即釋放資源。

2. 超時控制 (Timeout Control)

  • 場景: 您可以為整個請求或某個特定的操作設置一個超時時間。比如,您規定任何數據庫操作都不能超過3秒。
  • 不使用 WithContext 的情況: 如果某個數據庫查詢因為鎖或者性能問題卡住了,它可能會永遠地掛起,永久性地占用一個數據庫連接,直到數據庫自己超時。
  • 使用 WithContext 的情況: 您可以在 Service 層或 Handler 層創建一個帶超時的 context (e.g., context.WithTimeout(ctx, 3*time.Second))。如果數據庫操作在3秒內沒有完成,ctx 會自動被取消。WithContext 感知到這個取消信號后,會立即終止數據庫操作,并返回一個超時錯誤。這可以有效地防止慢查詢拖垮整個系統。

3. 傳遞元數據 (Passing Metadata)

  • 場景: 在復雜的微服務架構中,您需要追蹤一個請求經過了哪些服務。通常會有一個全局唯一的“追蹤ID (Trace ID)”。
  • context 的作用: context 是在函數調用鏈中安全地傳遞這類請求范圍內的元數據(如 Trace ID)的標準方式,而無需修改每個函數的參數列表。GORM 和很多其他庫都能與 OpenTelemetry 等鏈路追蹤系統集成,從 ctx 中提取這些信息用于日志和監控。
第5步:實現關于"產品"的 productService

之前在實現 userservice 時,對于 userservice 我們沒有做到抽象成接口的形式,直接將userservice 做結構體進行聲明,在此我會將productservice進行抽象,抽象成一個接口的形式。

package serviceimport ("awesomeProject/internal/models"     "awesomeProject/internal/repository" "context""errors" // 導入errors包,用于創建自定義錯誤
)// ProductService 定義了產品相關的業務邏輯接口
type ProductService interface {CreateProduct(ctx context.Context, name string, price float64, stock int) (*models.Product, error)GetProduct(ctx context.Context, id int64) (*models.Product, error)GetAllProducts(ctx context.Context) ([]*models.Product, error)UpdateProduct(ctx context.Context, id int64, name string, price float64, stock int) (*models.Product, error)DeleteProduct(ctx context.Context, id int64) error
}// productService 是 ProductService 的具體實現
type productService struct {productRepo repository.ProductRepository // 它依賴于 ProductRepository 接口,而不是具體實現
}// NewProductService 是 ProductService 的構造函數
func NewProductService(repo repository.ProductRepository) ProductService {return &productService{productRepo: repo}
}// CreateProduct 處理創建新產品的業務邏輯
func (s *productService) CreateProduct(ctx context.Context, name string, price float64, stock int) (*models.Product, error) {// 在這里可以添加業務邏輯,例如:// 1. 驗證產品名稱是否有效if name == "" {return nil, errors.New("product name cannot be empty")}// 2. 驗證價格是否合法if price <= 0 {return nil, errors.New("product price must be positive")}// 3. 產品庫存是否合法if stock < 0 {return nil, errors.New("product stock cannot be negative")}// 創建一個新的產品模型實例product := &models.Product{Name:  name,Price: price, Stock: stock,}// 調用倉庫層來持久化數據err := s.productRepo.Create(ctx, product)if err != nil {return nil, err}return product, nil
}// GetProduct 處理獲取單個產品的業務邏輯
func (s *productService) GetProduct(ctx context.Context, id int64) (*models.Product, error) {// 直接調用倉庫層。return s.productRepo.FindByID(ctx, id)
}// GetAllProducts 處理獲取所有產品的業務邏輯
func (s *productService) GetAllProducts(ctx context.Context) ([]*models.Product, error) {return s.productRepo.FindAll(ctx)
}// UpdateProduct 處理更新產品的業務邏輯
func (s *productService) UpdateProduct(ctx context.Context, id int64, name string, price float64, stock int) (*models.Product, error) {// 1. 首先,獲取要更新的產品product, err := s.productRepo.FindByID(ctx, id)if err != nil {return nil, err // 如果在查找過程中發生數據庫錯誤}if product == nil {return nil, errors.New("product not found") // 如果產品不存在}// 2. 更新產品的字段product.Name = nameproduct.Price = priceproduct.Stock = stock// 3. 在這里可以添加更復雜的驗證邏輯...// 4. 調用倉庫層的更新方法err = s.productRepo.Update(ctx, product)if err != nil {return nil, err}return product, nil
}// DeleteProduct 處理刪除產品的業務邏輯
func (s *productService) DeleteProduct(ctx context.Context, id int64) error {// 在刪除前,可以添加權限檢查等業務邏輯// 例如:檢查當前用戶是否有權限刪除該產品return s.productRepo.Delete(ctx, id)
}
第6步:實現全新的productHandler.go
package httpimport ("awesomeProject/internal/service" "github.com/gin-gonic/gin""net/http""strconv"
)// ProductHandler 負責處理產品相關的HTTP請求
type ProductHandler struct {productService service.ProductService // 它依賴于 ProductService 接口
}// NewProductHandler 是 ProductHandler 的構造函數
func NewProductHandler(svc service.ProductService) *ProductHandler {return &ProductHandler{productService: svc}
}// CreateProduct godoc
// @Summary      創建一個新產品
// @Description  根據傳入的JSON數據創建一個新產品
// @Tags         Products
// @Accept       json
// @Produce      json
// @Param        product  body      CreateProductRequest  true  "創建產品請求"
// @Success      201      {object}  models.Product
// @Failure      400      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /products [post]
func (h *ProductHandler) CreateProduct(c *gin.Context) {// 定義一個臨時的結構體來綁定請求的JSON bodyvar req struct {Name  string  `json:"name" binding:"required"`Price float64 `json:"price" binding:"gt=0"`Stock int     `json:"stock" binding:"gte=0"`}// 解析并驗證JSON請求體if err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data: " + err.Error()})return}// 調用Service層來創建產品product, err := h.productService.CreateProduct(c.Request.Context(), req.Name, req.Price, req.Stock)if err != nil {// 根據Service層返回的錯誤類型,可以返回更具體的HTTP狀態碼c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 返回201 Created狀態碼和創建成功的產品信息c.JSON(http.StatusCreated, product)
}// GetProduct godoc
// @Summary      獲取單個產品
// @Description  根據產品ID獲取產品詳情
// @Tags         Products
// @Produce      json
// @Param        id   path      int  true  "產品ID"
// @Success      200  {object}  models.Product
// @Failure      400  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /products/{id} [get]
func (h *ProductHandler) GetProduct(c *gin.Context) {// 從URL路徑中獲取ID參數id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})return}// 調用Service層獲取產品product, err := h.productService.GetProduct(c.Request.Context(), id)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 如果Service層返回nil,說明產品不存在if product == nil {c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})return}c.JSON(http.StatusOK, product)
}// GetAllProducts godoc
// @Summary      獲取所有產品列表
// @Description  獲取數據庫中所有產品的列表
// @Tags         Products
// @Produce      json
// @Success      200  {array}   models.Product
// @Failure      500  {object}  gin.H
// @Router       /products [get]
func (h *ProductHandler) GetAllProducts(c *gin.Context) {products, err := h.productService.GetAllProducts(c.Request.Context())if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}c.JSON(http.StatusOK, products)
}// UpdateProduct godoc
// @Summary      更新一個產品
// @Description  根據ID和傳入的JSON數據更新一個已存在的產品
// @Tags         Products
// @Accept       json
// @Produce      json
// @Param        id       path      int                   true  "產品ID"
// @Param        product  body      UpdateProductRequest  true  "更新產品請求"
// @Success      200      {object}  models.Product
// @Failure      400      {object}  gin.H
// @Failure      404      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /products/{id} [put]
func (h *ProductHandler) UpdateProduct(c *gin.Context) {id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})return}var req struct {Name  string  `json:"name" binding:"required"`Price float64 `json:"price" binding:"gt=0"`Stock int     `json:"stock" binding:"gte=0"`}if err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data: " + err.Error()})return}product, err := h.productService.UpdateProduct(c.Request.Context(), id, req.Name, req.Price, req.Stock)if err != nil {// 這里可以根據service返回的錯誤類型判斷是404還是500c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}c.JSON(http.StatusOK, product)
}// DeleteProduct godoc
// @Summary      刪除一個產品
// @Description  根據ID刪除一個產品
// @Tags         Products
// @Produce      json
// @Param        id   path      int  true  "產品ID"
// @Success      204  {object}  nil
// @Failure      500  {object}  gin.H
// @Router       /products/{id} [delete]
func (h *ProductHandler) DeleteProduct(c *gin.Context) {id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})return}err = h.productService.DeleteProduct(c.Request.Context(), id)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 對于刪除操作,成功后通常返回 204 No Contentc.Status(http.StatusNoContent)
}
第7步:實現全新的main.go

實現全新的main.go

package mainimport ("awesomeProject/internal/config""awesomeProject/internal/database""awesomeProject/internal/repository""awesomeProject/internal/service""awesomeProject/transport/http"_ "gorm.io/gorm""log""github.com/gin-gonic/gin"
)func main() {// 1. 加載配置cfg, err := config.Load("./configs/config.dev.yaml")if err != nil {log.Fatalf("FATAL: Failed to load config: %v", err)}// 2. 初始化GORM數據庫連接// 我們將GORM的初始化邏輯也封裝到了database包中,使main.go更整潔db, err := database.NewGormConnection(cfg.Database)if err != nil {log.Fatalf("FATAL: Failed to connect to database: %v", err)}log.Println("Database connection established successfully.")// 3. 依賴注入:將所有組件連接起來// 數據流向: Handler -> Service -> Repository -> Database// a. 創建 Repository 實例,它依賴 GORM 的數據庫連接(db)productRepo := repository.NewProductRepository(db)// b. 創建 Service 實例,它依賴 Repository 層的接口(productRepo)productService := service.NewProductService(productRepo)// c. 創建 Handler 實例,它依賴 Service 層的接口(productService)productHandler := http.NewProductHandler(productService)// 4. 初始化 Gin 路由引擎router := gin.Default()// 5. 注冊產品相關的路由// 創建一個API分組,方便管理版本,例如 /api/v1apiV1 := router.Group("/api/v1"){products := apiV1.Group("/products"){products.POST("", productHandler.CreateProduct)       // 創建產品products.GET("", productHandler.GetAllProducts)       // 獲取所有產品products.GET("/:id", productHandler.GetProduct)       // 獲取單個產品products.PUT("/:id", productHandler.UpdateProduct)    // 更新產品products.DELETE("/:id", productHandler.DeleteProduct) // 刪除產品}}// 6. 啟動服務器log.Println("Starting server on port :8080")if err := router.Run(":8080"); err != nil {log.Fatalf("FATAL: Failed to start server: %v", err)}
}
接口調用測試

添加商品成功

請添加圖片描述

查詢商品

請忽略中間的參數,懶得沒刪除而已

請添加圖片描述

查詢全部商品

請添加圖片描述

更新編號為1的商品

更新前:

請添加圖片描述

更新后:

請添加圖片描述

刪除編號為1的商品:

請添加圖片描述

刪除后查詢

請添加圖片描述

數據庫建設

products.sql

-- auto-generated definition
create table products
(id         bigint unsigned auto_incrementprimary key,created_at datetime(3)      null,updated_at datetime(3)      null,deleted_at datetime(3)      null,name       varchar(255)     not null,price      decimal(10, 2)   null,stock      bigint default 0 null
);create index idx_products_deleted_aton products (deleted_at);

數據表說明:

products: GORM 默認會將結構體名稱 Product 轉換為蛇形復數形式作為表名。

gorm.Model: 嵌入的 gorm.Model 自動為添加了 id, created_at, updated_at, deleted_at 四個核心字段。deleted_at 用于實現 GORM 的軟刪除功能。

請添加圖片描述

總結

通過將 GORM 集成到我們的分層架構中,我們實現了一個完美的平衡:

  • 獲得了 ORM 帶來的高開發效率:告別了繁瑣的 SQL 編寫和手動映射。
  • 保留了清晰的架構和解耦:各層職責分明,易于維護和測試。

有用的網站:

  • GORM 指南 | GORM 中文文檔

代碼倉庫:

🌍代碼框架鏈接


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88472.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88472.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88472.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CD47.【C++ Dev】list的模擬實現(2)

目錄 1.const修飾的迭代器的實現 方法1:分成兩個類 完整代碼 方法2:STL庫的寫法 2.STL庫的第三個模版參數T*的解釋 ->->的簡寫語法 3.其他成員函數 insert erase push_back、push_front、pop_front、pop_back size clear 析構函數~list() 拷貝構造函數(★…

UI前端與數字孿生融合新領域拓展:智慧教育的虛擬實驗室建設

hello寶子們...我們是艾斯視覺擅長ui設計、前端開發、數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩!一、引言&#xff1a;虛擬實驗室 —— 打破教育邊界的技術革命傳統實驗教學正面臨 “設備昂貴、…

7. TCP 和 UDP 的區別

總結 TCP 面向連接&#xff0c;需要三次握手建立連接&#xff0c;UDP 無連接&#xff0c;不需要握手&#xff0c;直接發送數據。UDP 有較好的實時性&#xff0c;效率比 TCP 高。TCP 面向字節流&#xff0c;實際上是 TCP 把數據看成一連串無結構的字節流&#xff0c;UDP 是面向報…

iOS Widget 開發-7:TimelineProvider 機制全解析:構建未來時間線

在 WidgetKit 中&#xff0c;TimelineProvider 是小組件生命周期的核心機制之一。它控制著 數據獲取時機、展示內容 與 刷新策略&#xff0c;是實現時間驅動內容更新的基礎。 本文將介紹 TimelineProvider 的工作原理、設計模式、常見場景與高級用法&#xff0c;幫助大家構建智…

基于PHP/MySQL的企業培訓考試系統源碼,高并發、穩定運行,源碼開源可二開

溫馨提示&#xff1a;文末有資源獲取方式這是一款專為企業設計的開源培訓考試系統&#xff0c;采用PHPMySQL技術棧開發&#xff0c;具有高并發處理能力和穩定運行特性。系統源碼完全開放&#xff0c;支持二次開發&#xff0c;可滿足各類企業的培訓考核需求。核心功能特點1. 高性…

時序數據庫InfluxDB

一.定義 時序數據庫 是一種專門用于高效存儲和查詢帶有時間戳的數據的數據庫。如果你的數據是隨著時間變化而不斷產生&#xff0c;并且你想知道過去某一時刻發生了什么&#xff0c;那么你應該用時序數據庫。 這類數據通常具有以下特征&#xff1a; 數據點按時間順序不斷寫入…

2025.07.09華為機考真題解析-第三題300分

?? 點擊直達筆試專欄 ??《大廠筆試突圍》 ?? 春秋招筆試突圍在線OJ ?? 筆試突圍OJ 03. 博物館安保攝像頭配置 問題描述 A先生負責為一家新開的博物館設計安保監控系統。博物館有多個展廳需要監控,每個展廳都有不同的面積。現在有多種型號的監控攝像頭可供選擇,每…

存儲過程封裝:復雜業務邏輯的性能優化

存儲過程作為數據庫層面的重要功能&#xff0c;能夠顯著提升復雜業務邏輯的執行效率。以下是存儲過程在性能優化中的核心優勢、實現策略和實際應用場景。一、存儲過程的核心優勢?網絡傳輸壓縮?存儲過程將多條SQL語句封裝為單次調用&#xff0c;相比應用層多次請求可減少60%-8…

逗號分隔字段統計秘籍:一條SQL實現逗號分割字段的數量分析

一、問題場景與痛點 在數據庫設計中&#xff0c;經常會遇到統計某一些數據的最大數量最小數量等&#xff0c;特別是**逗號分隔字段 **的統計會顯得非常困難 下面以我生產上遇到的一個問題講解&#xff1a; 有個需求是在o_work_order表中統計sn字段中哪個工單號的數量最多&#…

數據庫性能優化指南:解決ORDER BY導致的查詢性能問題( SQL Server )

數據庫性能優化指南&#xff1a;解決ORDER BY導致的查詢性能問題 問題描述 在300萬行的INTERFACE_INTERACTION_LOG表中執行以下查詢&#xff1a; SELECT TOP 1 * FROM INTERFACE_INTERACTION_LOG WHERE 1 1AND (SENDSTATUS 0 OR SENDSTATUS -1)AND SENDMETHOD POSTAND ERRO…

Centos 7下使用C++使用Rdkafka庫實現生產者消費者

1. 了解 Kafka Apache Kafka 是一個分布式流處理平臺&#xff0c;核心功能包括&#xff1a; 發布/訂閱消息系統&#xff1a;解耦生產者和消費者 分布式存儲&#xff1a;持久化、容錯的消息存儲 流處理&#xff1a;實時處理數據流 核心概念&#xff1a; 概念說明BrokerKaf…

UE5多人MOBA+GAS 13、添加死亡、復活邏輯以及布娃娃含物理資產的修改調整

文章目錄使用GE為角色添加定時的Tag控制死亡時間1、添加死亡Tag2、創建死亡GE&#xff0c;并完成相關配置3、在AbilitySystemComponent中監聽屬性的變化&#xff0c;調用GE來添加Tag到角色上4、在角色中監聽ASC傳入的Tag以及Tag的層數&#xff0c;來響應不同的函數添加死亡、復…

Jiasou TideFlow重塑AI SEO全鏈路自動化新標桿

引言 在Google日均處理85億次搜索請求的數字化浪潮中&#xff0c;傳統SEO工作流面臨三大致命瓶頸&#xff1a;人工拓詞效率低下、跨部門協作成本高企、數據監控鏈路斷裂。因此諸如Jiasou AI SEO這樣專門為AI SEO而生的Agent就應運而生了。 背景 Jiasou AIGC不僅僅可以批量生成…

CentOs 7 MySql8.0.23之前的版本主從復制

準備倆臺虛擬機并啟動倆臺虛擬機都開啟mysql后查看二進制日志是否開啟先登錄mysqlmysql -u root -r輸入sql命令show variables like %log_bin%;如果log_bin 的value為OFF則是沒有開啟&#xff0c;跟著下面步驟開啟二進制日志退出mysqlexitvim /etc/my.cnf在最底下添加log_binmy…

Leetcode 3607. Power Grid Maintenance

Leetcode 3607. Power Grid Maintenance 1. 解題思路2. 代碼實現 題目鏈接&#xff1a;3607. Power Grid Maintenance 1. 解題思路 這一題思路上首先是一個DSU的思路&#xff0c;將所有的連通網絡計算出來&#xff0c;并對每一個網絡的節點進行歸類。然后我們需要對每一個網…

開源 python 應用 開發(三)python語法介紹

最近有個項目需要做視覺自動化處理的工具&#xff0c;最后選用的軟件為python&#xff0c;剛好這個機會進行系統學習。短時間學習&#xff0c;需要快速開發&#xff0c;所以記錄要點步驟&#xff0c;防止忘記。 鏈接&#xff1a; 開源 python 應用 開發&#xff08;一&#xf…

1-Kafka介紹及常見應用場景

Kafka 介紹 Apache Kafka 是一個開源的 分布式流處理平臺&#xff0c;最初由 LinkedIn 開發&#xff0c;后捐贈給 Apache 軟件基金會。它被設計用于高吞吐量、低延遲、可水平擴展地處理實時數據流。官網地址是&#xff1a;https://kafka.apache.org/ 以下是 Kafka 的核心介紹…

CH9121T電路及配置詳解

目錄1. CH9121T簡介2. 原理圖及接口2.1 參考電路2.2 CH9121T評估板2.3 差分端口2.4 網口燈顯示2.5 晶振2.6 其他接口3. 使用手冊及說明3.1 配置介紹3.2 默認參數3.3 串口波特率3.4 配置指令3.5 應用示例1. CH9121T簡介 CH9121 是一款網絡串口透傳芯片&#xff0c;自帶 10/100M…

科研數據可視化核心技術:基于 AI 與 R 語言的熱圖、火山圖及網絡圖繪制實踐指南

在學術研究競爭日趨激烈的背景下&#xff0c;高質量的數據可視化已成為科研成果呈現與學術傳播的關鍵要素。據統計&#xff0c;超過 60% 的學術稿件拒稿原因與圖表質量存在直接關聯&#xff0c;而傳統繪圖工具在處理組學數據、復雜關聯數據時&#xff0c;普遍存在效率低下、規范…

Windows體驗macOS完整指南

一、虛擬機安裝macOS專業方案1. 環境準備階段硬件檢測&#xff1a;進入BIOS&#xff08;開機時按Del/F2鍵&#xff09;確認開啟VT-x/AMD-V虛擬化選項建議配置&#xff1a;i5十代以上CPU/16GB內存/256GB SSD軟件準備&#xff1a;官網下載VMware Workstation 17 Pro獲取Unlocker補…