go web开发(gin&gorm) 之DB配置及DAO的基本使用

转载请注明出处: https://www.cnblogs.com/funnyzpc/p/9501376.html

“`

   我先闲扯下,前天(也就是2018年11月16号)的某个时候,忽然有人在QQ上私聊我,一看是公司群以为是有人来慰问新人了,也没弄清楚身份就调侃起来,就这样:

问题是:我竟傻乎乎滴没看出来是行政那边的人,中午吃饭的时候和老同事聊起此事,才知道这位大锅是人事部boss,一时间感觉事情变得搞笑起来,当然,有意思的还不止这一件,就在两周前入职的时候,当时是复试,行政总监把车开到我之前公司楼下接我,出发到现场前给我买了杯咖啡,我说美式中杯就好了,这人说怎么也得大杯,面试过了后,到晚上,这人又发朋友圈说他兴奋的狠。。。

说实话,二当家也真够zuo的。。。😅,当然这伙计在我第一面的时候就闲聊了一个多小时,还不止,他竟然知道我小名😓

“`

  闲聊到这儿,现在就进入本次的主题:golang web开发之Dao配置

  在正式进入主题前,先说说框架的现状,个人用的是gin-gonic框架,这是个在校大学生写的基于go语言的高性能web框架,在此之前我对比过beego 、 iris 、gin-gonic这几个在维护频度和依赖支持以及star热度方面,个人选择了gin-gonic这个框架 ,同时也在github上选用了一套比较前卫的成型的框架代码,东西十分的好,但是个人觉得框架集成的mysql实在是看不下去(主要是性能低了+ 稳定性不够好+升级麻烦),遂就将数据库换成postgresql,配置完成就开始测试Dao,需要说的是其中gorm是位台湾胸弟写的ORM框架,于是开始~

  且先不管现有的mysql的配置,由于框架本身只集成了mysql,所以现在需要安装一个pg的连接driver,放到指定的目录就装好依赖了,至于怎么安装,大致有二。

  A>其一是使用go命令直接安装

1 go get -u github.com/lib/pq

  B>其二是跟我一样keng地手动安装,就是找到github.com的源码页面,将整个项目以一个zip包下载下来,而后解压到指定目录

需要注意的是手动安装一定要将github.com后面的路径改成以目录为结构的包地址

  连接组件安装完毕开始写一个db.go的数据库初始化类和一个参数结构体,这里我给出源码:

