• 【Golang】快速复习指南QuickReview(十一)——数据库访问(MySql为例)



    对于业务层面的开发,就离不开数据库的访问。

    1.创建项目

    俗话说卖钱不卖钱,摊摊儿要扯圆,甭管怎样,我们先建立一个标准的golang项目,来访问数据库。

    1.1 go mod 管理依赖

    go mod init gitee.com/RandyField/sqltest
    

    1.2 安装mysql驱动包

    go get -u github.com/go-sql-driver/mysql
    

    1.3 创建文件

    cd sqltest
    New-Item main.go  
    New-Item service.go #数据库访问方法
    New-Item models.go  #数据映射结构
    

    2.连接数据库

    main.go

    package main
    
    import (
    	"context"
    	"database/sql"
    	"fmt"
    	"log"
    
    	_ "github.com/go-sql-driver/mysql"
    )
    
    const (
    	user      = "root"
    	password  = "111111"
    	database  = "push-center"
    	protocol  = "tcp"
    	address   = "127.0.0.1"
    	charset   = "utf8mb4"
    	parseTime = "True"
    )
    
    var db *sql.DB
    
    func main() {
    	/*
    		https://pkg.go.dev/github.com/go-sql-driver/mysql
    		https://github.com/denisenkom/go-mssqldb
    		gorm-sqlx
    	*/
    	// Connstr := fmt.Sprintf("%s:%s@%s(%s)/%s?charset=%s&parseTime=%s",
    	// 	user, password, protocol, address, database, charset, parseTime)
    	// fmt.Println(Connstr)
    	// Connstr := "root:111111@tcp(127.0.0.1)/push-center?charset=utf8mb4&parseTime=True"
    
    	//parseTime 是查询结果是否自动解析为时间
    	//loc 是MySQL的时区设置
    	Connstr := "root:111111@tcp(127.0.0.1)/push-center?charset=utf8mb4&parseTime=True&loc=Local"
    	fmt.Println("start...")
    	var err error
    	db, err = sql.Open("mysql", Connstr)
    	if err != nil {
    		log.Fatalln(err.Error())
    	}
    
    	ctx := context.Background()
    	//验证连接是否有效
    	err = db.PingContext(ctx)
    	if err != nil {
    		log.Fatalf(err.Error())
    	}
    	fmt.Println("Connected")
    }
    
    • import "database/sql"
      • Golangdatabase/sql包提供了保证SQL或类SQL数据库的泛用接口。我们的数据库操作(编码)也只在database/sql包上进行。
    • import _ "github.com/go-sql-driver/mysql"
      • 连接数据库,需要加载目标数据库的驱动,Golang是没有提供官方的数据库驱动,所有的数据库驱动都是第三方驱动,但是它们都遵循sql.driver包里面定义的接口
      • 我们并直接使用这个驱动,所以使用_引入,只需要在引入驱动包时调用包内init函数进行自动注册。

    实际上,需要使用sql包的Register()

    • 数据库驱动名称

    • 并实现driver.Driver()接口的struct

    • 注册:sql.Register("mysql",&drv{})

    • Connstr:username/password@tcp(ipaddress)/database?parm1=&parm2=

      • 连接字符串,尤其注意后面的参数,博主在这里使用.netEFCore插入时间,值总是正确,而在使用golang时却总是有问题(晚8小时),无论在代码层面做何种转换。
        • parseTime是查询结果是否自动解析为时间
        • loc是MySQL的时区设置
    • sql.Open():仅仅是配置连接,但并不真正连接,需要两个参数:

      • 数据库驱动名称
      • 数据库连接字符串
      • 返回一个执行sql.DB这个struct的指针:*sql.DB

    这个指针才是我们操作数据库的关键钥匙,我们编码通过这个指针发送sql命令,获得结果。它抽象了底层数据库连接池并对其维护,且并发安全,这便意味着我们可以在多个goroutine中并发使用。针对*sql.DB有两种用法:

    • 定义全局变量,然后到处使用
    • 定义变量,将其作为参数传递给函数或者方法
    • ctx := context.Background():Context(上下文)类型可以携带截止时间、取消信号和其他请求范围的值,并且可以横跨API边界和进程。但是这里的context包的Background()返回的Context很特殊,非nil的空Context,不会被取消也没有值,没有截止时间。

      • 通常用在main函数、初始化或测试中,作为传入请求的顶级Context
    • db.PingContext(ctx):验证与数据库的连接是否仍然有效,如有必要则建立一个连接。

    3.访问数据库

    访问之前我们需要能够映射数据库表的struct,但是struct非必需条件。

    models.go

    package main
    
    //Notifypush 推送通知
    type notifypush struct {
    	Id          int    `json:"Id"`
    	AppName     string `json:"APP"`
    	Target      int    `json:"Platform"`
    	TargetValue *string
    	PushType    int
    	DeviceType  int `json:"Device"`
    	Title       *string
    	Body        *string `json:"Content"`
    	CreateTime  string
    }
    
    

    如果数据库字段有Null,可空类型, 结构体或者变量,都需要定义指针类型,否则会发生运行时错误。

    3.1 查询单条

    service.go

    package main
    
    import (
    	"log"
    	"time"
    )
    
    // GetById 根据ID获取单条数据
    func GetById(id int) (notifypush, error) {
    	n := notifypush{}
    	err := db.QueryRow("select Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notifypush where Id=?",
    		id).Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
    	// err := db.QueryRow("select Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notitypush where Id=@Id",
    	// 	sql.Named("Id", id)).Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
    	return n, err
    }
    

    如果是sqlserver,参数是使用@+参数名来进行站位,并配合sql.Named()函数使用。mysql不能这样,否则会报错mysql: driver does not support the use of Named Parameters

    3.2 查询多条

    // GetMultiRow 获取多条数据
    func GetMultiRow(id int) (ns []notifypush, err error) {
    	rows, err := db.Query("SELECT Id,AppName,Target,TargetValue,PushType,DeviceType,Title,Body,CreateTime From notifypush WHERE Id>?",
    		id)
    	// 非常重要:关闭rows释放持有的数据库链接
    	defer rows.Close()
    
    	for rows.Next() {
    		n := notifypush{}
    		err = rows.Scan(&n.Id, &n.AppName, &n.Target, &n.TargetValue, &n.PushType, &n.DeviceType, &n.Title, &n.Body, &n.CreateTime)
    		if err != nil {
    			log.Fatalln(err.Error())
    		}
    		ns = append(ns, n)
    	}
    	return
    }
    

    3.2 修改、插入、删除

    修改、插入、删除都是cmd,在sql包中只有一个方法:Exec,这里就省略delete操作,实际业务上很少使用物理删除。

    Update

    Update操作,需要定义方法(结构体为接收者)

    //Update 更新
    func (push *notifypush) Update() error {
    	_, err := db.Exec("UPDATE notifypush SET AppName=?,Target=?,PushType=?,DeviceType=? WHERE Id=?",
    		push.AppName, push.Target, push.PushType, push.DeviceType, push.Id)
    	return err
    }
    

    Insert

    //Insert 插入
    func Insert() error {
    	// _, err := db.Exec("INSERT INTO  notifypush(AppName,Target,PushType,DeviceType,CreateTime) VALUES(?,?,?,?,?)",
    	// 	"test-insert-app", 1, 2, 3, time.Now())
    
    	// 改用 prepare
    	sql := `INSERT INTO  notifypush(AppName,Target,PushType,DeviceType,CreateTime) VALUES(?,?,?,?,?)`
    	stmt, err := db.Prepare(sql)
    	if err != nil {
    		log.Fatalln(err.Error())
    	}
    	defer stmt.Close()
    	_, err = stmt.Exec("insert-app", 1, 2, 3, time.Now())
    	return err
    }
    

    4.调用方法

    4.1 编码

    main.go

    package main
    
    import (
    	"context"
    	"database/sql"
    	"fmt"
    	"log"
    
    	_ "github.com/go-sql-driver/mysql"
    )
    
    const (
    	user      = "root"
    	password  = "111111"
    	database  = "push-center"
    	protocol  = "tcp"
    	address   = "127.0.0.1"
    	charset   = "utf8mb4"
    	parseTime = "True"
    )
    
    var db *sql.DB
    
    func main() {
    	//ommit connect code
        
        //查询
        notify, err := GetById(10)
    	if err != nil {
    		log.Fatalf(err.Error())
    	}
    	data, err := json.Marshal(notify)
    	fmt.Printf("推送消息为:%s
    ", data)
        
        //更新
        notify.AppName = "smart"
    	err = notify.Update()
    	if err != nil {
    		log.Fatalf(err.Error())
    	}
        notify, err = GetById(10)
    	if err != nil {
    		log.Fatalf(err.Error())
    	}
    	data, err = json.Marshal(notify)
    	fmt.Printf("更新后:%s
    ", data)
        
        //插入
        err = Insert()
    	if err != nil {
    		log.Fatalf(err.Error())
    	}
        
        //查询多条
        ns, err := GetMultiRow(24)
    	datas, err := json.Marshal(ns)
    	fmt.Printf("推送消息为:%s
    ", datas)
    }
    

    4.2 运行

    这里我们的项目是一个具有多个文件.gomodule,所以不能简单使用go run main.go。需要先编译,才能运行

    go build #编译会生成sqltest.exe
    .sqltest.exe #运行
    

    5.ORM

    5.1 GORM

    GORMGoLang中最出色的ORM框架,支持MySQLPostgreSQLSqliteSQL Server,功能非常强大,也可以直接执行SQL并获取结果集。还有数据库迁移。博主把他看作Golang版本的EntityFramework

    5.2 Sqlx

    Sqlx是对GoLang标准database/sql的扩展。其特点是:

    1. 把SQL执行的结果集转化成数据结构(StructMapsSlices)。
    2. 支持问号(?)或命名的Prepared Statements,避免SQL注入的安全问题

    在博主看来,这个更像是一个golang版本的dapper

    参考连接

    https://www.bilibili.com/video/BV1dZ4y1577v

    https://qinzhiqiang.cn/2019/10/golang服务常用组件-gorm-sqlx-mysql-mongodb/


    作者:Garfield

    同步更新至个人博客:http://www.randyfield.cn/

    本文版权归作者和博客园所有,未经许可禁止转载,否则保留追究法律责任的权利,若有需要请联系287572291@qq.com.

  • 相关阅读:
    大数相加和快速相乘
    (转)直线分割平面 与 平面分割区域 问题
    8.3水了一天
    8.2数论(1)
    7.31 基本算法1.2
    7.31 基本算法1.1
    《GSoC 2019小结》&《买车小记》By Ray Guo
    【笔试题】中国电信天翼智慧家庭2020春季校园招聘
    TinyMCE主题的文章目录没法点击页内跳转
    【毕设】答辩如何准备?本科毕业论文如何撰写文献综述?
  • 原文地址:https://www.cnblogs.com/RandyField/p/14169051.html
Copyright © 2020-2023  润新知