前端工程师吐后端工程师(第九讲)——最不擅长的数据库操作

陈辰CC老师 · · 429 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

第八讲中我们介绍了如果使用Gin写一些常用的接口,本讲我们主要介绍一下数据库如何操作。这一讲没有具体页面,可能对于前端工程师会不太习惯。

首先我们要单件一个mysql数据库环境。如果不会的话可以参考这个教程

https://www.jianshu.com/p/3a0de5da49f3,在搭建完成MySQL,并且启动MySQL之后。数据的管理员账户名设置成root,数据库密码设置为00000000。因为后续我们在代码中使用的数据库相关代码,会以这个为准.

我们可以通过mysql -h 127.0.0.1 -P 3306 -u root -p 000000000  指令来连接刚刚搭建在本机上的数据库。127.0.0.1为本机IP,3306为数据库具体端口,root为数据库用户名,000000000为数据库密码。在我们执行了这段代码之后,就会看到下图。


连接本地数据库

紧接着我们需要创建数据库和做实验要使用的数据库表,数据库的名字我们就叫做go_test。数据库表的名字叫做t_test1。在此之前我们先看一下数据库中都有哪些库,通过执行show databases;来观察。执行结果如下图,有五个数据库。再次


默认数据库

information_schema数据库:信息数据库是关于数据的数据,如数据库名或表名,列的数据类型或者访问权限等。information_schema是信息数据库,其中保存着关于MySQL服务器所维护的所有其他数据库的信息。在information_schema中,有数个只读表。它们实际上是视图,而不是基本表,因此,你将无法看到与之相关的任何文件。

mysql数据库:这个是mysql的核心数据库,主要负责存储数据库的用户、权限设置、关键字等mysql自己需要使用的控制和管理信息。不可以删除,强烈建议不要更改个表的东西。(我们的用户名root和密码00000000也存储在这歌数据库的user表中)

performance_schema数据库:该数据库主要关注数据库运行过程中的性能相关的数据,一般情况下不需要关注。

sys数据库:其所有的数据源来自:performance_schema。目标是把performance_schema的把复杂度降低。

platform(某项目私人测试数据库)。

然后我们只需要执行CREATE DATABASE go_test,创建一个我们测试的数据库

创建数据库

执行完创建数据库的语句后,我们再执行show database;就能看到我们刚刚创建的数据库——go_test。然后进入我们刚刚创建的go_test数据库(执行use go_test),这个时候在控制台上会看到Database changed的提示语,告知我们数据库切换已经成功。


接下来我们需要创建一个用于测试的数据库表"t_1"。并且这在这个表中有两列数据,一列叫做id,另一例叫做name。具体的代码如下:

CREATE TABLE `go_test`.`t_1` (

  `id` int(8) NULL,

  `name` varchar(255) NULL

);

执行上列命令之后我们就会在控制台看到如下信息,其中第一行代码含义是,CREATE TABLE  `数据库名`.`表名`,之后两行SQL语句是数据库中两列的数据描述。


创建t_1数据库表

创建数据库表之后,对于数据的增、删、改、查就不通过简单的SQL语句的方式了,毕竟大多数开发后端需求的时候,我们需要通过后端动态语言的方式来。

还是老规矩复制一下gotest4,命名gotest5,然后把main.go中的代码更改如下:

package main

import (

    "database/sql"

    "fmt"

    "github.com/gin-gonic/gin"

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

)

var (

    DB  *sql.DB

    err error

)

func main() {

    DB, err = sql.Open("mysql", "root:00000000@tcp(127.0.0.1:3306)/go_test") // 设置连接数据库

    //设置数据库最大连接数

    DB.SetConnMaxLifetime(100)

    //设置上数据库最大闲置连接数

    DB.SetMaxIdleConns(10)

    //验证连接

    if err := DB.Ping(); err != nil {

        fmt.Println("open database fail")

        return

    }

    fmt.Println("connnect success")

    r := gin.Default()

    r.Run(":9999") // listen and serve on 0.0.0.0:8080

}

在代码中    _ "github.com/go-sql-driver/mysql"为Go语言连接mysql的驱动。

    "database/sql"为Go语言操作的API。具体的操作API可以观察官方文档https://golang.org/pkg/database/sql/ 

在代码中最关键的代码是下面这行代码。

DB, err = sql.Open("mysql", "root:00000000@tcp(127.0.0.1:3306)/go_test") // 设置连接数据库。