参数结构体:

  1 package config
  2 
  3 import (
  4     "encoding/json"
  5     "fmt"
  6     "io/ioutil"
  7     "os"
  8     "regexp"
  9     "strings"
 10     "unicode/utf8"
 11 
 12     "github.com/shen100/golang123/utils"
 13 )
 14 
 15 var jsonData map[string]interface{}
 16 
 17 func initJSON() {
 18     bytes, err := ioutil.ReadFile("./config.json")
 19     if err != nil {
 20         fmt.Println("ReadFile: ", err.Error())
 21         os.Exit(-1)
 22     }
 23 
 24     configStr := string(bytes[:])
 25     reg := regexp.MustCompile(`/\*.*\*/`)
 26 
 27     configStr = reg.ReplaceAllString(configStr, "")
 28     bytes = []byte(configStr)
 29 
 30     if err := json.Unmarshal(bytes, &jsonData); err != nil {
 31         fmt.Println("invalid config: ", err.Error())
 32         os.Exit(-1)
 33     }
 34 }
 35 
 36 type dBConfig struct {
 37     Dialect      string
 38     Database     string
 39     User         string
 40     Password     string
 41     Host         string
 42     Port         int
 43     Charset      string
 44     URL          string
 45     MaxIdleConns int
 46     MaxOpenConns int
 47     ConnMaxLifetime int64
 48     Sslmode         string
 49 }
 50 
 51 // DBConfig 数据库相关配置
 52 var DBConfig dBConfig
 53 
 54 func initDB() {
 55     utils.SetStructByJSON(&DBConfig, jsonData["database"].(map[string]interface{}))
 56     /*
 57         mysql数据库的连接方式
 58     url := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local",
 59         DBConfig.User, DBConfig.Password, DBConfig.Host, DBConfig.Port, DBConfig.Database, DBConfig.Charset)
 60     */
 61     /**
 62         更改mysql数据库为postgresql
 63         具体连接方式为>
 64             host=myhost port=myport user=gorm dbname=gorm password=mypassword
 65      */
 66     url := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=%s",
 67         DBConfig.Host,
 68         DBConfig.Port,
 69         DBConfig.User,
 70         DBConfig.Database,
 71         DBConfig.Password,
 72         DBConfig.Sslmode)
 73 
 74     DBConfig.URL = url
 75 }
 76 
 77 type serverConfig struct {
 78     APIPoweredBy       string
 79     SiteName           string
 80     Host               string
 81     ImgHost            string
 82     Env                string
 83     LogDir             string
 84     LogFile            string
 85     APIPrefix          string
 86     UploadImgDir       string
 87     ImgPath            string
 88     MaxMultipartMemory int
 89     Port               int
 90     StatsEnabled       bool
 91     TokenSecret        string
 92     TokenMaxAge        int
 93     PassSalt           string
 94     LuosimaoVerifyURL  string
 95     LuosimaoAPIKey     string
 96     CrawlerName        string
 97     MailUser           string //域名邮箱账号
 98     MailPass           string //域名邮箱密码
 99     MailHost           string //smtp邮箱域名
100     MailPort           int    //smtp邮箱端口
101     MailFrom           string //邮件来源
102     Github             string
103     BaiduPushLink      string
104 }
105 
106 
107 func init() {
108     initJSON()
109     initDB()
110 }

连接地址一定要根据所使用的orm框架来拼接相应的连接地址才对,这算是一个,下面这个是gorm的官方文档以作参考

http://doc.gorm.io/database.html#connecting-to-a-database

db.go初始化

 1 package model
 2 
 3 import (
 4     "fmt"
 5     "os"
 6     "time"
 7 
 8     "github.com/garyburd/redigo/redis"
 9     "github.com/globalsign/mgo"
10     "github.com/jinzhu/gorm"
11     _ "github.com/jinzhu/gorm/dialects/postgres"
12     "github.com/shen100/golang123/config"
13 )
14 
15 // DB 数据库连接
16 var DB *gorm.DB
17 var ERR error
18 
19 
20 func initDB() {
21     DB, ERR = gorm.Open(config.DBConfig.Dialect, config.DBConfig.URL)
22     if ERR != nil {
23         fmt.Println(ERR.Error())
24         os.Exit(-1)
25     }
26     if config.ServerConfig.Env == DevelopmentMode {
27         DB.LogMode(true)
28     }
29     DB.DB().SetMaxIdleConns(config.DBConfig.MaxIdleConns)
30     DB.DB().SetMaxOpenConns(config.DBConfig.MaxOpenConns)
31 
32     /**
33         禁用表名复数>
34         !!!如不禁用则会出现表 y结尾边ies的问题
35         !!!如果只是部分表需要使用源表名,请在实体类中声明TableName的构造函数
36     ```
37         func (实体名) TableName() string {
38             return "数据库表名"
39         }
40     ```
41      */
42     DB.SingularTable(true)
43     //db.DB().SetConnMaxLifetime(config.DBConfig.ConnMaxLifetime)
44 }
45 
46 
47 func init() {
48     initDB()
49 }

这里的初始化就是调用 gorm.Open 方法来打开db的连接,连接正常打开后设置连接池(空闲连接数、最大连接数),到这儿基本就完成了,不过,需要注意到的是:gorm默认的结构体映射是复数形式,比如你的博客表为blog,对应的结构体名就会是blogs,同时若表名为多个单词,对应的model结构体名字必须是驼峰式,首字母也必须大写,可能不太理解gorm的命名方式,个人也是被这个逻辑给折腾的不轻,查官方资料才知道需要配置一个参数,以实现结构体名为非复数形式:DB.SingularTable(true); 默认不设置的时候就是false;这是一坑。

  好了,结构体设置完成就需要在mian.go(启动类)中引入这两个文件所在的package (包);像这样

