• 《Go Web 编程》之第6章 存储数据


    第6章 存储数据

    存储数据手段:

    • 内存
    • 文件
    • 数据库

    对数据执行CRUD的创建、获取、更新和删除的操作。

    6.1 内存存储

    使用数组、切片、映射、栈、树、队列及其它任意类型(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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    6.2 文件存储

    内存数据非持久化,文件存储持久化。
    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))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    6.2.1 读写CSV文件

    通用的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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    6.2.2 glob包读写二进制数据

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    6.3 Go与SQL

    健壮且可扩展的环境存储数据,需要使用数据库服务器(database server),一种通过客户端-服务器模型(client-server model)访问数据的程序。

    数据库服务器常作为系统的一部分,出现在数据库管理系统中(database management system)。

    关系型数据库管理系统中(relational database management system,RDBMS),基于数据的关系模型构建的关系数据库,通过结构化查询语言(structured query language,SQL)访问数据。

    6.3.1 设置数据库

    (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)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.3.2 连接数据库

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    sql.DB结构是数据库句柄(handle),代表包含任意个数据库连接的连接池(pool)。Open函数使用数据库驱动名字(driver name)以及数据源名字(data source name)建立与数据库的连接。
    Open函数不会真正连接及检查用户参数,真正需要时才建立数据库连接。

    程序本身通过Register函数注册数据库驱动。

    sql.Register("postgres", &drv{})
    
    • 1

    导入_ "github.com/lib/pq"时,会注册驱动。

    6.3.3 创建帖子

    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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    预处理语句(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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.3.4 获取帖子

    // 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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6.3.5 更新帖子

    // 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    6.3.6 删除帖子

    
    // 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    6.3.7 获取多篇帖子

    // 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}]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    6.3.8 完整代码

    //创建用户及数据库
    //可通过pgAdmin可视化界面创建
    //createuser -p 5433 -P -d gwp
    //createdb gwp
    
    psql -p 5433 -U gwp -f D:\soft\PostgreSQL\setup.sql -d gwp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    setup.sql

    create table posts (
    	id serial primary key,
    	content text,
    	author varchar(255)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    6.4 Go与SQL的关系

    记录直接关系:

    • 一对一关联,“有一个”(has one)关系,比如一个用户一个个人简介;
    • 一对多关联,“有多个”(has many)关系,一个用户多篇论坛帖子;
    • 多对一关联,“属于”(belongs to)关系,多篇帖子属于同一个用户;
    • 多对多关联,一个用户多篇帖子,一篇帖子多个用户。

    6.4.1 设置数据库

    psql -p 5433 -U gwp -f D:\soft\PostgreSQL\setup.sql -d gwp
    
    • 1

    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)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    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}]}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    6.4.2 一对多关系

    type Post struct {
    	Id       int
    	Content  string
    	Author   string
    	Comments []Comment
    }
    
    type Comment struct {
    	Id      int
    	Content string
    	Author  string
    	Post    *Post
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    一个帖子Post包含多个评论Comments []Comment;
    一个评论Comment属于一个帖子Post *Post。

    6.5 Go与关系映射器

    面向对象编程语言中,对象-关系映射器(object-relational mapper,ORM),将关系数据库中的表与编程语言中的对象映射。

    Go有类似的关系映射器。

    6.5.1 Sqlx

    • 兼容database/sql包;
    • 通过结构化标签(struct tag)将数据库记录(行)封装为结构、映射或者切片;
    • 为预处理语句提供具名参数支持。

    默认情况下,StructScan根据结构体字段名的英文小写体,将结构体中的字段映射至表中的列。

    //AuthorName与表中author列映射
    AuthorName string `db: author`
    
    • 1
    • 2
    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}}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    6.5.2 Gorm

    遵循数据映射器模式(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}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    自动数据迁移特性创建数据库表,修改相应结构时,数据库表自动更新。

    //支持一多多个参数
    Db.AutoMigrate(&Post{}, &Comment{})
    
    • 1
    • 2
    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    Mac11.0.1系统M1处理器安装cocoaPoads最新教程
    [论文阅读] 颜色迁移-Correlated Color Space
    ArduPilot开源飞控之AP_Mission
    keil下载及安装(社区版本)
    Spring6 - ioc
    解决办法:使用Node.js访问MySQL8的时候会报错ER_NOT_SUPPORTED_AUTH_MODE
    flask 支付宝的使用
    Java基础练习题---类型转换、双分支、多分支、switch、for
    京东获取商品历史价格信息 API
    Java继承 学习资料
  • 原文地址:https://blog.csdn.net/oqqyx1234567/article/details/126730824