一、Golang连接&操作mysql数据库

1.1 前置步骤

github.com/go-sql-driver/mysql
DROP TABLE IF EXISTS album;
CREATE TABLE album (
  id         INT AUTO_INCREMENT NOT NULL,
  title      VARCHAR(128) NOT NULL,
  artist     VARCHAR(255) NOT NULL,
  price      DECIMAL(5,2) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO album 
  (title, artist, price) 
VALUES 
  ('Blue Train', 'John Coltrane', 56.99),
  ('Giant Steps', 'John Coltrane', 63.99),
  ('Jeru', 'Gerry Mulligan', 17.99),
  ('Sarah Vaughan', 'Sarah Vaughan', 34.98);

1.2 测试查询和插入数据

package main

import (
	"database/sql"
	"fmt"
	"log"

	"github.com/go-sql-driver/mysql"
)

var db *sql.DB

type Album struct {
	ID     int64
	Title  string
	Artist string
	Price  float32
}

func main() {
	cfg := mysql.Config{
		User:                 "root",      //os.Getenv("DBUSER"),
		Passwd:               "xwphs1234*", //os.Getenv("DBPASS"),
		Net:                  "tcp",
		Addr:                 "143.30.158.11:3306",
		DBName:               "recordings",
		AllowNativePasswords: true,
	}
	// get a database handle
	var err error
	db, err = sql.Open("mysql", cfg.FormatDSN())
	if err != nil {
		log.Fatal(err)
	}
	pingErr := db.Ping()
	if pingErr != nil {
		log.Fatal(pingErr)
	}
	fmt.Println("Connected!")

	albums, err := albumsByArtist("John Coltrane")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Albums found: %v\n", albums)

	// Hard-code ID 2 to test the query
	alb, err := albumByID(2)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Album found: %v\n", alb)

	// Album instance
	alb_instance := Album{
		Title:  "The Modern Sound of Betty Carter",
		Artist: "Betty Carter",
		Price:  49.99,
	}
	// Add album data to the database
	albID, err := addAlbum(alb_instance)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("ID of added album: %v\n", albID)
}

// albumsByArtist queries for albums that have specified name.
func albumsByArtist(name string) ([]Album, error) {
	// An albums slice to hold data from returned rows.
	var albums []Album
	rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
	if err != nil {
		return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
	}
	defer rows.Close()
	// Loop through rows, using Scan to assign column data to struct fields.
	for rows.Next() {
		var alb Album
		if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
			return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
		}
		albums = append(albums, alb)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
	}
	return albums, nil
}

// albumByID queries for the album with the specified ID
func albumByID(id int64) (Album, error) {
	// An album to hold data from returned row.
	var alb Album
	row := db.QueryRow("SELECT * FROM album where id = ?", id)
	if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
		if err == sql.ErrNoRows {
			return alb, fmt.Errorf("albumByID %d: no such album", id)
		}
		return alb, err
	}
	return alb, nil
}

// addAlbum adds the specified album to the database
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
	result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
	if err != nil {
		return 0, fmt.Errorf("addAlbum: %v\n", err)
	}
	id, err := result.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("addAlbum: %v\n", err)
	}
	return id, nil
}

1.3 查询一个可空的列值

使用下图这种可以用来表示一个可空的值。
在这里插入图片描述

sql.NullFloat64price.Valid
// priceByID get the price of album through specified ID.
// if the return value is -1, it represents the column value is null.
func priceByID(id int64) (float64, error) {
	// Price is an nullable column value, so we use sql.NullFloat64 to adopt it.
	var price sql.NullFloat64

	if err := db.QueryRow("SELECT price FROM album WHERE id = ?", id).Scan(&price); err != nil {
		if err == sql.ErrNoRows {
			return -1, fmt.Errorf("priceByID %d: no such album", id)
		}
		return -1, err
	}
	if price.Valid {
		return price.Float64, nil
	}
	return -1, nil
}

然后是调用函数priceByID。这段接在1.2 main函数末尾

	// Get the price of album with ID 2
	price, err := priceByID(2)
	if err != nil {
		log.Fatal(err)
	}
	if price == -1 {
		fmt.Println("This column value is null")
		return
	}
	fmt.Printf("The price of album with ID 2 is %v\n", price)

1.4 处理多结果集

Rows.NextResultSet

这里db.Query一次处理两个查询语句,结果放到两个结果集中。

github.com/go-sql-driver/mysqlMultiStatements: true
// testMultipleResultSet is a test that query to return multiple result set
func testMultipleResultSet() {
	rows, err := db.Query("SELECT * FROM album; SELECT * FROM song;")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// Loop through the first result set
	var albums []Album
	for rows.Next() {
		var alb Album
		if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
			log.Fatal(err)
		}
		albums = append(albums, alb)
	}
	fmt.Printf("Album found: %v\n", albums)

	// Advance to next result set
	rows.NextResultSet()

	// Loop through the second result set
	type Song struct {
		id     int64
		name   string
		artist string
	}
	var songs []Song
	for rows.Next() {
		var s Song
		if err := rows.Scan(&s.id, &s.name, &s.artist); err != nil {
			log.Fatal(err)
		}
		songs = append(songs, s)
	}
	fmt.Printf("Song found: %v\n", songs)

}

1.5 Prepared statement

使用prepared statement使代码跑的更快一点。因为它避免了每次执行数据库操作时重复创建statement的开销。

// preparedStatements retrieves the specified album by ID.
// prepared statement run a bit faster by avoiding the overhead of re-creating statement each time performs database operation.
func preparedStatements(id int) (Album, error) {
	// Define a prepared statement
	stmt, err := db.Prepare("SELECT * FROM album WHERE id = ?")
	if err != nil {
		log.Fatal(err)
	}
	var alb Album

	// Execute the parpared statement, passing an id value for the parameter where placeholder is ?.
	err = stmt.QueryRow(id).Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price)
	if err != nil {
		if err == sql.ErrNoRows {
			return alb, fmt.Errorf("preparedStatements %d: no such album", id)
		}
		return alb, err
	}
	return alb, nil
}