因为个人在启动方法中使用到这两个包的相关方法,所以是正常引入,若是当前文件内没有使用到,请在包的引号前加一个 “_” ,以表示自动调用相关包内的init方法(因为在main中使用过,故也会自动调用包内的init方法)。

  db的基本配置已经完成了,启动main.go若无报错,则配置成功~

  配置完成得测试下,Dao的调用,以及在结构体内配置相关映射参数,以及实现主键自增(很重要,里面有)。

  这里本人用的是本人已经写完的一个业务来测试,简要的介绍下gorm的配置参数以及Dao的调用方式方法~

   通过对象的方式操作数据表时,必须要有个model的结构体和数据库表结构,这里我给一个结构体的go代码和表结构的截图

结构体:

package model

import "time"

// Article 文章
type Article struct {
    ID            uint       `gorm:"primary_key" sql:"auto_increment;primary_key;unique" json:"id"`
    CreatedAt     time.Time  `json:"createdAt"`
    UpdatedAt     time.Time  `json:"updatedAt"`
    DeletedAt     *time.Time `sql:"index" json:"deletedAt"`
    Name          string     `json:"name"`
    BrowseCount   uint       `json:"browseCount"`
    CommentCount  uint       `json:"commentCount"`
    CollectCount  uint       `json:"collectCount"`
    Status        int        `json:"status"`
    Content       string     `json:"content"`
    HTMLContent   string     `json:"htmlContent"`
    ContentType   int        `json:"contentType"`
    Categories    []Category `gorm:"many2many:article_category;ForeignKey:ID;AssociationForeignKey:ID" json:"categories"`
    Comments      []Comment  `gorm:"ForeignKey:SourceID" json:"comments"`
    UserID        uint       `json:"userID"`
    User          User       `json:"user"`
    LastUserID    uint       `json:"lastUserID"` //最后一个回复话题的人
    LastUser      User       `json:"lastUser"`
    LastCommentAt *time.Time `json:"lastCommentAt"`
}

数据库表结构

由于postgresql的特殊性,在构建表的时候主键ID必须是serial类型才会在结构保存的时候生成一个主键自增的触发器,主键在表结构保存后就是int类型,这是一坑(当然也只有在postgresql中存在),不论用的是oracleDB还是mySqlDB亦或是PostgreSQLDB,实现主键自增都需要(至少)设置一个主键。

  再就是表结构对应的代码结构体(Model类或实体类),配置的时候一定要注意,一定要定义字段参数标签,标签就目前用到的一共有三类:

  gorm标签:gorm构造标签,这里面可以定义字段类型、主键、长度、关联关系等等,这个定义一定要有的,若字段存在多个属性需要以key:value的形式给出,整个标签属性均在英文双引号内;目前官方给出的标签类型可以有以下几种

  sql标签:很奇怪的是这个标签在官方gorm里面并没有提到,就个人来看这个标签可能是数据库driver提供的,就目前用到的就只有以下几个(自增、主键、唯一),若有多个属性的时候请以分号隔开

sql:"auto_increment;primary_key;unique"

PostgreSQL的用户需要特别注意的是:若要使用数据库的主键自增,请务必声明以上几个属性,否则数据插入一定会报错!这又是一。。。

  JSON序列化标签: 其实,这个标签跟ORM半毛钱关系也没有,这里只是提一下(因为很有用),这个标签在对象打印或者输出到请求端的时候可以将model的字段以别名的形式输出,若使用默认序列化的方式将字段输出则所有的地段都是大写开头,所以说十分有用~,在结构体(model)里大概这么定义

