Go操作Mysql数据库|Go主题月

介绍

为系统提供存储能力的数据库有很多,包括关系型、非关系型

主流的关系型数据库有 Oracle、DB2、MySQL、Microsoft SQL Server、Microsoft Access ...

非关系型 mongodb、redis、CouchDB ...

在 Go lang 中提供了操作数据库接口 database/sql包,没有提供具体的数据库驱动

在使用database/sql包时必须注入一个数据库驱动(必须有一个驱动)

操作数据

使用 go-sql-driver 驱动

安装数据库驱动

go get -u github.com/go-sql-driver/mysql
复制代码

初始化数据库连接

使用 database/sql 提供了建立连接

func Open(driverName, dataSourceName string) (*DB, error) {}

复制代码
  • driverName 驱动名称
  • dataSourceName 数据库连接地址
  • 返回操作数据库 DB
初始化,获取 DB
func InitDivider() (db *sql.DB) {
	url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", "root", "123456", "127.0.0.1", "3306", "moose")
	db, err := sql.Open("mysql", url)
	if err != nil {
		fmt.Println(err)
		return
	}
	return db
}
复制代码
  • url 包括数据的账号、密码、地址、端口、数据库名

建立表

使用之前数据库脚本建立一个 t_user_info 用户信息表

DROP TABLE IF EXISTS `t_user_info`;
CREATE TABLE `t_user_info` (
  `user_id` bigint(20) NOT NULL COMMENT '用户Id',
  `username` varchar(64) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '用户名',
  `account_id` bigint(20) NOT NULL,
  `account_name` varchar(64) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '用户名',
  `phone` varchar(11) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '手机号',
  `gender` char(1) COLLATE utf8_bin NOT NULL COMMENT '性别 male 1; female 2;un_known or hide 0',
  `avatar` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '头像',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `uniq_phone` (`phone`),
  UNIQUE KEY `uniq_user_id` (`user_id`),
  UNIQUE KEY `uniq_username` (`username`),
  UNIQUE KEY `uniq_account_id` (`account_id`),
  UNIQUE KEY `uniq_account_name` (`account_name`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户信息表';
复制代码

更多数据库脚本地址

gitee.com/shizidada/m…

以下操作全部操作

前提: 创建一个测试查询方法,传入操作数据库句柄(DB)

增加

func TestInsert(db *sql.DB) {
	sql := `INSERT INTO t_user_info(user_id, username, account_id, account_name, gender, phone, avatar) values(?,?,?,?,?,?,?)`
	_, err := db.Exec(sql, "1", "测试用户", "10", "测试账号", 1, "18732132132", "https://moose-plus.oss-cn-shenzhen.aliyuncs.com/avatar.png")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("添加成功")
}

func main() {

	db := InitDivider()

	defer db.Close()

	TestInsert(db)
}
复制代码
λ go run main.go
"添加成功"
复制代码

如果 id 是自增长,db.Exec第一个参数可以获取到返回结果

func (db *DB) Exec(query string, args ...interface{}) (Result, error) {}

// A Result summarizes an executed SQL command.
type Result interface {
	// LastInsertId returns the integer generated by the database
	// in response to a command. Typically this will be from an
	// "auto increment" column when inserting a new row. Not all
	// databases support this feature, and the syntax of such
	// statements varies.
	LastInsertId() (int64, error)

	// RowsAffected returns the number of rows affected by an
	// update, insert, or delete. Not every database or database
	// driver may support this.
	RowsAffected() (int64, error)
}
复制代码

查看数据库也存在一条刚刚添加数据

修改

func TestUpdate(db *sql.DB) {
	sql := `UPDATE t_user_info SET username = ? WHERE user_id = ?`
	result, err := db.Exec(sql, "修改名字", "1")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(result)
}
复制代码

删除

func TestDelete(db *sql.DB) {
	sql := `DELETE FROM t_user_info WHERE user_id = ?`
	result, err := db.Exec(sql, "1")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(result)
}
复制代码

查看数据库,数据被删除了

查询

UserInfo 结构体

type UserInfo struct {
	UserId   string
	UserName string
	Avatar   string
    ...
}
复制代码
func TestQueryRow(db *sql.DB) {
	// 查询 SQL
	sql := "select user_id, avatar, username from t_user_info"

	var userInfo UserInfo
	err := db.QueryRow(sql).Scan(&userInfo.UserId, &userInfo.UserName, &userInfo.Avatar)
	if err != nil {
		fmt.Println("sacn error :: ", err)
		return
	}
	fmt.Println(userInfo)
}

func main() {
	db := InitDivider()

    // 延迟关闭,在 main 方法执行完
	defer db.Close()

	TestQueryRow(db)

}
复制代码
λ go run main.go
{785919644501544960 https://moose-plus.oss-cn-shenzhen.aliyuncs.com/2020-12-23/avatar.png 江景}
复制代码

注意 在查询 sql, select field1, field2, field3 需要和 Scan(field1, field2, field3)保持对应,要不然会出现错乱

查询返回多条数据

func TestQuery(db *sql.DB) {
	sql := "select user_id, avatar, username from t_user_info where user_id IN(?,?)"
	rows, err := db.Query(sql, "785919644501544960", "790883082524954600")
	if err != nil {
		fmt.Println(err)
		return
	}

	for rows.Next() {
		var userInfo UserInfo
		err = rows.Scan(&userInfo.UserId, &userInfo.UserName, &userInfo.Avatar)
		if err != nil {
			fmt.Println("发送错误")
			return
		}
		fmt.Println(userInfo)
	}
}
复制代码
λ go run main.go
{785919644501544960 https://moose-plus.oss-cn-shenzhen.aliyuncs.com/2020-12-23/avatar.png 江景}
{790883082524954600 https://default.png 李白}
复制代码
  • 查询多条使用 Query

  • Query 查询结果返回

func (*sql.DB).Query(query string, args ...interface{}) (*sql.Rows, error)
复制代码
  • 遍历 Rows,使用 Next 判断是否还有下一条
  func (rs *Rows) Next() bool
复制代码
  • 数据封装和单条数据一致 Scan

Sql 注入问题

通过 SQL 语法里的一些组合,执行 SQL 语句,返回想要的结果

使用 db.Prepare(sql),将 sql 进行预编译,返回 Stmt,使用 Stmt 进行数据库操作

func (db *DB) Prepare(query string) (*Stmt, error)

...

stmt, err := db.Prepare(sql)
stmt.Exec()

...
复制代码

数据库事务

执行一条 SQL,保证原子性,要么全部成功,要么全部失败


func (db *DB) Begin() (*Tx, error)

tx, _ := db.Begin()
// db.BeginTx()
tx.Commit()
tx.Rollback()

复制代码

执行 SQL 之前开启事务 db.Begin(),如果需要回滚执行 tx.Rollback(), 不需要回滚执行 tx.Commit()

TODO

go 在操作数据库过程中,还是有些麻烦的,市面上也有封装操作数据的第三方包,还有一些 orm 框架,简化数据操作。