gcache是gorm的中间件,插入后gorm即刻拥有缓存。

gcache

License Go Report Card Build Status GoDoc

gcache是gorm的中间件,插入后gorm即刻拥有缓存。

Overview

  • 即插即用
  • 旁路缓存
  • 数据源使用 Redis
  • 防击穿
  • 防穿透

安装

$ go get github.com/8treenet/gcache

快速使用

import (
    "github.com/8treenet/gcache"
    "github.com/jinzhu/gorm"
    "github.com/8treenet/gcache/option""
)

func init() {
    //创建 gorm.DB
    db, _ = gorm.Open("mysql", "")

    opt := option.DefaultOption{}
    opt.Expires = 300                //缓存时间, 默认120秒。范围30-43200
    opt.Level = option.LevelSearch   //缓存级别,默认LevelSearch。LevelDisable:关闭缓存,LevelModel:模型缓存, LevelSearch:查询缓存
    opt.AsyncWrite = false           //异步缓存更新, 默认false。 insert update delete 成功后是否异步更新缓存。 ps: affected如果未0,不触发更新。
    opt.PenetrationSafe = false 	 //开启防穿透, 默认false。 ps:防击穿强制全局开启。
    
    //缓存中间件附加到gorm.DB
    gcache.AttachDB(db, &opt, &option.RedisOption{Addr:"localhost:6379"})
}

约定

  • 模型必须指明主键 gorm:"primary_key"
  • 不支持 Group
  • 不支持 Having
  • 查询条件和查询参数分离

Example

    #查看 example_test.go 了解更多。
    more src/github.com/8treenet/gcache/example/example_test.go