BrowseCount   uint       `json:"browseCount"`

  现在就尝试做一个保存操作,我的代码代码

        saveErr = model.DB.Create(&article).Error if saveErr == nil { if userErr := model.DB.Model(&user).Update(map[string]interface{}{ "article_count": user.ArticleCount, "score": user.Score, }).Error; userErr != nil { fmt.Println(userErr.Error()) } }

由于我的DB操作都是定义在db的配置文件里面的一个变量

var DB *gorm.DB

所以使用的时候直接看Create方法即可(注意,保存对象一定要提前定义,使用指针的方式将对象保存)。

保存成功日志

[2018-11-24 22:02:03]  [5.87ms]  INSERT INTO "article" ("created_at","updated_at","deleted_at","name","browse_count","comment_count","collect_count","status","content","html_content","content_type","user_id","last_user_id","last_comment_at") VALUES ('2018-11-24T22:02:03+08:00','2018-11-24T22:02:03+08:00','<nil>','怎能不说呢','0','0','0','1','欸~','','1','1','0','<nil>') RETURNING "article"."id"

由于go的特性,所有为空(null)字段均在记录操作的时候以<nil>代替,介意的话可以将字段设置一个默认值,或者给表字段添加一个默认值。

  在此,gorm的配置已经完成,接下来所有dao的操作均使用gorm提供Delete、Update、Insert、select等方法来实现,具体请参见官方文档(好像有中文版):

      http://doc.gorm.io/

  虽然,大多数dao操作都可以通过gorm提供的api来实现,但也存在些不便的地方,主要在以下几点:

  >事务:事务是比较麻烦的一个地方,若确实需要用到事务请在第一个dao操作前调用gorm的Begin()方法,在最后一个dao操作成功后调用Commit()方法,若保存出现异常,需要在每个dao操作后做下判断,若失败使用Rollback()做回退处理,

  >级联查询: 虽然官方的gorm提供级联的方式,但在gorm标签定义外键类型后并没任何用,这里给出的建议(比如一对多)是:在外层查询完成后循环记录,使用连接字段查询出关联记录才可,

  >复杂查询:复杂查询需要手动写sql(),由于gorm并没有提供任何sql模板(类似于java 的 mybatis),遂,需要在代码中手动做动态sql处理,个人建议是用大括号做模板变量,各个例子哈~

    var sql = `SELECT                    
            b.id,b.cid,b.name,b.browse_count,b.comment_count,
            b.collect_count,b.created_at,b.created_by,b.updated_at, b.last_comment_at,b.last_comment_by from blog as b, blog_category as bc, blog_top as t WHERE b.cid
=bc.id and b.id=t.blog_id and b.status=1 {filterByCid} ORDER BY b.created_at desc , b.updated_at desc {filterLimit}` /* 这里当分类为所有时>取最近20条博客记录 当分类为指定分类时>取指定分类下所有博客记录 */ if 0== cId { sql = strings.Replace(sql, "{filterByCid}", "", -1) sql = strings.Replace(sql, "{filterLimit}", "limit 20", -1) }else{ sql = strings.Replace(sql, "{filterByCid}", "and b.cid = "+strconv.Itoa(cId), -1) sql = strings.Replace(sql, "{filterLimit}", "", -1) }

具体的调用方式是(一下代码中的红色部分):

    if err := model.DB.Raw(sql).Scan(&blogs).Error; err != nil {
        SendErrJSON("error", c)
        return
    }

  >分页:gorm提供了Limit和Offset 这两个方法来配合分页操作,但,这里需要说的是,在连表查询(复杂查询)下必须手动使用limit offset or rownum来分页(),是不是很原始~

  ok,本篇就到这里就结束了,内容如有疏漏,请参阅以下文档:

  gorm文档:

    http://gorm.io/docs/

    http://doc.gorm.io

  gin-gonic文档:

    https://github.com/gin-gonic/gin

    https://github.com/shen100/golang123

现在是:2018-11-24 23:36:28 ,各位晚安哈~