大家好,我叫謝偉,是一名程序員。
如果你是做后端開發(fā)的,日常工作中,除了熟悉編程語(yǔ)言之外,數(shù)據(jù)庫(kù)怕是最常用的技術(shù)了吧。
比如搭建一個(gè)Web后臺(tái)管理系統(tǒng),你需要數(shù)據(jù)吧,你總不能指望網(wǎng)頁(yè)都是靜態(tài)數(shù)據(jù)吧。需要數(shù)據(jù),那么就要和數(shù)據(jù)庫(kù)打交道。日常開發(fā)中你可能會(huì)使用關(guān)系型數(shù)據(jù)庫(kù),比如 MySQL、PostgresSQL,也可能使用NoSQL型數(shù)據(jù)庫(kù),比如MongoDB,redis等,甚至?xí)褂酶鞣N各樣的符合特定場(chǎng)景下的數(shù)據(jù)庫(kù)。
但我建議,至少需要熟練掌握一門關(guān)系型數(shù)據(jù)庫(kù),日常開發(fā)中你會(huì)發(fā)現(xiàn)絕大多數(shù)的需求的實(shí)現(xiàn)都需要和數(shù)據(jù)庫(kù)打交道。你僅僅只會(huì)簡(jiǎn)單的增刪改查,是不太夠用的。僅僅只會(huì)在編程語(yǔ)言層面編寫簡(jiǎn)單SQL,也是不太夠用。
你需要會(huì):
- 數(shù)據(jù)庫(kù)的設(shè)計(jì):數(shù)據(jù)庫(kù)設(shè)計(jì)三大范式
- 數(shù)據(jù)庫(kù)多表操作
- 數(shù)據(jù)庫(kù)服務(wù)端操作
- 備份恢復(fù)
- 事務(wù)等操作
- 分庫(kù)分表等操作
本節(jié)的主題:gorm 的使用。
大綱:
- 原生database/sql 接口
- 豐富的第三方驅(qū)動(dòng)
- gorm 的使用
1. 原生 database/sql 接口
Go 官方并沒(méi)有提供數(shù)據(jù)庫(kù)驅(qū)動(dòng),只定義了一些標(biāo)準(zhǔn)的接口。所以你會(huì)看看各種各樣的第三方驅(qū)動(dòng)。
本質(zhì)上都在實(shí)現(xiàn)官方提供的標(biāo)準(zhǔn)接口。
sql.Register
作用用來(lái)注冊(cè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)。
所以你使用第三方數(shù)據(jù)庫(kù)驅(qū)動(dòng),最重要的一點(diǎn)就是要導(dǎo)入該庫(kù),實(shí)現(xiàn)第三方數(shù)據(jù)庫(kù)驅(qū)動(dòng)的init 函數(shù)。該init 函數(shù)即是實(shí)現(xiàn)注冊(cè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)。
sqlite3 導(dǎo)入: _ "github.com/mattn/go-sqlite3"
init 函數(shù)
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
}
原生函數(shù)如下:
func Register(name string, driver driver.Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("sql: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("sql: Register called twice for driver " + name)
}
drivers[name] = driver
}
可以看出,核心是driver.Driver接口,定義了一個(gè) open 方法
type Driver interface {
// Open returns a new connection to the database.
// The name is a string in a driver-specific format.
//
// Open may return a cached connection (one previously
// closed), but doing so is unnecessary; the sql package
// maintains a pool of idle connections for efficient re-use.
//
// The returned connection is only used by one goroutine at a
// time.
Open(name string) (Conn, error)
}
Conn 是一個(gè)數(shù)據(jù)庫(kù)連接接口,定義了Prepare、Close、Begin方法
- Prepare:SQL語(yǔ)句準(zhǔn)備階段
- Close: 關(guān)閉連接
- Begin: 事務(wù)處理
type Conn interface {
// Prepare returns a prepared statement, bound to this connection.
Prepare(query string) (Stmt, error)
// Close invalidates and potentially stops any current
// prepared statements and transactions, marking this
// connection as no longer in use.
//
// Because the sql package maintains a free pool of
// connections and only calls Close when there's a surplus of
// idle connections, it shouldn't be necessary for drivers to
// do their own connection caching.
Close() error
// Begin starts and returns a new transaction.
//
// Deprecated: Drivers should implement ConnBeginTx instead (or additionally).
Begin() (Tx, error)
}
Stmt是一種準(zhǔn)備好的狀態(tài),和Conn相關(guān)聯(lián),而且只能應(yīng)用于一個(gè)goroutine中,不能應(yīng)用于多個(gè)goroutine。
type Stmt interface {
// Close closes the statement.
//
// As of Go 1.1, a Stmt will not be closed if it's in use
// by any queries.
Close() error
// NumInput returns the number of placeholder parameters.
//
// If NumInput returns >= 0, the sql package will sanity check
// argument counts from callers and return errors to the caller
// before the statement's Exec or Query methods are called.
//
// NumInput may also return -1, if the driver doesn't know
// its number of placeholders. In that case, the sql package
// will not sanity check Exec or Query argument counts.
NumInput() int
// Exec executes a query that doesn't return rows, such
// as an INSERT or UPDATE.
//
// Deprecated: Drivers should implement StmtExecContext instead (or additionally).
Exec(args []Value) (Result, error)
// Query executes a query that may return rows, such as a
// SELECT.
//
// Deprecated: Drivers should implement StmtQueryContext instead (or additionally).
Query(args []Value) (Rows, error)
}
大概就是定義了一系列的標(biāo)準(zhǔn)接口,接口內(nèi)注意輸入?yún)?shù)和返回值,然后一步步下去,就大概可以知道官方的接口是如何定義的。
官方的這些接口,需要被第三方數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn),不管是sqlite、mysql、PostgresSQL 都需要實(shí)現(xiàn)這些接口,實(shí)際的使用過(guò)程中調(diào)用這些接口即可。
比如:
數(shù)據(jù)庫(kù)表:
id | created_at | updated_at | deleted_at | type_name |
---|---|---|---|---|
1 | "2018-08-11 13:29:45.773658+08" | "2018-08-11 13:29:45.773658+08" | "詩(shī)" | |
2 | "2018-08-11 13:29:45.779012+08" | "2018-08-11 13:29:45.779012+08" | "詞" | |
3 | "2018-08-11 13:29:45.781381+08" | "2018-08-11 13:29:45.781381+08" | "曲" | |
4 | "2018-08-11 13:29:45.784009+08" | "2018-08-11 13:29:45.784009+08" | "文言文" |
如何獲取這4條記錄:
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
func main() {
db, _ := sql.Open("postgres", "host=127.0.0.1 user=xiewei dbname=crawler_info port=5432 sslmode=disable password=admin")
rows, _ := db.Query(`select * from poetry_types limit 4`)
fmt.Println(rows.Columns())
for rows.Next() {
var id int
var createdAt time.Time
var updatedAt time.Time
var deletedAt *time.Time
var typeName string
rows.Scan(&id, &createdAt, &updatedAt, &deletedAt, &typeName)
fmt.Println(id, createdAt, updatedAt, deletedAt, typeName)
}
}
使用這種方法,你只需知道原生database/sql 的接口是如何調(diào)用的即可,具體的實(shí)現(xiàn)由第三方已實(shí)現(xiàn)。
知道數(shù)據(jù)庫(kù)(crawler_info), 知道數(shù)據(jù)庫(kù)表(poetry_type),知道數(shù)據(jù)庫(kù)內(nèi)字段的類型( id int, createdAt time.Time, updatedAt time.Time, deletedAt *time.Time, typeName string)
即可寫SQL 語(yǔ)句操作數(shù)據(jù)庫(kù),實(shí)現(xiàn)增刪改查。
這種接口的定義有什么好處?
不管你操作sqlite、mysql、PostgreSQL 等數(shù)據(jù)庫(kù),只是連接驅(qū)動(dòng)這塊不一致,其他的操作一模一樣。
# postgresql
db, _ := sql.Open("postgres", "host=127.0.0.1 user=xiewei dbname=crawler_info port=5432 sslmode=disable password=admin")
# sqlite
db, _ := sql.Open("sqlite3", "*****")
# mysql
db, _ := sql.Open("mysql", "*****")
2. gorm
使用上述方法的缺點(diǎn)是使你代碼內(nèi)充斥著 SQL 語(yǔ)句。使用 ORM (對(duì)象關(guān)系映射)可以解決這個(gè)問(wèn)題,使我們操作對(duì)象即可達(dá)到操作數(shù)據(jù)庫(kù)的目的。
gorm 的使用步驟:
- 定義model 即對(duì)象層(知道操作的對(duì)象是誰(shuí))
- 建立連接
- 創(chuàng)建數(shù)據(jù)表(數(shù)據(jù)庫(kù)中存在表也可不執(zhí)行該步,定義model 即可,字段變更會(huì)新增字段)
- 操作數(shù)據(jù)庫(kù)
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
// 聲明數(shù)據(jù)庫(kù)表的形式
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
// 建立連接
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("failed to connect database")
}
defer db.Close()
// 生成數(shù)據(jù)庫(kù)表
db.AutoMigrate(&Product{})
// 新增一條記錄:將Product 對(duì)象轉(zhuǎn)換成數(shù)據(jù)庫(kù)內(nèi)一條記錄
db.Create(&Product{Code: "L1212", Price: 1000})
// 獲取對(duì)象:將數(shù)據(jù)庫(kù)內(nèi)一條記錄轉(zhuǎn)換成 product 對(duì)象
var product Product
db.First(&product, 1) // find product with id 1
db.First(&product, "code = ?", "L1212") // find product with code l1212
// 更新記錄
db.Model(&product).Update("Price", 2000)
// 刪除記錄
db.Delete(&product)
}
使用的orm 技術(shù)的關(guān)鍵在于理解,對(duì)象和數(shù)據(jù)庫(kù)表的映射。
1. 要操作數(shù)據(jù)庫(kù)內(nèi)已存在的數(shù)據(jù)表怎么使用 orm ?
- 定義和數(shù)據(jù)庫(kù)內(nèi)對(duì)應(yīng)的數(shù)據(jù)表的model
數(shù)據(jù)內(nèi)存在這么一張表(dynasties):
id created_at updated_at deleted_at name
1 2018-08-11 05:29:45.647432 2018-08-11 05:29:45.647432 先秦
2 2018-08-11 05:29:45.660827 2018-08-11 05:29:45.660827 兩漢
3 2018-08-11 05:29:45.664372 2018-08-11 05:29:45.664372 魏晉
4 2018-08-11 05:29:45.668520 2018-08-11 05:29:45.668520 南北朝
5 2018-08-11 05:29:45.672059 2018-08-11 05:29:45.672059 隋代
6 2018-08-11 05:29:45.675465 2018-08-11 05:29:45.675465 唐代
7 2018-08-11 05:29:45.678486 2018-08-11 05:29:45.678486 五代
8 2018-08-11 05:29:45.680459 2018-08-11 05:29:45.680459 宋代
9 2018-08-11 05:29:45.682840 2018-08-11 05:29:45.682840 金朝
10 2018-08-11 05:29:45.684378 2018-08-11 05:29:45.684378 元代
11 2018-08-11 05:29:45.686548 2018-08-11 05:29:45.686548 明代
12 2018-08-11 05:29:45.688638 2018-08-11 05:29:45.688638 清代
13 2018-08-11 12:02:59.433187 2018-08-11 12:02:59.433187 近代
14 2018-08-11 12:29:02.976485 2018-08-11 12:29:02.976485 現(xiàn)代
15 2018-08-11 12:29:52.015595 2018-08-11 12:29:52.015595 未知
則定義 model 對(duì)象 :
type Dynasty struct {
gorm.Model
Name string `gorm:"tye:varchar" json:"name"`
}
gorm.model gorm 內(nèi)置的 格式如下:
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
- 默認(rèn)取定義的結(jié)構(gòu)體的小寫的復(fù)數(shù)形式為數(shù)據(jù)庫(kù)表名(Dynasty/dynasties)
建立連接即可操作:
db, err := gorm.open("postgres", "*****")
- 獲取記錄:
newDynastyRecord 即獲取到id = 1 的一條記錄。該條記錄的值填充在newDynastyRecord內(nèi)。
比如獲取該條記錄的name: 即 newDynastyRecord.Name
var newDynastyRecord Dynasty
db.Where("id = ?", 1).First(&newDynastyRecord)
fmt.Println(newDynastyRecord.Name)
var newDynastyRecordList []Dynasty
db.Find(&newDynastyRecordList)
fmt.Println(len(newDynastyRecordList)) // 15
- Where 限定條件,和 sql 語(yǔ)句中的 Where 用法差不多
- First 即獲取滿足條件的第一條記錄
- Find 可以獲取滿足條件的所有記錄
2. 數(shù)據(jù)庫(kù)內(nèi)不存在數(shù)據(jù)庫(kù)表怎么操作
- 定義 model 對(duì)象
type PoetryType struct {
gorm.Model
TypeName string `gorm:"type:varchar" json:"type_name"`
}
不存在的表,首先需要?jiǎng)?chuàng)建表:db.AutoMigrate(&PoetryType)
創(chuàng)建之后操作:
即把新增的記錄新增入poetry_types 表內(nèi)
var poetryType PoetryType
poetryType = PoetryType{
Typaname: "詩(shī)"
}
db.create(&poetryType)
核心只有一條,在 gorm 內(nèi)操作對(duì)象(struct),以達(dá)到操作數(shù)據(jù)表的目的。有對(duì)象,沒(méi)數(shù)據(jù)庫(kù)表,操作失敗;沒(méi)對(duì)象,有數(shù)據(jù)庫(kù)表,操作失敗。找不到對(duì)應(yīng)關(guān)系,操作失敗; 對(duì)應(yīng)關(guān)系搞錯(cuò),操作失敗。
3. 實(shí)例
我這邊利用爬蟲技術(shù),把一系列的詩(shī)人的基本信息,詩(shī)人的詩(shī)文,朝代和詩(shī)的類型入庫(kù)了。
大概的樣子如下:
dynasties 表:朝代
1 2018-08-11 05:29:45.647432 2018-08-11 05:29:45.647432 先秦
2 2018-08-11 05:29:45.660827 2018-08-11 05:29:45.660827 兩漢
3 2018-08-11 05:29:45.664372 2018-08-11 05:29:45.664372 魏晉
poetry_types 表:詩(shī)類型
1 2018-08-11 05:29:45.773658 2018-08-11 05:29:45.773658 詩(shī)
2 2018-08-11 05:29:45.779012 2018-08-11 05:29:45.779012 詞
3 2018-08-11 05:29:45.781381 2018-08-11 05:29:45.781381 曲
poetry_infos 表:詩(shī)文的具體內(nèi)容
1 2018-08-11 13:39:37.341955 2018-08-11 13:39:37.341955 將進(jìn)酒 君不見(jiàn),黃河之水天上來(lái),奔流到海不復(fù)回。君不見(jiàn),高堂明鏡悲白發(fā),朝如青絲暮成雪。人生得意須盡歡,莫使金樽空對(duì)月。天生我材必有用,千金散盡還復(fù)來(lái)。烹羊宰牛且為樂(lè),會(huì)須一飲三百杯。岑夫子,丹丘生,將進(jìn)酒,杯莫停。與君歌一曲,請(qǐng)君為我傾耳聽。(傾耳聽 一作:側(cè)耳聽)鐘鼓饌玉不足貴,但愿長(zhǎng)醉不復(fù)醒。(不足貴 一作:何足貴;不復(fù)醒 一作:不愿醒/不用醒)古來(lái)圣賢皆寂寞,惟有飲者留其名。(古來(lái) 一作:自古;惟 通:唯)陳王昔時(shí)宴平樂(lè),斗酒十千恣歡謔。主人何為言少錢,徑須沽取對(duì)君酌。五花馬,千金裘,呼兒將出換美酒,與爾同銷萬(wàn)古愁。 34290 1 6
2 2018-08-11 13:39:37.414925 2018-08-11 13:39:37.414925 水調(diào)歌頭·明月幾時(shí)有 "丙辰中秋,歡飲達(dá)旦,大醉,作此篇,兼懷子由。
明月幾時(shí)有?把酒問(wèn)青天。不知天上宮闕,今夕是何年。我欲乘風(fēng)歸去,又恐瓊樓玉宇,高處不勝寒。起舞弄清影,何似在人間?(何似 一作:何時(shí);又恐 一作:惟 / 唯恐)轉(zhuǎn)朱閣,低綺戶,照無(wú)眠。不應(yīng)有恨,何事長(zhǎng)向別時(shí)圓?人有悲歡離合,月有陰晴圓缺,此事古難全。但愿人長(zhǎng)久,千里共嬋娟。(長(zhǎng)向 一作:偏向)" 33531 2 8
poets 表:詩(shī)人信息
1 2018-08-11 13:39:37.334029 2018-08-11 13:39:37.334029 李白 https://img.gushiwen.org/authorImg/libai.jpg 1310 6169 李白(701年-762年),字太白,號(hào)青蓮居士,唐朝浪漫主義詩(shī)人,被后人譽(yù)為“詩(shī)仙”。祖籍隴西成紀(jì)(待考),出生于西域碎葉城,4歲再隨父遷至劍南道綿州。李白存世詩(shī)文千余篇,有《李太白集》傳世。762年病逝,享年61歲。其墓在今安徽當(dāng)涂,四川江油、湖北安陸有紀(jì)念館。 1310篇詩(shī)文 6
2 2018-08-11 13:39:37.412562 2018-08-11 13:39:37.412562 蘇軾 https://img.gushiwen.org/authorImg/sushi.jpg 3637 4024 蘇軾(1037-1101),北宋文學(xué)家、書畫家、美食家。字子瞻,號(hào)東坡居士。漢族,四川人,葬于潁昌(今河南省平頂山市郟縣)。一生仕途坎坷,學(xué)識(shí)淵博,天資極高,詩(shī)文書畫皆精。其文汪洋恣肆,明白暢達(dá),與歐陽(yáng)修并稱歐蘇,為“唐宋八大家”之一;詩(shī)清新豪健,善用夸張、比喻,藝術(shù)表現(xiàn)獨(dú)具風(fēng)格,與黃庭堅(jiān)并稱蘇黃;詞開豪放一派,對(duì)后世有巨大影響,與辛棄疾并稱蘇辛;書法擅長(zhǎng)行書、楷書,能自創(chuàng)新意,用筆豐腴跌宕,有天真爛漫之趣,與黃庭堅(jiān)、米芾、蔡襄并稱宋四家;畫學(xué)文同,論畫主張神似,提倡“士人畫”。著有《蘇東坡全集》和《東坡樂(lè)府》等。 3637篇詩(shī)文 8
3 2018-08-11 13:39:37.495146 2018-08-11 13:39:37.495146 陶淵明 https://img.gushiwen.org/authorImg/taoyuanming.jpg 186 1466 陶淵明(約365年—427年),字元亮,(又一說(shuō)名潛,字淵明)號(hào)五柳先生,私謚“靖節(jié)”,東晉末期南朝宋初期詩(shī)人、文學(xué)家、辭賦家、散文家。漢族,東晉潯陽(yáng)柴桑人(今江西九江)。曾做過(guò)幾年小官,后辭官回家,從此隱居,田園生活是陶淵明詩(shī)的主要題材,相關(guān)作品有《飲酒》、《歸園田居》、《桃花源記》、《五柳先生傳》、《歸去來(lái)兮辭》等。 186篇詩(shī)文 3
基于上述的信息,構(gòu)建如下API 服務(wù):
核心步驟如下:
- 建立 gin api server
- 根據(jù)上文的數(shù)據(jù)庫(kù)表定義對(duì)象 model
- gin 路由和控制器的編寫
api server
func GinInit() {
r := gin.New()
gin.SetMode(gin.DebugMode)
r.Use(gin.Logger())
//r.Use(gin.Recovery())
v1 := r.Group("/v1/api")
{
poet.Register(v1) // 所有的路由和控制器
}
getAllApi(r)
r.Run(":8080")
}
路由和控制器
package poet
import "github.com/gin-gonic/gin"
func Register(r *gin.RouterGroup) {
r.GET("/poet/:id", ShowPoetHandler)
r.GET("/poetry/:id", ShowPoetryHandler)
r.GET("/dynasty/:id", ShowDynastyHandler)
r.GET("/poetryType/:id", ShowPoetTypeHandler)
r.GET("/gushiwen/poet", ShowListPoetHandler)
r.GET("/gushiwen/poetry", ShowListPoetryHandler)
r.GET("/gushiwen/dynasty", ShowListDynastyHandler)
r.GET("/gushiwen/poetryType", ShowListPoetTypeHandler)
}
model
package model
import "github.com/jinzhu/gorm"
// 朝代的表: 先秦、兩漢、魏晉、南北朝、隋代、唐代、五代、宋代、金朝、元代、明代、清代...
type Dynasty struct {
gorm.Model
Name string `gorm:"tye:varchar" json:"name"`
}
// 詩(shī)人的表
type Poet struct {
gorm.Model
Name string `gorm:"type:varchar" json:"name"`
ImageURL string `gorm:"type:varchar" json:"image_url"`
Number uint `gorm:"type:integer" json:"number"`
Liked uint `gorm:"type:integer" json:"liked"`
Description string `gorm:"type:varchar" json:"description"`
DynastyID uint `gorm:"type:integer" json:"dynasty_id"`
PoetryInfo []PoetryInfo
}
// 詩(shī)類型的表: 詩(shī)、詞、曲、文言文
type PoetryType struct {
gorm.Model
TypeName string `gorm:"type:varchar" json:"type_name"`
}
// 詩(shī)文的表
type PoetryInfo struct {
gorm.Model
Title string `gorm:"type:varchar" json:"title"`
Content string `gorm:"type:varchar" json:"content"`
Liked uint `gorm:"type:integer;default(0)" json:"liked"`
PoetID uint `gorm:"type:integer" json:"poet_id"`
DynastyID uint `gorm:"type:integer" json:"dynasty_id"`
}
路由 1 和 控制器 1
r.GET("/poet/:id", ShowPoetHandler)
func NotFound() (int, map[string]interface{}) {
return http.StatusBadGateway, gin.H{"Message": "not found record"}
}
func ShowPoetHandler(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var poet model.Poet
if dbError := initial.DataBase.Where("id = ?", id).First(&poet).Error; dbError != nil {
c.JSON(NotFound())
return
}
c.JSON(http.StatusOK, poet)
}
路由 2 和控制 2
r.GET("/gushiwen/poet", ShowListPoetHandler)
type ListPoetParams struct {
CommonPagination
Search string `form:"search" json:"search"`
Return string `form:"return" json:"return" binding:"omitempty,eq=all_list|eq=all_count"`
PoetNumber uint `form:"number" json:"number"`
Like uint `form:"like" json:"like"`
Dynasty string `form:"dynasty" json:"dynasty"`
}
func ShowListPoetHandler(c *gin.Context) {
var param ListPoetParams
if err := c.ShouldBind(¶m); err != nil {
fmt.Println("error", err)
return
}
offset := param.PerPage * (param.Page - 1)
order := param.OrderBy + " " + param.SortBy
var poet []model.Poet
query := initial.DataBase.Preload("PoetryInfo").Model(&poet)
if param.Return != "all_list" {
query = query.Offset(offset).Limit(param.PerPage)
}
if param.Search != "" {
query = query.Where("name = ?", param.Search)
}
if param.PoetNumber != 0 {
query = query.Where("number < ?", param.PoetNumber)
}
if param.Like != 0 {
query = query.Where("liked < ?", param.Like)
}
if param.Dynasty != "" {
var dynasty model.Dynasty
if dbError := initial.DataBase.Where("name = ?", param.Dynasty).First(&dynasty).Error; dbError != nil {
c.JSON(NotFound())
return
}
query = query.Where("dynasty_id = ?", dynasty.ID)
}
query.Order(order).Find(&poet)
c.JSON(http.StatusOK, poet)
}
效果演示:
實(shí)際操作的SQL 語(yǔ)句:
參考代碼: 參考代碼
- 使用的PostgreSQL 數(shù)據(jù)庫(kù),確保本機(jī)有服務(wù)啟動(dòng)
- 先db 下的壓縮包使用 pg_restore 命令導(dǎo)入數(shù)據(jù)庫(kù)
- 修改 configs/setting.yml 數(shù)據(jù)庫(kù)配置
- make install 安裝依賴庫(kù)
- make dev 啟動(dòng)服務(wù)
全文完,再會(huì)。
多看文檔