golang自动从结构体生成SQL查询的强大构建器插件patcher的使用
Golang自动从结构体生成SQL查询的强大构建器插件Patcher的使用
Patcher是一个Go库,提供了一种从结构体生成SQL补丁的简单方法。这个库的诞生源于为数据库生成补丁的需求:当向结构体添加新字段时,会导致代码库中出现大量新的if
检查。Patcher旨在通过自动生成SQL补丁来解决这个问题。
什么是Patcher?
- 自动SQL生成:自动从结构体生成SQL UPDATE查询,减少手动编写和维护SQL语句的需求
- 代码简化:减少了处理不同结构体字段所需的样板代码和if-else条件,使代码更简洁易维护
- 结构体差异:允许将一个结构体的更改注入到另一个结构体,并根据差异生成更新脚本
- JOIN支持:通过创建实现Joiner接口的结构体来生成SQL JOIN
为什么使用Patcher?
- 节省时间:自动从结构体生成SQL查询,减少手动编写和维护SQL语句的时间
- 减少错误:根据结构体字段自动生成SQL查询,消除结构体字段变更时需要手动更新查询的风险
- 简化代码:减少处理不同结构体字段所需的样板代码和if-else条件
- 简化数据同步:通过结构体差异比较生成更新脚本
- 支持JOIN:通过Joiner接口简化多表关联查询
- 灵活配置:提供自定义SQL生成过程的选项
- 易于集成:可轻松集成到现有项目中
- 开源:基于Apache 2.0许可证
使用示例
不使用Patcher的情况
package main
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
type User struct {
ID int `json:"id"`
Name *string `json:"name"`
Email *string `json:"email"`
}
func updateUser(db *sql.DB, user User) error {
query := "UPDATE users SET"
var updates []string
var args []any
if user.Name != nil {
updates = append(updates, "name = ?")
args = append(args, *user.Name)
}
if user.Email != nil {
updates = append(updates, "email = ?")
args = append(args, *user.Email)
}
if len(updates) == 0 {
return errors.New("no fields to update")
}
query += " " + strings.Join(updates, ", ") + " WHERE id = ?"
args = append(args, user.ID)
_, err := db.Exec(query, args...)
return err
}
func patchHandler(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer db.Close()
vars := mux.Vars(r)
userIDStr := vars["id"]
userID, err := strconv.Atoi(userIDStr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user := User{ID: userID}
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := updateUser(db, user); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/users/{id}", patchHandler).Methods("PATCH")
server := &http.Server{
Addr: ":8080",
Handler: r,
ReadHeaderTimeout: 10 * time.Second,
}
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
使用Patcher的情况
package main
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"github.com/jacobbrewer1/patcher"
)
type User struct {
ID *int `db:"id" json:"id,omitempty"`
Name *string `db:"name" json:"name,omitempty"`
Email *string `db:"email" json:"email,omitempty"`
}
type UserWhere struct {
ID *int `db:"id"`
}
func NewUserWhere(id int) *UserWhere {
return &UserWhere{ID: &id}
}
func (u *UserWhere) Where() (sqlStr string, sqlArgs []any) {
return "id = ?", []any{*u.ID}
}
func patchHandler(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer db.Close()
vars := mux.Vars(r)
idStr := vars["id"]
parsedID, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user := new(User)
if err := json.NewDecoder(r.Body).Decode(user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user.ID = &parsedID
condition := NewUserWhere(parsedID)
sqlStr, args, err := patcher.GenerateSQL(
user,
patcher.WithTable("users"),
patcher.WithWhere(condition),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, execErr := db.Exec(sqlStr, args...)
if execErr != nil {
http.Error(w, execErr.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/users/{id}", patchHandler).Methods("PATCH")
server := &http.Server{
Addr: ":8080",
Handler: r,
ReadHeaderTimeout: 5 * time.Second,
}
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
基本用法
基本示例
package main
import (
"encoding/json"
"fmt"
"github.com/jacobbrewer1/patcher"
)
type Person struct {
ID *int `db:"-"`
Name *string `db:"name"`
}
type PersonWhere struct {
ID *int `db:"id"`
}
func NewPersonWhere(id int) *PersonWhere {
return &PersonWhere{
ID: &id,
}
}
func (p *PersonWhere) Where() (string, []any) {
return "id = ?", []any{*p.ID}
}
func main() {
const jsonStr = `{"id": 1, "name": "john"}`
person := new(Person)
if err := json.Unmarshal([]byte(jsonStr), person); err != nil {
panic(err)
}
condition := NewPersonWhere(*person.ID)
sqlStr, args, err := patcher.GenerateSQL(
person,
patcher.WithTable("people"),
patcher.WithWhere(condition),
)
if err != nil {
panic(err)
}
fmt.Println(sqlStr)
fmt.Println(args)
}
输出SQL:
UPDATE people
SET name = ?
WHERE (1 = 1)
AND (
id = ?
)
参数:
["john", 1]
结构体差异比较
package main
import (
"fmt"
"github.com/jacobbrewer1/patcher"
)
type Something struct {
Number int
Text string
PrePopulated string
NewText string
}
func main() {
s := Something{
Number: 5,
Text: "Hello",
PrePopulated: "PrePopulated",
}
n := Something{
Number: 6,
Text: "Old Text",
NewText: "New Text",
}
// 将n的变更应用到s
if err := patcher.LoadDiff(&s, &n); err != nil {
panic(err)
}
fmt.Println(s.Number)
fmt.Println(s.Text)
fmt.Println(s.PrePopulated)
fmt.Println(s.NewText)
}
输出:
6
Hello
PrePopulated
New Text
从差异生成SQL更新
package main
import (
"fmt"
"github.com/jacobbrewer1/patcher"
)
type Something struct {
Number int
Text string
PrePopulated string
NewText string
}
type SomeWhere struct {
id int
}
func NewSomeWhere(id int) *SomeWhere {
return &SomeWhere{id: id}
}
func (s *SomeWhere) Where() (string, []any) {
return "id = ?", []any{s.id}
}
func main() {
s := Something{
Number: 5,
Text: "Old Text",
PrePopulated: "PrePopulated",
NewText: "New Text",
}
n := Something{
Number: 5,
Text: "Old Text",
PrePopulated: "PrePopulatedDifferent",
NewText: "New Text",
}
wherer := NewSomeWhere(5)
// 从差异创建SQL补丁
patch, err := patcher.NewDiffSQLPatch(
&s,
&n,
patcher.WithTable("table_name"),
patcher.WithWhere(wherer),
)
if err != nil {
panic(err)
}
sqlStr, sqlArgs, err := patch.GenerateSQL()
if err != nil {
panic(err)
}
fmt.Println(sqlStr)
fmt.Println(sqlArgs)
}
输出SQL:
UPDATE table_name
SET pre_populated = ?
WHERE (1 = 1)
AND (
id = ?
)
参数:
["PrePopulatedDifferent", 5]
安装
使用以下命令安装Patcher库:
go get github.com/jacobbrewer1/patcher
配置选项
LoadDiff选项
includeZeroValues
: 设置为true以在差异中包含零值includeNilValues
: 设置为true以在差异中包含nil值
GenerateSQL选项
WithTable(tableName string)
: 指定SQL查询的表名WithWhere(whereClause Wherer)
: 提供SQL查询的WHERE子句- 可以传递实现
WhereTyper
接口的结构体以在WHERE子句中使用OR
- 可以传递实现
WithJoin(joinClause Joiner)
: 添加JOIN子句到SQL查询includeZeroValues
: 设置为true以在补丁中包含零值includeNilValues
: 设置为true以在补丁中包含nil值
贡献
欢迎贡献!请按照以下步骤进行贡献:
- Fork仓库
- 为你的功能或错误修复创建新分支
- 为你的变更编写测试
- 运行测试确保一切正常
- 提交拉取请求
运行测试:
go test ./...
更多关于golang自动从结构体生成SQL查询的强大构建器插件patcher的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang自动从结构体生成SQL查询的强大构建器插件patcher的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 结构体到 SQL 查询构建器 - Patcher 使用指南
Patcher 是一个强大的 Go 语言工具,可以自动从结构体生成 SQL 查询语句,简化数据库操作。下面我将详细介绍如何使用 Patcher。
安装 Patcher
首先安装 Patcher 工具:
go get -u github.com/huandu/go-patcher/patcher
基本使用示例
假设我们有以下用户结构体:
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
Age int `db:"age"`
CreatedAt time.Time `db:"created_at"`
}
1. 生成 SELECT 查询
func GetUsersByAge(db *sql.DB, minAge, maxAge int) ([]User, error) {
var users []User
q := patcher.NewSelectQuery(&User{})
q.Where("age BETWEEN ? AND ?", minAge, maxAge)
q.OrderBy("age DESC")
q.Limit(10)
query, args := q.Build()
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt); err != nil {
return nil, err
}
users = append(users, user)
}
return users, nil
}
2. 生成 INSERT 查询
func CreateUser(db *sql.DB, user *User) error {
q := patcher.NewInsertQuery(user)
query, args := q.Build()
result, err := db.Exec(query, args...)
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
user.ID = id
return nil
}
3. 生成 UPDATE 查询
func UpdateUserEmail(db *sql.DB, id int64, newEmail string) error {
q := patcher.NewUpdateQuery(&User{})
q.Set("email", newEmail)
q.Where("id = ?", id)
query, args := q.Build()
_, err := db.Exec(query, args...)
return err
}
4. 生成 DELETE 查询
func DeleteUser(db *sql.DB, id int64) error {
q := patcher.NewDeleteQuery(&User{})
q.Where("id = ?", id)
query, args := q.Build()
_, err := db.Exec(query, args...)
return err
}
高级功能
条件构建
func SearchUsers(db *sql.DB, name string, minAge, maxAge int) ([]User, error) {
q := patcher.NewSelectQuery(&User{})
if name != "" {
q.Where("name LIKE ?", "%"+name+"%")
}
if minAge > 0 {
q.Where("age >= ?", minAge)
}
if maxAge > 0 {
q.Where("age <= ?", maxAge)
}
query, args := q.Build()
// 执行查询...
}
批量插入
func BatchCreateUsers(db *sql.DB, users []*User) error {
q := patcher.NewBatchInsertQuery(users...)
query, args := q.Build()
_, err := db.Exec(query, args...)
return err
}
自定义表名
默认情况下,Patcher 使用结构体名的小写形式作为表名。你可以通过实现 TableName()
方法自定义:
func (u *User) TableName() string {
return "app_users"
}
性能优化建议
- 对于频繁执行的查询,可以缓存构建好的 SQL 语句
- 批量操作时使用事务提高性能
- 复杂的 JOIN 查询建议手动编写 SQL
总结
Patcher 提供了从结构体自动生成 SQL 查询的强大功能,可以显著减少样板代码。它特别适合 CRUD 操作,但对于复杂查询,可能需要结合原生 SQL 使用。
希望这个指南能帮助你快速上手 Patcher!如需更高级用法,可以参考官方文档。