Golang Gorm实现自定义多态模型关联查询
一、表结构
CREATE TABLE `orders` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL DEFAULT '',
`orderable_id` int unsigned NOT NULL DEFAULT '0',
`orderable_type` char(30) NOT NULL DEFAULT '',
`status` tinyint unsigned NOT NULL DEFAULT '0',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `phone` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`phone_name` varchar(50) NOT NULL DEFAULT '',
`phone_model` varchar(30) NOT NULL DEFAULT '',
`status` tinyint unsigned NOT NULL DEFAULT '0',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `cars` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`car_name` varchar(50) NOT NULL DEFAULT '',
`car_model` varchar(30) NOT NULL DEFAULT '',
`status` tinyint unsigned NOT NULL DEFAULT '0',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
二、接口定义
// LoaderAble 接口定义数据加载行为
type LoaderAble interface {
LoadAble(IDs []int) (map[int]any, error)
}
// LoadAbleItem 接口定义了可加载项的通用方法
type LoadAbleItem interface {
GetAbleType() string // 获取类型键值
GetAbleID() int // 获取ID
SetLoadedAbleData(data any) // 设置加载的数据
}
三、模型定义并实现接口
type Order struct {
Id int `json:"id"`
OrderNo string `json:"order_no"`
OrderableId int `json:"orderable_id"`
OrderableType string `json:"orderable_type"`
Orderable any `json:"orderable" gorm:"-"`
Status uint8 `json:"status"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
func (tb *Order) TableName() string {
return "orders"
}
func (tb *Order) GetAbleType() string {
return tb.OrderableType
}
func (tb *Order) GetAbleID() int {
return tb.OrderableId
}
func (tb *Order) SetLoadedAbleData(data any) {
tb.Orderable = data
}
//--------------------------------------分割线--------------------------------------
type Car struct {
Id int `json:"id"`
CarName string `json:"car_name"`
CarModel string `json:"car_model"`
Status uint8 `json:"status"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
func (tb *Car) TableName() string {
return "cars"
}
// CarLoaderAble 实现 Loader 接口
type CarLoaderAble struct{}
// LoadAble 具体实现加载多态关联逻辑
// IDs 多态关联类型ID(主要参数)
func (loader *CarLoaderAble) LoadAble(IDs []int) (resultMap map[int]any, err error) {
IDsLen := len(IDs)
if IDsLen == 0 {
return
}
car := make([]*Car, 0, IDsLen)
err = mysql.DefaultMysql.Db.Debug().Where("id IN (?) AND status = ?", IDs, 1).Find(&car).Error
if err != nil {
return
}
resultMap = make(map[int]any, IDsLen)
for _, item := range car {
resultMap[item.Id] = item
}
return
}
//--------------------------------------分割线--------------------------------------
type Phone struct {
Id int `json:"id"`
PhoneName string `json:"phone_name"`
PhoneModel string `json:"phone_model"`
Status uint8 `json:"status"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
func (tb *Phone) TableName() string {
return "phone"
}
// PhoneLoaderAble 实现 Loader 接口
type PhoneLoaderAble struct{}
// LoadAble 具体实现加载多态关联逻辑
// IDs 多态关联类型ID(主要参数)
func (loader *PhoneLoaderAble) LoadAble(IDs []int) (resultMap map[int]any, err error) {
IDsLen := len(IDs)
if IDsLen == 0 {
return
}
phone := make([]*Phone, 0, IDsLen)
err = mysql.DefaultMysql.Db.Debug().Where("id IN (?) AND status = ?", IDs, 1).Find(&phone).Error
if err != nil {
return
}
resultMap = make(map[int]any, IDsLen)
for _, item := range phone {
resultMap[item.Id] = item
}
return
}
四、创建loader预加载器
// LoaderAbleFactory 用于管理不同类型的加载器
type LoaderAbleFactory struct {
loaders map[string]LoaderAble
}
func NewLoaderAbleFactory() *LoaderAbleFactory {
return &LoaderAbleFactory{
loaders: make(map[string]LoaderAble),
}
}
func (f *LoaderAbleFactory) RegisterLoader(typeName string, loader LoaderAble) {
f.loaders[typeName] = loader
}
五、注册loader预加载器服务
var (
loaderAbleFactory *LoaderAbleFactory
)
// init 选择在项目启动初始化时进行全局加载
func init() {
loaderAbleFactory = NewLoaderAbleFactory()
loaderAbleFactory.RegisterLoader("phone", &PhoneLoaderAble{})
loaderAbleFactory.RegisterLoader("car", &CarLoaderAble{})
log.Println("多态模型关系注册成功...")
}
六、实现LoadAble通用的加载函数
// LoadAble 通用的加载函数,可以处理任何实现了 LoadableItem 接口的切片
func LoadAble[T LoadAbleItem](items []T) error {
if len(items) == 0 {
return nil
}
// 按类型分组收集ID
typeIDsMap := make(map[string][]int)
for _, item := range items {
typeKey := item.GetAbleType()
typeIDsMap[typeKey] = append(typeIDsMap[typeKey], item.GetAbleID())
}
// 使用对应的loader加载数据
typeDataMap := make(map[string]map[int]any)
for typeName, ids := range typeIDsMap {
loader, ok := loaderAbleFactory.loaders[typeName]
if !ok {
continue
}
resultMap, err := loader.LoadAble(ids)
if err != nil {
return err
}
typeDataMap[typeName] = resultMap
}
// 填充数据
for _, item := range items {
if dataMap, ok := typeDataMap[item.GetAbleType()]; ok {
if data, exists := dataMap[item.GetAbleID()]; exists {
item.SetLoadedAbleData(data)
}
}
}
return nil
}
七、调试
- 准备数据
--orders表
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, '202411010001', 1002, 'car', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, '202411010002', 1001, 'phone', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, '202411010003', 1000, 'car', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (4, '202411010004', 1001, 'car', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (5, '202411010005', 1002, 'phone', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
--phone表
INSERT INTO `phone` (`id`, `phone_name`, `phone_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1000, 'XiaoMi', '2S', 2, '2024-11-01 11:59:37', '2024-11-01 11:59:40', NULL);
INSERT INTO `phone` (`id`, `phone_name`, `phone_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1001, 'HUAWEI', 'mate60', 1, '2024-11-01 11:59:54', '2024-11-01 11:59:57', NULL);
INSERT INTO `phone` (`id`, `phone_name`, `phone_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1002, 'Apple', 'iPhone 12 Pro Max', 1, '2024-11-01 12:00:26', '2024-11-01 12:00:28', NULL);
--cars表
INSERT INTO `cars` (`id`, `car_name`, `car_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1000, '奥迪', 'A6L', 1, '2024-11-01 11:57:53', '2024-11-01 11:57:55', NULL);
INSERT INTO `cars` (`id`, `car_name`, `car_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1001, '宝马', '5系', 1, '2024-11-01 11:58:12', '2024-11-01 11:58:15', NULL);
INSERT INTO `cars` (`id`, `car_name`, `car_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1002, '奔驰', 'E300', 1, '2024-11-01 11:58:53', '2024-11-01 11:58:56', NULL);
2. 编写代码
// Gin框架+Gorm为例
api.GET("/orders", controller.GetOrderList)
// GetOrderList 获取订单列表接口
func GetOrderList(c *gin.Context) {
orders := make([]*model.Order, 0)
err := mysql.DefaultMysql.Db.Debug().Find(&orders).Error
if err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
err = model.LoadAble(orders)
if err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"data": orders})
}
3. 发起请求
curl '127.0.0.1:16888/api/orders'
{
"data": [
{
"id": 1,
"order_no": "202411010001",
"orderable_id": 1002,
"orderable_type": "car",
"orderable": {
"id": 1002,
"car_name": "奔驰",
"car_model": "E300",
"status": 1,
"created_at": "2024-11-01T11:58:53+08:00",
"updated_at": "2024-11-01T11:58:56+08:00",
"deleted_at": null
},
"status": 1,
"created_at": "2024-11-01T12:03:03+08:00",
"updated_at": "2024-11-01T12:03:06+08:00",
"deleted_at": null
},
{
"id": 2,
"order_no": "202411010002",
"orderable_id": 1001,
"orderable_type": "phone",
"orderable": {
"id": 1001,
"phone_name": "HUAWEI",
"phone_model": "mate60",
"status": 1,
"status_new": 1,
"created_at": "2024-11-01T11:59:54+08:00",
"updated_at": "2024-11-01T11:59:57+08:00",
"deleted_at": null
},
"status": 1,
"created_at": "2024-11-01T12:03:03+08:00",
"updated_at": "2024-11-01T12:03:06+08:00",
"deleted_at": null
},
{
"id": 3,
"order_no": "202411010003",
"orderable_id": 1000,
"orderable_type": "car",
"orderable": {
"id": 1000,
"car_name": "奥迪",
"car_model": "A6L",
"status": 1,
"created_at": "2024-11-01T11:57:53+08:00",
"updated_at": "2024-11-01T11:57:55+08:00",
"deleted_at": null
},
"status": 1,
"created_at": "2024-11-01T12:03:03+08:00",
"updated_at": "2024-11-01T12:03:06+08:00",
"deleted_at": null
},
{
"id": 4,
"order_no": "202411010004",
"orderable_id": 1001,
"orderable_type": "car",
"orderable": {
"id": 1001,
"car_name": "宝马",
"car_model": "5系",
"status": 1,
"created_at": "2024-11-01T11:58:12+08:00",
"updated_at": "2024-11-01T11:58:15+08:00",
"deleted_at": null
},
"status": 1,
"created_at": "2024-11-01T12:03:03+08:00",
"updated_at": "2024-11-01T12:03:06+08:00",
"deleted_at": null
},
{
"id": 5,
"order_no": "202411010005",
"orderable_id": 1002,
"orderable_type": "phone",
"orderable": {
"id": 1002,
"phone_name": "Apple",
"phone_model": "iPhone 12 Pro Max",
"status": 1,
"status_new": 1,
"created_at": "2024-11-01T12:00:26+08:00",
"updated_at": "2024-11-01T12:00:28+08:00",
"deleted_at": null
},
"status": 1,
"created_at": "2024-11-01T12:03:03+08:00",
"updated_at": "2024-11-01T12:03:06+08:00",
"deleted_at": null
}
]
}