Files
sponge/internal/dao/userExample.go
2024-02-08 16:35:33 +08:00

408 lines
11 KiB
Go

package dao
import (
"context"
"errors"
"fmt"
"time"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/model"
cacheBase "github.com/zhufuyi/sponge/pkg/cache"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/zhufuyi/sponge/pkg/utils"
"golang.org/x/sync/singleflight"
"gorm.io/gorm"
)
var _ UserExampleDao = (*userExampleDao)(nil)
// UserExampleDao defining the dao interface
type UserExampleDao interface {
Create(ctx context.Context, table *model.UserExample) error
DeleteByID(ctx context.Context, id uint64) error
DeleteByIDs(ctx context.Context, ids []uint64) error
UpdateByID(ctx context.Context, table *model.UserExample) error
GetByID(ctx context.Context, id uint64) (*model.UserExample, error)
GetByCondition(ctx context.Context, condition *query.Conditions) (*model.UserExample, error)
GetByIDs(ctx context.Context, ids []uint64) (map[uint64]*model.UserExample, error)
GetByLastID(ctx context.Context, lastID uint64, limit int, sort string) ([]*model.UserExample, error)
GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error)
CreateByTx(ctx context.Context, tx *gorm.DB, table *model.UserExample) (uint64, error)
DeleteByTx(ctx context.Context, tx *gorm.DB, id uint64) error
UpdateByTx(ctx context.Context, tx *gorm.DB, table *model.UserExample) error
}
type userExampleDao struct {
db *gorm.DB
cache cache.UserExampleCache // if nil, the cache is not used.
sfg *singleflight.Group // if cache is nil, the sfg is not used.
}
// NewUserExampleDao creating the dao interface
func NewUserExampleDao(db *gorm.DB, xCache cache.UserExampleCache) UserExampleDao {
if xCache == nil {
return &userExampleDao{db: db}
}
return &userExampleDao{
db: db,
cache: xCache,
sfg: new(singleflight.Group),
}
}
func (d *userExampleDao) deleteCache(ctx context.Context, id uint64) error {
if d.cache != nil {
return d.cache.Del(ctx, id)
}
return nil
}
// Create a record, insert the record and the id value is written back to the table
func (d *userExampleDao) Create(ctx context.Context, table *model.UserExample) error {
err := d.db.WithContext(ctx).Create(table).Error
_ = d.deleteCache(ctx, table.ID)
return err
}
// DeleteByID delete a record by id
func (d *userExampleDao) DeleteByID(ctx context.Context, id uint64) error {
err := d.db.WithContext(ctx).Where("id = ?", id).Delete(&model.UserExample{}).Error
if err != nil {
return err
}
// delete cache
_ = d.deleteCache(ctx, id)
return nil
}
// DeleteByIDs delete records by batch id
func (d *userExampleDao) DeleteByIDs(ctx context.Context, ids []uint64) error {
err := d.db.WithContext(ctx).Where("id IN (?)", ids).Delete(&model.UserExample{}).Error
if err != nil {
return err
}
// delete cache
for _, id := range ids {
_ = d.deleteCache(ctx, id)
}
return nil
}
// UpdateByID update a record by id
func (d *userExampleDao) UpdateByID(ctx context.Context, table *model.UserExample) error {
err := d.updateDataByID(ctx, d.db, table)
// delete cache
_ = d.deleteCache(ctx, table.ID)
return err
}
func (d *userExampleDao) updateDataByID(ctx context.Context, db *gorm.DB, table *model.UserExample) error {
if table.ID < 1 {
return errors.New("id cannot be 0")
}
update := map[string]interface{}{}
// todo generate the update fields code to here
// delete the templates code start
if table.Name != "" {
update["name"] = table.Name
}
if table.Password != "" {
update["password"] = table.Password
}
if table.Email != "" {
update["email"] = table.Email
}
if table.Phone != "" {
update["phone"] = table.Phone
}
if table.Avatar != "" {
update["avatar"] = table.Avatar
}
if table.Age > 0 {
update["age"] = table.Age
}
if table.Gender > 0 {
update["gender"] = table.Gender
}
if table.LoginAt > 0 {
update["login_at"] = table.LoginAt
}
// delete the templates code end
return db.WithContext(ctx).Model(table).Updates(update).Error
}
// GetByID get a record by id
func (d *userExampleDao) GetByID(ctx context.Context, id uint64) (*model.UserExample, error) {
// no cache
if d.cache == nil {
record := &model.UserExample{}
err := d.db.WithContext(ctx).Where("id = ?", id).First(record).Error
return record, err
}
// get from cache or mysql
record, err := d.cache.Get(ctx, id)
if err == nil {
return record, nil
}
if errors.Is(err, model.ErrCacheNotFound) {
// for the same id, prevent high concurrent simultaneous access to mysql
val, err, _ := d.sfg.Do(utils.Uint64ToStr(id), func() (interface{}, error) { //nolint
table := &model.UserExample{}
err = d.db.WithContext(ctx).Where("id = ?", id).First(table).Error
if err != nil {
// if data is empty, set not found cache to prevent cache penetration, default expiration time 10 minutes
if errors.Is(err, model.ErrRecordNotFound) {
err = d.cache.SetCacheWithNotFound(ctx, id)
if err != nil {
return nil, err
}
return nil, model.ErrRecordNotFound
}
return nil, err
}
// set cache
err = d.cache.Set(ctx, id, table, cache.UserExampleExpireTime)
if err != nil {
return nil, fmt.Errorf("cache.Set error: %v, id=%d", err, id)
}
return table, nil
})
if err != nil {
return nil, err
}
table, ok := val.(*model.UserExample)
if !ok {
return nil, model.ErrRecordNotFound
}
return table, nil
} else if errors.Is(err, cacheBase.ErrPlaceholder) {
return nil, model.ErrRecordNotFound
}
// fail fast, if cache error return, don't request to db
return nil, err
}
// GetByCondition get a record by condition
// query conditions:
//
// name: column name
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
// value: column value, if exp=in, multiple values are separated by commas
// logic: logical type, defaults to and when value is null, only &(and), ||(or)
//
// example: find a male aged 20
//
// condition = &query.Conditions{
// Columns: []query.Column{
// {
// Name: "age",
// Value: 20,
// },
// {
// Name: "gender",
// Value: "male",
// },
// }
func (d *userExampleDao) GetByCondition(ctx context.Context, c *query.Conditions) (*model.UserExample, error) {
queryStr, args, err := c.ConvertToGorm()
if err != nil {
return nil, err
}
table := &model.UserExample{}
err = d.db.WithContext(ctx).Where(queryStr, args...).First(table).Error
if err != nil {
return nil, err
}
return table, nil
}
// GetByIDs get records by batch id
func (d *userExampleDao) GetByIDs(ctx context.Context, ids []uint64) (map[uint64]*model.UserExample, error) {
// no cache
if d.cache == nil {
var records []*model.UserExample
err := d.db.WithContext(ctx).Where("id IN (?)", ids).Find(&records).Error
if err != nil {
return nil, err
}
itemMap := make(map[uint64]*model.UserExample)
for _, record := range records {
itemMap[record.ID] = record
}
return itemMap, nil
}
// get form cache or mysql
itemMap, err := d.cache.MultiGet(ctx, ids)
if err != nil {
return nil, err
}
var missedIDs []uint64
for _, id := range ids {
_, ok := itemMap[id]
if !ok {
missedIDs = append(missedIDs, id)
continue
}
}
// get missed data
if len(missedIDs) > 0 {
// find the id of an active placeholder, i.e. an id that does not exist in mysql
var realMissedIDs []uint64
for _, id := range missedIDs {
_, err = d.cache.Get(ctx, id)
if errors.Is(err, cacheBase.ErrPlaceholder) {
continue
}
realMissedIDs = append(realMissedIDs, id)
}
if len(realMissedIDs) > 0 {
var missedData []*model.UserExample
err = d.db.WithContext(ctx).Where("id IN (?)", realMissedIDs).Find(&missedData).Error
if err != nil {
return nil, err
}
if len(missedData) > 0 {
for _, data := range missedData {
itemMap[data.ID] = data
}
err = d.cache.MultiSet(ctx, missedData, cache.UserExampleExpireTime)
if err != nil {
return nil, err
}
} else {
for _, id := range realMissedIDs {
_ = d.cache.SetCacheWithNotFound(ctx, id)
}
}
}
}
return itemMap, nil
}
// GetByLastID get paging records by last id and limit
func (d *userExampleDao) GetByLastID(ctx context.Context, lastID uint64, limit int, sort string) ([]*model.UserExample, error) {
page := query.NewPage(0, limit, sort)
records := []*model.UserExample{}
err := d.db.WithContext(ctx).Order(page.Sort()).Limit(page.Size()).Where("id < ?", lastID).Find(&records).Error
if err != nil {
return nil, err
}
return records, nil
}
// GetByColumns get paging records by column information,
// Note: query performance degrades when table rows are very large because of the use of offset.
//
// params includes paging parameters and query parameters
// paging parameters (required):
//
// page: page number, starting from 0
// size: lines per page
// sort: sort fields, default is id backwards, you can add - sign before the field to indicate reverse order, no - sign to indicate ascending order, multiple fields separated by comma
//
// query parameters (not required):
//
// name: column name
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
// value: column value, if exp=in, multiple values are separated by commas
// logic: logical type, defaults to and when value is null, only &(and), ||(or)
//
// example: search for a male over 20 years of age
//
// params = &query.Params{
// Page: 0,
// Size: 20,
// Columns: []query.Column{
// {
// Name: "age",
// Exp: ">",
// Value: 20,
// },
// {
// Name: "gender",
// Value: "male",
// },
// }
func (d *userExampleDao) GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error) {
queryStr, args, err := params.ConvertToGormConditions()
if err != nil {
return nil, 0, errors.New("query params error: " + err.Error())
}
var total int64
if params.Sort != "ignore count" { // determine if count is required
err = d.db.WithContext(ctx).Model(&model.UserExample{}).Select([]string{"id"}).Where(queryStr, args...).Count(&total).Error
if err != nil {
return nil, 0, err
}
if total == 0 {
return nil, total, nil
}
}
records := []*model.UserExample{}
order, limit, offset := params.ConvertToPage()
err = d.db.WithContext(ctx).Order(order).Limit(limit).Offset(offset).Where(queryStr, args...).Find(&records).Error
if err != nil {
return nil, 0, err
}
return records, total, err
}
// CreateByTx create a record in the database using the provided transaction
func (d *userExampleDao) CreateByTx(ctx context.Context, tx *gorm.DB, table *model.UserExample) (uint64, error) {
err := tx.WithContext(ctx).Create(table).Error
return table.ID, err
}
// DeleteByTx delete a record by id in the database using the provided transaction
func (d *userExampleDao) DeleteByTx(ctx context.Context, tx *gorm.DB, id uint64) error {
update := map[string]interface{}{
"deleted_at": time.Now(),
}
err := tx.WithContext(ctx).Model(&model.UserExample{}).Where("id = ?", id).Updates(update).Error
if err != nil {
return err
}
// delete cache
_ = d.deleteCache(ctx, id)
return nil
}
// UpdateByTx update a record by id in the database using the provided transaction
func (d *userExampleDao) UpdateByTx(ctx context.Context, tx *gorm.DB, table *model.UserExample) error {
err := d.updateDataByID(ctx, tx, table)
// delete cache
_ = d.deleteCache(ctx, table.ID)
return err
}