存储数据手段:
对数据执行CRUD的创建、获取、更新和删除的操作。
使用数组、切片、映射、栈、树、队列及其它任意类型(container,容器)包裹的数据结构。
将硬盘数据缓存内存中,可以提高性能。
map_store.go
package main
import (
"fmt"
)
type Post struct {
Id int
Content string
Author string
}
var (
PostById = make(map[int]*Post)
PostsByAuthor = make(map[string][]*Post)
)
func store(post *Post) {
PostById[post.Id] = post
PostsByAuthor[post.Author] = append(PostsByAuthor[post.Author], post)
}
func main() {
post1 := &Post{Id: 1, Content: "Hello World!", Author: "Sau Sheong"}
post2 := &Post{Id: 2, Content: "Bonjour Monde!", Author: "Pierre"}
post3 := &Post{Id: 3, Content: "Hola Mundo!", Author: "Pedro"}
post4 := &Post{Id: 4, Content: "Greetings Earthlings!", Author: "Sau Sheong"}
store(post1)
store(post2)
store(post3)
store(post4)
fmt.Println(PostById[1])
fmt.Println(PostById[2])
for _, post := range PostsByAuthor["Sau Sheong"] {
fmt.Println(post)
}
for _, post := range PostsByAuthor["Pedro"] {
fmt.Println(post)
}
}
内存数据非持久化,文件存储持久化。
read_write_files.go
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
data := []byte("Hello World!\n")
// write to file and read from file using WriteFile and ReadFile
err := ioutil.WriteFile("data1", data, 0644)
if err != nil {
panic(err)
}
read1, _ := ioutil.ReadFile("data1")
fmt.Print(string(read1))
// write to file and read from file using the File struct
file1, _ := os.Create("data2")
defer file1.Close()
bytes, _ := file1.Write(data)
fmt.Printf("Wrote %d bytes to file\n", bytes)
file2, _ := os.Open("data2")
defer file2.Close()
read2 := make([]byte, len(data)-5)
bytes, _ = file2.Read(read2)
fmt.Printf("Read %d bytes from file\n", bytes)
fmt.Println(string(read2))
read3 := make([]byte, 5+3)
bytes, _ = file2.Read(read3)
fmt.Printf("Read %d bytes from file\n", bytes)
fmt.Println(string(read3))
}
通用的CSV(comma-separated value,逗号分隔值)文本格式。
csv_store.go
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
)
type Post struct {
Id int
Content string
Author string
}
func main() {
// creating a CSV file
csvFile, err := os.Create("posts.csv")
if err != nil {
panic(err)
}
defer csvFile.Close()
allPosts := []Post{
Post{Id: 1, Content: "Hello World!", Author: "Sau Sheong"},
Post{Id: 2, Content: "Bonjour Monde!", Author: "Pierre"},
Post{Id: 3, Content: "Hola Mundo!", Author: "Pedro"},
Post{Id: 4, Content: "Greetings Earthlings!", Author: "Sau Sheong"},
}
writer := csv.NewWriter(csvFile)
for _, post := range allPosts {
line := []string{strconv.Itoa(post.Id), post.Content, post.Author}
err := writer.Write(line)
if err != nil {
panic(err)
}
}
//确保缓冲区数据都写入文件
writer.Flush()
// reading a CSV file
file, err := os.Open("posts.csv")
if err != nil {
panic(err)
}
defer file.Close()
reader := csv.NewReader(file)
//设置负值,record缺少字段时,读取进程不会中断
//正数表示每条记录读取的字段数量,世界读取少于该值会报错
//0,表示第一条记录的字段数量用作FieldsPerRecord值
reader.FieldsPerRecord = -1
record, err := reader.ReadAll()
if err != nil {
panic(err)
}
var posts []Post
for _, item := range record {
id, _ := strconv.ParseInt(item[0], 0, 0)
post := Post{Id: int(id), Content: item[1], Author: item[2]}
posts = append(posts, post)
}
fmt.Println(posts[0].Id)
fmt.Println(posts[0].Content)
fmt.Println(posts[0].Author)
}
encoding/glob包用于管理gob组成的流(stream),一种在编码器(encoder)和解码器(decoder)之间进行交互的二进制数据,用于序列化。
gob_store.go
package main
import (
"bytes"
"encoding/gob"
"fmt"
"io/ioutil"
)
type Post struct {
Id int
Content string
Author string
}
// store data
func store(data interface{}, filename string) {
buffer := new(bytes.Buffer)
encoder := gob.NewEncoder(buffer)
err := encoder.Encode(data)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(filename, buffer.Bytes(), 0600)
if err != nil {
panic(err)
}
}
// load the data
func load(data interface{}, filename string) {
raw, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
buffer := bytes.NewBuffer(raw)
dec := gob.NewDecoder(buffer)
err = dec.Decode(data)
if err != nil {
panic(err)
}
}
func main() {
post := &Post{Id: 1, Content: "Hello World!", Author: "Sau Sheong"}
store(post, "post1")
var postRead Post
load(&postRead, "post1")
fmt.Println(postRead)
}
健壮且可扩展的环境存储数据,需要使用数据库服务器(database server),一种通过客户端-服务器模型(client-server model)访问数据的程序。
数据库服务器常作为系统的一部分,出现在数据库管理系统中(database management system)。
关系型数据库管理系统中(relational database management system,RDBMS),基于数据的关系模型构建的关系数据库,通过结构化查询语言(structured query language,SQL)访问数据。
(1)创建数据库用户
createuser -p 5433 -P -d gwp
(2)为用户创建数据库
createdb gwp
(3)运行安装脚本,创建表
psql -p 5433 -U gwp -d gwp -f D:\soft\PostgreSQL\setup.sql
setup.sql
create table posts (
id serial primary key,
content text,
author varchar(255)
);
var Db *sql.DB
// connect to the Db
func init() {
var err error
Db, err = sql.Open("postgres", "host=localhost port=5433 user=gwp dbname=gwp password=gwp sslmode=disable")
if err != nil {
panic(err)
}
err = Db.Ping()
if err != nil {
panic(err)
}
fmt.Println("connect success")
}
sql.DB结构是数据库句柄(handle),代表包含任意个数据库连接的连接池(pool)。Open函数使用数据库驱动名字(driver name)以及数据源名字(data source name)建立与数据库的连接。
Open函数不会真正连接及检查用户参数,真正需要时才建立数据库连接。
程序本身通过Register函数注册数据库驱动。
sql.Register("postgres", &drv{})
导入_ "github.com/lib/pq"时,会注册驱动。
type Post struct {
Id int
Content string
Author string
}
// Create a new post
func (post *Post) Create() (err error) {
statement := "insert into posts (content, author) values ($1, $2) returning id"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
//returning id赋值给post.Id
err = stmt.QueryRow(post.Content, post.Author).Scan(&post.Id)
return
}
post := &Post{Content: "Hello World!", Author: "Sau Sheong"}
// Create a post
fmt.Println(post) // {0 Hello World! Sau Sheong}
post.Create()
fmt.Println(post) // {1 Hello World! Sau Sheong}
预处理语句(prepared statement),SQL语句模板,用于重复执行指定的SQL语句。
执行时需为参数提供实际值,该语句会返回id值。
statement := "insert into posts (content, author) values ($1, $2) returning id"
//创建预处理语句
stmt, err := Db.Prepare(statement)
//执行预处理语句
//returning id赋值给post.Id
err = stmt.QueryRow(post.Content, post.Author).Scan(&post.Id)
// Get a single post
func GetPost(id int) (post Post, err error) {
post = Post{}
err = Db.QueryRow("select id, content, author from posts where id = $1", id).Scan(&post.Id, &post.Content, &post.Author)
fmt.Println("getone success")
return
}
// Get one post
readPost, _ := GetPost(post.Id)
fmt.Println(readPost) // {1 Hello World! Sau Sheong}
// Update a post
func (post *Post) Update() (err error) {
_, err = Db.Exec("update posts set content = $2, author = $3 where id = $1", post.Id, post.Content, post.Author)
fmt.Println("update success")
return
}
// Update the post
readPost.Content = "Bonjour Monde!"
readPost.Author = "Pierre"
readPost.Update()
// Delete a post
func (post *Post) Delete() (err error) {
_, err = Db.Exec("delete from posts where id = $1", post.Id)
fmt.Println("deleteone success")
return
}
// Delete all posts
func DeleteAll() (err error) {
_, err = Db.Exec("delete from posts")
fmt.Println("deleteall success")
return
}
// Delete the post
readPost.Delete()
// Delete all posts
DeleteAll()
// get all posts
func Posts(limit int) (posts []Post, err error) {
rows, err := Db.Query("select id, content, author from posts limit $1", limit)
if err != nil {
return
}
for rows.Next() {
post := Post{}
err = rows.Scan(&post.Id, &post.Content, &post.Author)
if err != nil {
return
}
posts = append(posts, post)
}
rows.Close()
fmt.Println("getall success")
return
}
// Get all posts
posts, _ := Posts(10)
fmt.Println(posts) // [{1 Bonjour Monde! Pierre}]
//创建用户及数据库
//可通过pgAdmin可视化界面创建
//createuser -p 5433 -P -d gwp
//createdb gwp
psql -p 5433 -U gwp -f D:\soft\PostgreSQL\setup.sql -d gwp
setup.sql
create table posts (
id serial primary key,
content text,
author varchar(255)
);
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
type Post struct {
Id int
Content string
Author string
}
var Db *sql.DB
// connect to the Db
func init() {
var err error
Db, err = sql.Open("postgres", "host=localhost port=5433 user=gwp dbname=gwp password=gwp sslmode=disable")
if err != nil {
panic(err)
}
err = Db.Ping()
if err != nil {
panic(err)
}
fmt.Println("connect success")
}
// Create a new post
func (post *Post) Create() (err error) {
statement := "insert into posts (content, author) values ($1, $2) returning id"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
//returning id赋值给post.Id
err = stmt.QueryRow(post.Content, post.Author).Scan(&post.Id)
fmt.Println("create success")
return
}
// Get a single post
func GetPost(id int) (post Post, err error) {
post = Post{}
err = Db.QueryRow("select id, content, author from posts where id = $1", id).Scan(&post.Id, &post.Content, &post.Author)
fmt.Println("getone success")
return
}
// Update a post
func (post *Post) Update() (err error) {
_, err = Db.Exec("update posts set content = $2, author = $3 where id = $1", post.Id, post.Content, post.Author)
fmt.Println("update success")
return
}
// get all posts
func Posts(limit int) (posts []Post, err error) {
rows, err := Db.Query("select id, content, author from posts limit $1", limit)
if err != nil {
return
}
for rows.Next() {
post := Post{}
err = rows.Scan(&post.Id, &post.Content, &post.Author)
if err != nil {
return
}
posts = append(posts, post)
}
rows.Close()
fmt.Println("getall success")
return
}
// Delete a post
func (post *Post) Delete() (err error) {
_, err = Db.Exec("delete from posts where id = $1", post.Id)
fmt.Println("deleteone success")
return
}
// Delete all posts
func DeleteAll() (err error) {
_, err = Db.Exec("delete from posts")
fmt.Println("deleteall success")
return
}
func main() {
post := &Post{Content: "Hello World!", Author: "Sau Sheong"}
// Create a post
fmt.Println(post) // {0 Hello World! Sau Sheong}
post.Create()
fmt.Println(post) // {1 Hello World! Sau Sheong}
// Get one post
readPost, _ := GetPost(post.Id)
fmt.Println(readPost) // {1 Hello World! Sau Sheong}
// Update the post
readPost.Content = "Bonjour Monde!"
readPost.Author = "Pierre"
readPost.Update()
// Get all posts
posts, _ := Posts(10)
fmt.Println(posts) // [{1 Bonjour Monde! Pierre}]
// Delete the post
readPost.Delete()
// Get all posts
posts, _ = Posts(10)
fmt.Println(posts) // []
// Delete all posts
DeleteAll()
}
记录直接关系:
psql -p 5433 -U gwp -f D:\soft\PostgreSQL\setup.sql -d gwp
setup.sql
drop table if exists posts cascade;
drop table if exists comments;
create table posts (
id serial primary key,
content text,
author varchar(255)
);
create table comments (
id serial primary key,
content text,
author varchar(255),
post_id integer references posts(id)
);
package main
import (
"database/sql"
"errors"
"fmt"
_ "github.com/lib/pq"
)
type Post struct {
Id int
Content string
Author string
Comments []Comment
}
type Comment struct {
Id int
Content string
Author string
Post *Post
}
var Db *sql.DB
// connect to the Db
func init() {
var err error
Db, err = sql.Open("postgres", "host=localhost port=5433 user=gwp dbname=gwp password=gwp sslmode=disable")
if err != nil {
panic(err)
}
err = Db.Ping()
if err != nil {
panic(err)
}
}
func (comment *Comment) Create() (err error) {
if comment.Post == nil {
err = errors.New("Post not found")
return
}
err = Db.QueryRow("insert into comments (content, author, post_id) values ($1, $2, $3) returning id", comment.Content, comment.Author, comment.Post.Id).Scan(&comment.Id)
return
}
// Get a single post
func GetPost(id int) (post Post, err error) {
post = Post{}
post.Comments = []Comment{}
_ = Db.QueryRow("select id, content, author from posts where id = $1", id).Scan(&post.Id, &post.Content, &post.Author)
rows, err := Db.Query("select id, content, author from comments where post_id = $1", id)
if err != nil {
return
}
for rows.Next() {
comment := Comment{Post: &post}
err = rows.Scan(&comment.Id, &comment.Content, &comment.Author)
if err != nil {
return
}
post.Comments = append(post.Comments, comment)
}
rows.Close()
return
}
// Create a new post
func (post *Post) Create() (err error) {
err = Db.QueryRow("insert into posts (content, author) values ($1, $2) returning id", post.Content, post.Author).Scan(&post.Id)
return
}
func main() {
post := Post{Content: "Hello World!", Author: "Sau Sheong"}
post.Create()
// Add a comment
comment := Comment{Content: "Good post!", Author: "Joe", Post: &post}
comment.Create()
readPost, _ := GetPost(post.Id)
fmt.Println(readPost) // {1 Hello World! Sau Sheong [{1 Good post! Joe 0xc20802a1c0}]}
fmt.Println(readPost.Comments) // [{1 Good post! Joe 0xc20802a1c0}]
fmt.Println(readPost.Comments[0].Post) // &{1 Hello World! Sau Sheong [{1 Good post! Joe 0xc20802a1c0}]}
}
type Post struct {
Id int
Content string
Author string
Comments []Comment
}
type Comment struct {
Id int
Content string
Author string
Post *Post
}
一个帖子Post包含多个评论Comments []Comment;
一个评论Comment属于一个帖子Post *Post。
面向对象编程语言中,对象-关系映射器(object-relational mapper,ORM),将关系数据库中的表与编程语言中的对象映射。
Go有类似的关系映射器。
默认情况下,StructScan根据结构体字段名的英文小写体,将结构体中的字段映射至表中的列。
//AuthorName与表中author列映射
AuthorName string `db: author`
package main
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type Post struct {
Id int
Content string
AuthorName string `db: author`
}
var Db *sqlx.DB
// connect to the Db
func init() {
var err error
Db, err = sqlx.Open("postgres", "host=localhost port=5433 user=gwp dbname=gwp password=gwp sslmode=disable")
if err != nil {
panic(err)
}
err = Db.Ping()
if err != nil {
panic(err)
}
}
// Get a single post
func GetPost(id int) (post Post, err error) {
post = Post{}
err = Db.QueryRowx("select id, content, author from posts where id = $1", id).StructScan(&post)
if err != nil {
return
}
return
}
// Create a new post
func (post *Post) Create() (err error) {
err = Db.QueryRow("insert into posts (content, author) values ($1, $2) returning id", post.Content, post.AuthorName).Scan(&post.Id)
return
}
func main() {
post := Post{Content: "Hello World!", AuthorName: "Sau Sheong"}
post.Create()
fmt.Println(post) // {1 Hello World! Sau Sheong}}
}
遵循数据映射器模式(Data-Mapper pattern),通过提供映射器来将数据库中的数据映射为结构。
基本特性:
package main
import (
"fmt"
"time"
"github.com/jinzhu/gorm"
_ "github.com/lib/pq"
)
type Post struct {
Id int
Content string
Author string `sql:"not null"`
Comments []Comment
CreatedAt time.Time
}
type Comment struct {
Id int
Content string
Author string `sql:"not null"`
PostId int
CreatedAt time.Time
}
var Db *gorm.DB
// connect to the Db
func init() {
var err error
Db, err = gorm.Open("postgres", "host=localhost port=5433 user=gwp dbname=gwp password=gwp sslmode=disable")
if err != nil {
panic(err)
}
Db.AutoMigrate(&Post{}, &Comment{})
}
func main() {
post := Post{Content: "Hello World!", Author: "Sau Sheong"}
fmt.Println(post) // {0 Hello World! Sau Sheong [] 0001-01-01 00:00:00 +0000 UTC}
// Create a post
Db.Create(&post)
fmt.Println(post) // {1 Hello World! Sau Sheong [] 2015-04-13 11:38:50.91815604 +0800 SGT}
// Add a comment
comment := Comment{Content: "Good post!", Author: "Joe"}
Db.Model(&post).Association("Comments").Append(comment)
// Get comments from a post
var readPost Post
Db.Where("author = $1", "Sau Sheong").First(&readPost)
var comments []Comment
Db.Model(&readPost).Related(&comments)
fmt.Println(comments[0]) // {1 Good post! Joe 1 2015-04-13 11:38:50.920377 +0800 SGT}
}
自动数据迁移特性创建数据库表,修改相应结构时,数据库表自动更新。
//支持一多多个参数
Db.AutoMigrate(&Post{}, &Comment{})
CreatedAt time.Time字段表明创建新记录时,自动设置。
Author string `sql:"not null"`结构标签表明值不能为null。
Comment中的PostId int字段,自动看作外键,并创建所需关系。
comment := Comment{Content: "Good post!", Author: "Joe"}
//串联Model方法、Association方法和Append方法将评论添加到帖子里。
Db.Model(&post).Association("Comments").Append(comment)
//获取帖子及评论
var readPost Post
Db.Where("author = $1", "Sau Sheong").First(&readPost)
var comments []Comment
Db.Model(&readPost).Related(&comments)