其中mysql为数据库的连接方式,root为数据库名称,tcp为连接数据库形式,127.0.0.1:3306为数据库和数据库端口,go_test为我们要连接的数据。 DB为返回的数据库连接,err为返回的错误信息(如果DB.Ping()连接失败的情况下 ,就直接返回错误信息,并且输出"open database fail")。反之,数据库连接成功后输出,"connnect success",接下来让我们执行一下代码,看看是不是这样。


数据库连接成功


下面我们介绍一下如何在我们创建的数据库表中,往t_1表中添加数据。

首先,我们在main.go中创建一个InsertUser函数,这个函数主要功能是往我们刚刚创建t_1中添加一条数据。具体代码如下:

// 添加数据库数据

func InsertUser(user User) bool {

    //开启事务

    tx, err := DB.Begin()

    if err != nil {

        fmt.Println("tx fail")

        return false

    }

    //准备sql语句

    stmt, err := tx.Prepare("INSERT INTO t_1 (`id`, `name`) VALUES (?, ?)")

    if err != nil {

        fmt.Println("Prepare fail")

        return false

    }

    //将参数传递到sql语句中并且执行

    res, err := stmt.Exec(user.id, user.name)

    if err != nil {

        fmt.Println("Exec fail")

        return false

    }

    //将事务提交

    tx.Commit()

    //获得上一个插入自增的id

    fmt.Println(res.LastInsertId())

    return true

}


下面让我们介绍一下代码片段中的关键部分,tx为一个MySQL的事务对象(MySQL的事务可以自行百度,这里用一句简单的话来表述就是要么事务内的操作都成功,要么就是都不成功,保证数据库操作的完整性的)。

tx.Prepare为频繁操作时使用的函数,主要是为了缓存MySQL操作使得MySQL操作更高效,除此之外,还有QueryRow,QueryRow表示只返回一行的查询,不会缓存任何东西。

我们可以在更深层次来分析,目前我们学习database/sql提供两类查询操作,Query和Exec方法。他们都可以使用plaintext和preprea方式查询。对于后者,可以有效的避免数据库注入。而prepare方式又可以有显示的声明stmt对象,也有隐藏的方式。显示的创建stmt会有3次网络请求,创建->执行->关闭,再批量操作可以考虑这种做法,另外一种方式创建prepare后就执行,因此不会因为reprepare导致高并发下的leak连接问题。

plaintext形式:rows,err:=db.Query("SELECT * FROM user WHERE gid = 1")

preprea形式:rows,err:=db.Query("SELECT * FROM user WHERE gid = ?",1)


stmt.Exec表示执行语句,它不会返回行。DB的类型为:*sql.DB,有了DB之后我们就可以执行CRUD操作。Go将数据库操作分为两类:Query与Exec。两者的区别在于前者会返回结果,而后者不会。


tx.Commit()就是实际执行该MySQL。至于代码中的User类的定义如下:

type User struct {

    id  int64

    name string

}

下面我们介绍一下type的功能。

//定义一个类

type Books struct{

title string

author string

subject string

}

//定义接口

type Phoneinterface{

call()

}

// 定义自定义类型

type name string  // 使用 type 基于现有基础类型,结构体,函数类型创建用户自定义类型。

//  可以设置别名

type handle func(str string) // 自定义一个函数func,别名叫做handle,传入一个string参数

// 类型开关(我们可以把它理解TS中的any类型,但是不一样的地方是any本身是一种类型,但是interface不是一种类型,是任何类型的代名词)

func classifier(items ...interface{}) {

  for i,x := range items {

      switch x.(type) {

      case bool:

        fmt.Printf("type #%d is bool",i)

      case float64:

        fmt.Printf("type #%d is float64",i)

      case string:

        fmt.Printf("type #%d is string",i)

      case int:

        fmt.Printf("type #%d is int",i)

      default:

        fmt.Printf("type is unknow")

      }

  }

}


下面我们把main.go文件中的代码都展上

package main

import (

    "database/sql"

    "fmt"

    "github.com/gin-gonic/gin"

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

)

var (

    DB  *sql.DB

    err error

)

type User struct {

    id  int64

    name string

}

func main() {

    DB, err = sql.Open("mysql", "root:00000000@tcp(127.0.0.1:3306)/go_test") // 设置连接数据库的参数

    //设置数据库最大连接数

    DB.SetConnMaxLifetime(100)

    //设置上数据库最大闲置连接数

    DB.SetMaxIdleConns(10)

    //验证连接

    if err := DB.Ping(); err != nil {

        fmt.Println("open database fail")

        return

    }

    fmt.Println("connnect success")

    // 添加数据

    r := gin.Default()

    var user User

    user.id = 1

    user.name = "张三"

    InsertUser(user)

    r.Run(":9999") // listen and serve on 0.0.0.0:9999

}

