существуетПредыдущийВ этой статье я поделился некоторым опытом и расширениями своего использования gorm в новых и обновленных сценариях. В этой статье я поделюсь своим опытом создания запросов.
Во-первых, я разделил запрос на количество задействованных таблиц:
- Запрос одной таблицы
- Многотабличный запрос
По объему запроса его можно разделить на:
- запрос один
- запрос диапазона
- запросить группу
- упорядоченный запрос
- запросить первые несколько
- Пейджинговый запрос
При ежедневном использовании однотабличный запрос занимает большую часть сцены. Инкапсуляция этой части кода в соответствии с областью запроса может значительно уменьшить избыточный код.
Запрос одной таблицы
Итак, я следовал стилю API gorm и сделал следующую инкапсуляцию:
ps: в следующих примерах предполагается, что пользовательский объект был определен
запрос один
func (dw *DBExtension) GetOne(result interface{}, query interface{}, args ...interface{}) (found bool, err error) {
var (
tableNameAble TableNameAble
ok bool
)
if tableNameAble, ok = query.(TableNameAble); !ok {
if tableNameAble, ok = result.(TableNameAble); !ok {
return false, errors.New("neither the query nor result implement TableNameAble")
}
}
err = dw.Table(tableNameAble.TableName()).Where(query, args...).First(result).Error
if err == gorm.ErrRecordNotFound {
dw.logger.LogInfoc("mysql", fmt.Sprintf("record not found for query %s, the query is %+v, args are %+v", tableNameAble.TableName(), query, args))
return false, nil
}
if err != nil {
dw.logger.LogErrorc("mysql", err, fmt.Sprintf("failed to query %s, the query is %+v, args are %+v", tableNameAble.TableName(), query, args))
return false, err
}
return true, nil
}
Этот абзац стоит пояснить об обработке, когда данные не могут быть запрошены, сообщает горм.gorm.ErrRecordNotFoundОшибка, я сделал специальную обработку этой ошибки и выразил это особое состояние с помощью логического значения found.
Код вызова следующий:
condition := User{Id:1}
result := User{}
if found, err := dw.GetOne(&result, condition); !found {
//not found
if err != nil {
// has error
return err
}
}
Его также можно написать так, чтобы более гибко указывать условия запроса:
result := User{}
if found, err := dw.GetOne(&result, "id = ?", 1); !found {
//not found
if err != nil {
// has error
return err
}
}
Операторы, выполняемые в обоих способах написания:
select * from test.user where id = 1
запрос диапазона
Для четырех типов запросов страны я сделал следующий пакет:
func (dw *DBExtension) GetList(result interface{}, query interface{}, args ...interface{}) error {
return dw.getListCore(result, "", 0, 0, query, args)
}
func (dw *DBExtension) GetOrderedList(result interface{}, order string, query interface{}, args ...interface{}) error {
return dw.getListCore(result, order, 0, 0, query, args)
}
func (dw *DBExtension) GetFirstNRecords(result interface{}, order string, limit int, query interface{}, args ...interface{}) error {
return dw.getListCore(result, order, limit, 0, query, args)
}
func (dw *DBExtension) GetPageRangeList(result interface{}, order string, limit, offset int, query interface{}, args ...interface{}) error {
return dw.getListCore(result, order, limit, offset, query, args)
}
func (dw *DBExtension) getListCore(result interface{}, order string, limit, offset int, query interface{}, args []interface{}) error {
var (
tableNameAble TableNameAble
ok bool
)
if tableNameAble, ok = query.(TableNameAble); !ok {
// type Result []*Item{}
// result := &Result{}
resultType := reflect.TypeOf(result)
if resultType.Kind() != reflect.Ptr {
return errors.New("result is not a pointer")
}
sliceType := resultType.Elem()
if sliceType.Kind() != reflect.Slice {
return errors.New("result doesn't point to a slice")
}
// *Item
itemPtrType := sliceType.Elem()
// Item
itemType := itemPtrType.Elem()
elemValue := reflect.New(itemType)
elemValueType := reflect.TypeOf(elemValue)
tableNameAbleType := reflect.TypeOf((*TableNameAble)(nil)).Elem()
if elemValueType.Implements(tableNameAbleType) {
return errors.New("neither the query nor result implement TableNameAble")
}
tableNameAble = elemValue.Interface().(TableNameAble)
}
db := dw.Table(tableNameAble.TableName()).Where(query, args...)
if len(order) != 0 {
db = db.Order(order)
}
if offset > 0 {
db = db.Offset(offset)
}
if limit > 0 {
db = db.Limit(limit)
}
if err := db.Find(result).Error; err != nil {
dw.logger.LogErrorc("mysql", err, fmt.Sprintf("failed to query %s, query is %+v, args are %+v, order is %s, limit is %d", tableNameAble.TableName(), query, args, order, limit))
return err
}
return nil
}
Чтобы уменьшить избыточный код, общая логика написана наgetListCoreВ функции используются некоторые знания об отражении голанга.
Но просто помните, что самая большая разница между отражением golang и отражением других языков заключается в том, чтоотражение golang для примитивных значений вместо типов, все легко понять.
Один из приемов заключается в том, как определить, реализует ли тип интерфейс, используя указатель на nil.
elemValue := reflect.New(itemType)
elemValueType := reflect.TypeOf(elemValue)
tableNameAbleType := reflect.TypeOf((*TableNameAble)(nil)).Elem()
if elemValueType.Implements(tableNameAbleType) {
return errors.New("neither the query nor result implement TableNameAble")
}
Что касается конкретного использования, то я не буду приводить примеры по одному, студенты, знакомые с gorm api, смогут увидеть это с первого взгляда.
Многотабличный запрос
Что касается многотабличных запросов, потому что сложно извлечь разные сценарии в разных сценариях, нет инкапсуляции, но мой опыт таков.Отдайте приоритет использованию метода gorm вместо самостоятельного написания sql. Вы можете делать все, что хотите.
Здесь я ленюсь и публикую самый сложный кусок кода, который я написал в проекте, для вашего развлечения.
сложный пример
Этот код взят из промежуточной таблицы данных скрытых точек. Чтобы использовать общий код для реализации запросов в различных сценариях отображения, дизайн кода стал более гибким, что включает запросы к нескольким связанным таблицам, динамическую фильтрацию и агрегирование в соответствии с условиями запроса, а также Логика запроса пагинации.
func buildCommonStatisticQuery(tableName, startDate, endDate string) *gorm.DB {
query := models.DB().Table(tableName)
if startDate == endDate || endDate == "" {
query = query.Where("date = ?", startDate)
} else {
query = query.Where("date >= ? and date <= ?", startDate, endDate)
}
return query
}
func buildElementsStatisticQuery(startDate, endDate, elemId string, elemType int32) *gorm.DB {
query := buildCommonStatisticQuery("spotanalysis.element_statistics", startDate, endDate)
if elemId != "" && elemType != 0 {
query = query.Where("element_id = ? and element_type = ?", elemId, elemType)
}
return query
}
func CountElementsStatistics(count *int32, startDate, endDate, instId, appId, elemId string, elemType int32, groupFields []string ) error {
query := buildElementsStatisticQuery(startDate, endDate, elemId, elemType)
query = whereInstAndApp(query, instId, appId)
if len(groupFields) != 0 {
query = query.Select(fmt.Sprintf("count(distinct(concat(%s)))", strings.Join(groupFields, ",")))
} else {
query = query.Select("count(id)")
}
query = query.Count(count)
return query.Error
}
func GetElementsStatistics(result interface{}, startDate, endDate, instId, appId, elemId string, elemType int32, groupFields []string, orderBy string, ascOrder bool, limit, offset int32) error {
query := buildElementsStatisticQuery(startDate, endDate, elemId, elemType)
if len(groupFields) != 0 {
groupBy := strings.Join(groupFields, "`,`")
groupBy = "`" + groupBy + "`"
query = query.Group(groupBy)
query = havingInstAndApp(query, instId, appId)
sumFields := strings.Join([]string{
"SUM(`element_statistics`.`mp_count`) AS `mp_count`",
"SUM(`element_statistics`.`h5_count`) AS `h5_count`",
"SUM(`element_statistics`.`total_count`) AS `total_count`",
"SUM(`element_statistics`.`collection_count`) AS `collection_count`",
"SUM(`element_statistics`.`mp_share_count`) AS `mp_share_count`",
"SUM(`element_statistics`.`h5_share_count`) AS `h5_share_count`",
"SUM(`element_statistics`.`poster_share_count`) AS `poster_share_count`",
"SUM(`element_statistics`.`total_share_count`) AS `total_share_count`",
}, ",")
query = query.Select(groupBy + "," + sumFields)
} else {
query = whereInstAndApp(query, instId, appId)
}
query = getPagedList(query, orderBy, ascOrder, limit, offset)
return query.Find(result).Error
}
func getPagedList(query *gorm.DB, orderBy string, ascOrder bool, limit , offset int32) *gorm.DB {
if orderBy != "" {
if ascOrder {
orderBy += " asc"
} else {
orderBy += " desc"
}
query = query.Order(orderBy)
}
if offset != 0 {
query = query.Offset(offset)
}
if limit != 0 {
query = query.Limit(limit)
}
return query
}
func whereInstAndApp(query *gorm.DB, instId string, appId string) *gorm.DB {
query = query.Where("inst_id = ?", instId)
if appId != "" {
query = query.Where("app_id = ?", appId)
}
return query
}
func havingInstAndApp(query *gorm.DB, instId string, appId string) *gorm.DB {
query = query.Having("inst_id = ?", instId)
if appId != "" {
query = query.Having("app_id = ?", appId)
}
return query
}
Спасибо за ваше терпение, и если эта статья была вам полезна, пожалуйста, поставьте лайк~~~
Если вы можете перейти в репозиторий кода:Github:Ksloveyuan/gorm-exСтавьте ✩звезду✩, хозяин будет еще благодарен!