Owner
8treenet
Code writing robot.
8treenet
Comments
  • Preload 不能正常地去存储层获取数据

    Preload 不能正常地去存储层获取数据

    如下代码,TestPreloadSkipCache能通过, TestPreload会失败:

    type TestRole struct {
    	gorm.Model
    	Name string
    }
    
    type TestUser struct {
    	gorm.Model
    	UserName string `gorm:"size:32"`
    	Password string `gorm:"size:32"`
    	Age      int
    	Status   int
    	Roles    []TestRole `gorm:"many2many:test_user_roles;"`
    }
    
    func init() {
    	var e error
    	addr := "user=lab password=lab dbname=lab port=5432 sslmode=disable"
    	db, e = gorm.Open("postgres", addr)
    	if e != nil {
    		panic(e)
    	}
    	db.AutoMigrate(&TestRole{})
    	db.AutoMigrate(&TestUser{})
    	db.AutoMigrate(&TestEmail{})
    
    	opt := gcache.DefaultOption{}
    	opt.Expires = 300              //缓存时间,默认60秒。范围 30-900
    	opt.Level = gcache.LevelSearch //缓存级别,默认LevelSearch。LevelDisable:关闭缓存,LevelModel:模型缓存, LevelSearch:查询缓存
    	opt.AsyncWrite = false         //异步缓存更新, 默认false。 insert update delete 成功后是否异步更新缓存
    	opt.PenetrationSafe = false    //开启防穿透, 默认false。
    
    	//缓存中间件 注入到Gorm
    	cachePlugin = gcache.AttachDB(db, &opt, &gcache.RedisOption{Addr: "localhost:6379"})
    
    	InitData()
    	//开启Debug,查看日志
    	db.LogMode(true)
    	cachePlugin.Debug()
    }
    
    func InitData() {
    	cachePlugin.FlushDB()
    	db.Exec("truncate test_user_roles")
    	db.Exec("truncate test_roles")
    	db.Exec("truncate test_users")
    	db.Exec("truncate test_emails")
    
    	roleAdmin := &TestRole{
    		Name: "ADMIN",
    	}
    	db.Save(roleAdmin)
    
    	roleUser := &TestRole{
    		Name: "USER",
    	}
    	db.Save(roleUser)
    
    	for index := 1; index < 21; index++ {
    		user := &TestUser{}
    		user.UserName = fmt.Sprintf("%s_%d", "name", index)
    		user.Password = fmt.Sprintf("%s_%d", "password", index)
    		user.Age = 20 + index
    		user.Status = rand.Intn(3)
    		user.Roles = []TestRole{*roleUser, *roleAdmin}
    		db.Save(user)
    
    		email := &TestEmail{}
    		email.TypeID = index
    		email.TestUserID = index
    		db.Save(email)
    	}
    }
    
    func TestPreloadSkipCache(t *testing.T) {
    	var tc TestUser
    	db.First(&tc, "user_name = ?", "name_1")
    	assert.Equal(t, "name_1", tc.UserName)
    	assert.Equal(t, 0, len(tc.Roles))
    
    	cachePlugin.SkipCache().Preload("Roles").Find(&tc)
    	assert.Equal(t, 2, len(tc.Roles))
    }
    
    func TestPreload(t *testing.T) {
    	var tc TestUser
    	db.First(&tc, "user_name = ?", "name_1")
    	assert.Equal(t, "name_1", tc.UserName)
    	assert.Equal(t, 0, len(tc.Roles))
    
    	db.Preload("Roles").Find(&tc)
    	assert.Equal(t, 2, len(tc.Roles)) // FAIL
    }
    
  • panic: reflect: call of reflect.Value.FieldByName on ptr Value

    panic: reflect: call of reflect.Value.FieldByName on ptr Value

    类似的错误很容易发生,能看出来是什么原因吗:

    panic: reflect: call of reflect.Value.FieldByName on ptr Value
    
    goroutine 67 [running]:
    github.com/jinzhu/gorm.(*Scope).callCallbacks.func1(0xc000113d80)
    	/root/go/pkg/mod/github.com/jinzhu/[email protected]/scope.go:865 +0xb0
    panic(0x13b4ea0, 0xc00063cbc0)
    	/home/jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go-lang-1.13/src/runtime/panic.go:679 +0x1b2
    reflect.flag.mustBe(...)
    	/home/jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go-lang-1.13/src/reflect/value.go:208
    reflect.Value.FieldByName(0x13aad00, 0xc00063cba0, 0x196, 0x121f13c, 0x2, 0xc00063cba0, 0x196, 0xc00060d640)
    	/home/jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go-lang-1.13/src/reflect/value.go:890 +0x1ed
    github.com/8treenet/gcache/internal.(*easyScope).EasyPrimarys(0xc0004ccb00, 0x1616ed8, 0x8, 0xc0000486c0, 0x3c, 0xc00060d630)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/easy_scope.go:497 +0xbcf
    github.com/8treenet/gcache/internal.(*queryHandle).BySearch(0xc0000196f8, 0xc0004ccb00, 0xc00063c080, 0x197, 0x203000, 0x203000, 0x203000)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/handle_query.go:109 +0xf7
    github.com/8treenet/gcache/internal.(*callQuery).bySearch(0xc0003cc340, 0xc0004ccb00, 0x0, 0x0, 0x0, 0x0)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/call_query.go:118 +0xd6
    github.com/8treenet/gcache/internal.(*callQuery).invoke.func1(0x413031, 0xd0, 0x14a1860, 0xfba41cc9035f6801)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/call_query.go:53 +0x75
    github.com/8treenet/gcache/internal.(*Group).doCall(0xc0003cc348, 0xc000491f20, 0xc000040690, 0x4c, 0xc0000198a0)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/singleflight.go:91 +0x2e
    github.com/8treenet/gcache/internal.(*Group).Do(0xc0003cc348, 0xc000040690, 0x4c, 0xc0000198a0, 0xe, 0xc00060d5c8, 0x2, 0xc000040690, 0x4c)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/singleflight.go:61 +0x1ba
    github.com/8treenet/gcache/internal.(*callQuery).invoke(0xc0003cc340, 0xc000113d80)
    	/root/go/pkg/mod/github.com/8treenet/[email protected]/internal/call_query.go:46 +0x1a8
    github.com/jinzhu/gorm.(*Scope).callCallbacks(0xc000113d80, 0xc0003cce20, 0x4, 0x4, 0x0)
    	/root/go/pkg/mod/github.com/jinzhu/[email protected]/scope.go:869 +0x92
    github.com/jinzhu/gorm.(*DB).Find(0xc0003e8270, 0x12caea0, 0xc00063c080, 0xc000633950, 0x3, 0x3, 0xc000633950)
    	/root/go/pkg/mod/github.com/jinzhu/[email protected]/main.go:354 +0x98
    gitlab.com/example/dal/reader.(*Reader).GetProducts(0xc00048f580, 0xc000633950, 0x3, 0x3, 0xc00063c060, 0x2, 0x2, 0x2, 0xc0004d4240)
    	/home/jenkins/workspace/example/dal/reader/product.go:19 +0xb4
    gitlab.com/sensestar/example/payment/controllers.(*PaymentController).GetProducts(0x27c54b0, 0x19ed8e0, 0xc0005006c0, 0xc0004d4240, 0x27c54b0, 0xc0005006c0, 0xc000027b58)
    	/home/jenkins/workspace/example/payment/controllers/payment.go:72 +0x4ee
    gitlab.com/sensestar/example/payment._PaymentService_GetProducts_Handler(0x15ede00, 0x27c54b0, 0x19ed8e0, 0xc0005006c0, 0xc000490f60, 0x0, 0x19ed8e0, 0xc0005006c0, 0xc0004deb10, 0x10)
    	/home/jenkins/workspace/example/payment/payment.pb.go:5483 +0x217
    google.golang.org/grpc.(*Server).processUnaryRPC(0xc000210d00, 0x1a04620, 0xc0000ebe00, 0xc0004cc300, 0xc000089800, 0x2788aa0, 0x0, 0x0, 0x0)
    	/root/go/pkg/mod/google.golang.org/[email protected]/server.go:1082 +0x4fd
    google.golang.org/grpc.(*Server).handleStream(0xc000210d00, 0x1a04620, 0xc0000ebe00, 0xc0004cc300, 0x0)
    	/root/go/pkg/mod/google.golang.org/[email protected]/server.go:1405 +0xd27
    google.golang.org/grpc.(*Server).serveStreams.func1.1(0xc0004de9b0, 0xc000210d00, 0x1a04620, 0xc0000ebe00, 0xc0004cc300)
    	/root/go/pkg/mod/google.golang.org/[email protected]/server.go:746 +0xbb
    created by google.golang.org/grpc.(*Server).serveStreams.func1
    	/root/go/pkg/mod/google.golang.org/[email protected]/server.go:744 +0xa1
    
  • 请问如何将全表数据,一次set到redis缓存中

    请问如何将全表数据,一次set到redis缓存中

    在执行 golang: querys:=db.Table("dsp_campaign_frequercy").Find(&dCF).Scan(&dCF)

    sql : select *from dsp_campaign_frequercy; 发现,并没有将查询出来的数据,插入到redis中,请问如何将全表数据,一次插入到redis中。

  • Delete got panic: reflect.Value.Addr of unaddressable value

    Delete got panic: reflect.Value.Addr of unaddressable value

    测试代码如下:

    func TestAssociation(t *testing.T) {
    	var tc TestUser
    	db.First(&tc, "user_name = ?", "name_1")
    	assert.Equal(t, "name_1", tc.UserName)
    	assert.Equal(t, 0, len(tc.Roles))
    
    	cachePlugin.SkipCache().Preload("Roles").Find(&tc)
    	assert.Equal(t, 2, len(tc.Roles))
    
    	db.Preload("Roles").Find(&tc)
    	assert.Equal(t, 2, len(tc.Roles))
    
    	var adminRole TestRole
    	db.Model(&adminRole).Where("name = ?", "ADMIN").First(&adminRole)
    	assert.Equal(t, "ADMIN", adminRole.Name)
    
    	db.Model(tc).Association("Roles").Delete(adminRole) // panic: reflect.Value.Addr of unaddressable value
    }
    

    这个删除操作会导致 panic: reflect.Value.Addr of unaddressable value,测试了去掉 gcache 是能通过测试的。

    Other code see #16, full code see https://github.com/8treenet/gcache/compare/master...sutra:testAssociation?expand=1

  • 当我下载包到时候报如下错误,我看了下确实缺少这个定义

    当我下载包到时候报如下错误,我看了下确实缺少这个定义

    src\github.com\8treenet\gcache\internal\easy_scope.go:295:10: es.IsCompleteParentheses undefined (type *easyScope has no field or method IsCompleteParentheses)