// 添加数据库数据

func InsertUser(user User) bool {

    //开启事务

    tx, err := DB.Begin()

    if err != nil {

        fmt.Println("tx fail")

        return false

    }

    //准备sql语句

    stmt, err := tx.Prepare("INSERT INTO t_1 (`id`, `name`) VALUES (?, ?)")

    if err != nil {

        fmt.Println("Prepare fail")

        return false

    }

    //将参数传递到sql语句中并且执行

    res, err := stmt.Exec(user.id, user.name)

    if err != nil {

        fmt.Println("Exec fail")

        return false

    }

    //将事务提交

    tx.Commit()

    //获得上一个插入自增的id

    fmt.Println(res.LastInsertId())

    return true

}


在执行了代码之后,就能看到如下界面,没有输出tx fail,Prepare fail,Exec fail证明我们的代码层面没有问题。

添加数据执行成功

那么我们要看一下数据库中到底有没有,在此推荐两款MySQL可视化工具(Navicat、WorkBench),下图中是Navicat的界面,我们可以观察到我们刚刚创建的数据库、表和添加的数据。


数据库中添加的数据


为了后续操作方便,我们顺便添加李四、王五两条数据。接下来我们看一下,如何通过代码的方式查询我们刚刚添加的数据。具体代码如下:

// 查询id 为1 的用户信息

func QueryUser() bool {

    var user User

    rows, e := DB.Query("select * from t_1 where id=1")

    if e == nil {

        errors.New("query incur error")

    }

    fmt.Println(rows)

    for rows.Next() {

        if err := rows.Scan(&user.id, &user.name); err != nil {

            log.Fatal(err)

        }

        fmt.Printf("%d id %s\n", user.id, user.name)

    }

    return true

}



查询id为1的数据

本次查询操作没有使用事务的方式进行封装,仅仅是常规的查询操作,之所以这么做是让大家了解其他的方式。但是在平时的真实开发场景中还是建议大家使用事务组装的开发模式。


在做更改操作之前,我们先关注一下数据中目前的数据是什么样的。通过Navicat观察如下图:


t_1中的数据


然后在main函数中调用下面这个UpdateUser函数,在UpdateUser中除了MySQL语句不同,我们还用到一个新的函数RowsAffected(),这个函数的主要的作用是在我们做某些操作的时候,比如:查询、更改、删除数据库操作的时候,涉及到多少条数据。

// 更改id 为1 的用户信息

func UpdateUser() bool {

    result, err := DB.Exec("UPDATE t_1 set name=? where id=1", "张三改")

    if err != nil {

        fmt.Printf("Insert failed,err:%v", err)

        return false

    }

    rowsaffected, err := result.RowsAffected()

    if err != nil {

        fmt.Printf("Get RowsAffected failed,err:%v", err)

        return false

    }

    fmt.Println("RowsAffected:", rowsaffected)

    fmt.Println("更改成功:")

    return true

}

执行上面代码之后,我们先观察控制台部分如下,控制台打印出了更改成功。另外我们也可以看到控制台框出部分,就是展示的此次操作影响的数据。


更改数据成功


我们可以看到id为1的数据的name属性变成张三改了。

修改id为1的name属性


最后我们来看一下如何删除MySQL中的数据,有过前面增、查、改之后,删除操作就显得更加简单了,直接看下面的代码就可以了。


//删除数据

func deleteUser() bool {

    result, err := DB.Exec("delete from t_1 where id=?", 1)

    if err != nil {

        fmt.Printf("Insert failed,err:%v", err)

        return

    }

    rowsaffected, err := result.RowsAffected()

    if err != nil {

        fmt.Printf("Get RowsAffected failed,err:%v", err)

        return

    }

    fmt.Println("RowsAffected:", rowsaffected)

    fmt.Println("删除数据成功")

}

在执行成功了之后,我们观察控制台和数据库。


删除操作控制台信息


删除操作数据库信息

至此关于数据库的所有操作基本都完成了,本讲内容较多,我们介绍了数据库的连接、数据表内容的增、查、改、删操作,并且介绍了操作中的一些常用函数的用法。下一讲,我们讲介绍另一种非常常用的存储形式Redis的连接方法和使用注意事项。


陈辰(CC老师)    978563552@qq.com


参考资料:https://www.jianshu.com/p/ee0d2e7bef54

参考资料:https://www.cnblogs.com/rickiyang/p/11074180.html


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:陈辰CC老师

查看原文:前端工程师吐后端工程师(第九讲)——最不擅长的数据库操作

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

429 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传