MySQL教程|创建一个表并插入行的方法

115 阅读10分钟

在本教程中,我们将学习如何创建一个表并向该表插入记录。

系列索引

连接到MySQL并创建数据库 创建表并插入记录 选择单条/多条记录 - WIP
预备语句 - WIP
更新记录 - WIP
删除记录 -

WIP

创建表

我们将创建一个名为product 的表,其字段为product_id,product_name,product_price,created_atupdated_at

下面是创建此表的MySQL查询。

CREATE TABLE IF NOT EXISTS product(product_id int primary key auto_increment, product_name text, product_price int, created_at datetime default CURRENT_TIMESTAMP, updated_at datetime default CURRENT_TIMESTAMP)  

product_id是一个自动递增的int ,它作为主键。created_atupdated_at 的默认值被设置为当前时间戳。现在我们有了查询,让我们把它转换成Go代码并创建我们的表。

DB包的ExecContext方法可以执行任何不返回任何记录的查询。在我们的例子中,创建表的查询没有返回任何行,因此我们将使用ExecContext() context方法来创建我们的表。

让我们做一个负责任的开发者,创建一个有超时的上下文,以便在出现任何网络分区或运行时错误时,创建表的查询会超时。

query := `CREATE TABLE IF NOT EXISTS product(product_id int primary key auto_increment, product_name text,  
        product_price int, created_at datetime default CURRENT_TIMESTAMP, updated_at datetime default CURRENT_TIMESTAMP)`

ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)  
defer cancelfunc()  

在上面的代码中,我们已经创建了一个具有5秒超时的上下文。让我们继续前进,在ExecContext() 方法中使用这个上下文。

res, err := db.ExecContext(ctx, query)  
if err != nil {  
    log.Printf("Error %s when creating product table", err)
    return err
}

我们将创建的上下文和MySQL查询作为参数传递给ExecContext 方法,如果有错误,则返回错误。db 是在前面的教程golangbot.com/connect-cre… 中创建的数据库连接池请通过它来了解如何连接到MySQL并创建一个连接池。

现在表被成功创建。调用ExecContext() ,返回的结果集包含一个方法,返回受影响的行数。创建表的语句并没有影响任何行,但还是让我们通过调用res.RowsAffected() 方法来检查一下。

rows, err := res.RowsAffected()  
    if err != nil {
        log.Printf("Error %s when getting rows affected", err)
        return err
    }
log.Printf("Rows affected when creating table: %d", rows)  

上面的代码将打印Rows affected when creating table: 0 ,因为create table 并不影响任何行。

整个代码在下面提供。

package main

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

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

const (  
    username = "root"
    password = "naveenr123"
    hostname = "127.0.0.1:3306"
    dbname   = "ecommerce"
)

func dsn(dbName string) string {  
    return fmt.Sprintf("%s:%s@tcp(%s)/%s", username, password, hostname, dbName)
}

func dbConnection() (*sql.DB, error) {  
    db, err := sql.Open("mysql", dsn(""))
    if err != nil {
        log.Printf("Error %s when opening DB\n", err)
        return nil, err
    }
    //defer db.Close()

    ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancelfunc()
    res, err := db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS "+dbname)
    if err != nil {
        log.Printf("Error %s when creating DB\n", err)
        return nil, err
    }
    no, err := res.RowsAffected()
    if err != nil {
        log.Printf("Error %s when fetching rows", err)
        return nil, err
    }
    log.Printf("rows affected %d\n", no)

    db.Close()
    db, err = sql.Open("mysql", dsn(dbname))
    if err != nil {
        log.Printf("Error %s when opening DB", err)
        return nil, err
    }
    //defer db.Close()

    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(20)
    db.SetConnMaxLifetime(time.Minute * 5)

    ctx, cancelfunc = context.WithTimeout(context.Background(), 5*time.Second)
    defer cancelfunc()
    err = db.PingContext(ctx)
    if err != nil {
        log.Printf("Errors %s pinging DB", err)
        return nil, err
    }
    log.Printf("Connected to DB %s successfully\n", dbname)
    return db, nil
}

func createProductTable(db *sql.DB) error {  
    query := `CREATE TABLE IF NOT EXISTS product(product_id int primary key auto_increment, product_name text, 
        product_price int, created_at datetime default CURRENT_TIMESTAMP, updated_at datetime default CURRENT_TIMESTAMP)`
    ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancelfunc()
    res, err := db.ExecContext(ctx, query)
    if err != nil {
        log.Printf("Error %s when creating product table", err)
        return err
    }
    rows, err := res.RowsAffected()
    if err != nil {
        log.Printf("Error %s when getting rows affected", err)
        return err
    }
    log.Printf("Rows affected when creating table: %d", rows)
    return nil
}

func main() {  
    db, err := dbConnection()
    if err != nil {
        log.Printf("Error %s when getting db connection", err)
        return
    }
    defer db.Close()
    log.Printf("Successfully connected to database")
    err = createProductTable(db)
    if err != nil {
        log.Printf("Create product table failed with error %s", err)
        return
    }
}

我在dbConnection() 函数中包含了先前教程中连接到MySQL并创建数据库的代码。与之前的教程相比,唯一的变化是在第30行和第1行的defer语句。第30行和第52行的defer语句被注释了。52行被注释,因为我们不希望在从这个函数返回后立即关闭数据库。

main() 函数在第89行创建了一个新的数据库连接池,并将其传递给 。89行创建一个新的数据库连接池,并将其传递给第95行的createProductTable 函数。95.我们在第93行中推迟了数据库的关闭,以便与数据库的连接在第93行中被关闭。93行,以便程序终止时关闭与数据库的连接。运行这个程序,你可以看到以下输出。

2020/10/25 20:30:51 rows affected 1  
2020/10/25 20:30:51 Connected to DB ecommerce successfully  
2020/10/25 20:30:51 Successfully connected to database  
2020/10/25 20:30:51 Rows affected when creating table: 0  

为了验证表是否已经成功创建,你可以在MySQL查询浏览器中运行desc product; ,你可以看到它返回表的模式。

插入行

下一步是向我们刚刚创建的product 表插入行。下面提供了向产品表插入行的查询。

INSERT INTO product(product_name, product_price) VALUES ("iPhone", 800);  

让我们讨论一下如何在Go中使用上述查询并向表中插入行。

首先,让我们创建一个产品结构来表示我们的产品。

type product struct {  
    name      string
    price     int
}

第二步是创建一个预备语句。预备语句是用来对SQL查询进行参数化处理的,这样就可以有效地以不同的参数运行同一查询。它也有助于防止SQL注入

在我们的例子中,查询的参数是product_nameproduct_price 。创建准备好的语句模板的方法是用问号替换参数? 。以下查询的准备好的语句模板

INSERT INTO product(product_name, product_price) VALUES ("iPhone", 800);  

INSERT INTO product(product_name, product_price) VALUES (?, ?);  

你可以看到,"iPhone"800 被替换为问号。

func insert(db *sql.DB, p product) error {  
    query := "INSERT INTO product(product_name, product_price) VALUES (?, ?)"
    ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancelfunc()
    stmt, err := db.PrepareContext(ctx, query)
    if err != nil {
    log.Printf("Error %s when preparing SQL statement", err)
    return err
    }
    defer stmt.Close()
}

上述代码的第2行是准备好的语句模板。在第5行中,我们为 "准备好的语句 "创建了一个模板。第5行,我们使用这个模板为我们的插入查询创建一个准备好的语句。像往常一样,我们使用一个带超时的上下文来处理网络错误。语句在使用后应该被关闭。因此,在下一行中,我们推迟了语句的关闭。

下一步是向准备好的语句传递必要的参数并执行它。

res, err := stmt.ExecContext(ctx, p.name, p.price)  
if err != nil {  
    log.Printf("Error %s when inserting row into products table", err)
    return err
}
rows, err := res.RowsAffected()  
if err != nil {  
    log.Printf("Error %s when finding rows affected", err)
    return err
}
log.Printf("%d products created ", rows)  
return nil  

准备好的语句希望有两个参数,即产品名称和产品价格。ExecContext 方法接受一个interface{}参数的变量列表。传递给它的变量参数的数量应该与准备好的语句模板中的问号数量一致? ,否则会出现运行时错误Column count doesn't match value count at row 1 when preparing SQL statement

在我们的例子中,模板中有两个问号,因此在上面的代码片断中,在第1行,我们传递了两个参数product{}。1,我们将两个参数产品名称和价格传递给ExecContext方法。

下面提供了整个insert 函数。

func insert(db *sql.DB, p product) error {  
    query := "INSERT INTO product(product_name, product_price) VALUES (?, ?)"
    ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancelfunc()
    stmt, err := db.PrepareContext(ctx, query)
    if err != nil {
        log.Printf("Error %s when preparing SQL statement", err)
        return err
    }
    defer stmt.Close()
    res, err := stmt.ExecContext(ctx, p.name, p.price)
    if err != nil {
        log.Printf("Error %s when inserting row into products table", err)
        return err
    }
    rows, err := res.RowsAffected()
    if err != nil {
        log.Printf("Error %s when finding rows affected", err)
        return err
    }
    log.Printf("%d products created ", rows)
    return nil
}

请在main 函数的末尾添加以下代码,以调用insert 函数。

func main() {  
...

p := product{  
        name:  "iphone",
        price: 950,
    }
err = insert(db, p)  
if err != nil {  
    log.Printf("Insert product failed with error %s", err)
    return
    }
}

如果一切顺利,程序将打印1 products created

你可以通过运行select * from product; ,检查产品是否已经成功插入,你可以在MySQL查询浏览器中看到以下输出。

最后插入的ID

可能需要得到一个具有自动增量主键的插入查询的最后插入的ID。在我们的例子中,product_id 是一个自动递增的int主键。我们可能需要最后插入的产品ID,以便在其他表中引用。例如,我们有一个供应商表,并希望在创建新产品时映射供应商。在这种情况下,获取最后插入的ID是必不可少的。可以使用结果集的LastInsertId 方法来获取这个ID。在insert 函数的末尾,在return nil 之前添加以下代码。

func insert(db *sql.DB, p product) error {  
...

    prdID, err := res.LastInsertId()
    if err != nil {
    log.Printf("Error %s when getting last inserted product",     err)
    return err
    }
    log.Printf("Product with ID %d created", prdID)
    return nil
}

当加入上述代码的程序运行时,将打印出Product with ID 2 created 行。我们可以看到,最后插入的产品的ID是2

插入多行

让我们把我们的插入语句提高到一个新的水平,并尝试使用一个查询来插入多行。

以下是插入多行的MySQL语法

insert into product(product_name, product_price) values ("Galaxy","990"),("iPad","500")  

要插入的不同行用逗号分开。让我们看看如何用Go实现这个目标。

其逻辑是根据需要插入的产品数量,在查询的values 部分之后动态地生成("Galaxy","990"),("iPad","500") 。在本例中,需要插入两个产品,即GalaxyiPad 。因此,有必要生成一个格式如下的准备好的语句模板。

insert into product(product_name, product_price) values (?,?),(?,?)  

让我们马上写一个函数来完成这个任务。

func multipleInsert(db *sql.DB, products []product) error {  
    query := "INSERT INTO product(product_name, product_price) VALUES "
    var inserts []string
    var params []interface{}
    for _, v := range products {
        inserts = append(inserts, "(?, ?)")
        params = append(params, v.name, v.price)
    }
}

我们遍历传递给函数的products 参数,对于每个产品,我们在第6行将(?, ?) 添加到inserts 分段。6.在同样的for 循环中,我们将实际的参数追加到? ,以代替问号params 片断。
在准备好的语句模板之前,还有一个步骤。inserts 片段的长度为2,它包含(?, ?)(?, ?) 。这两个片段必须在中间用逗号连接起来。可以用Join来做这件事。它把一个字符串片断和一个分隔符作为参数,用分隔符连接片断中的元素。

queryVals := strings.Join(inserts, ",")  
query = query + queryVals  

queryVals现在包含(?, ?),(?, ?) 。然后我们将queryqueryVals 连接起来,生成最后的准备好的语句模板INSERT INTO product(product_name, product_price) VALUES (?, ?),(?, ?)

剩下的代码与单行插入函数类似。这里是完整的函数。

func multipleInsert(db *sql.DB, products []product) error {  
    query := "INSERT INTO product(product_name, product_price) VALUES "
    var inserts []string
    var params []interface{}
    for _, v := range products {
        inserts = append(inserts, "(?, ?)")
        params = append(params, v.name, v.price)
    }
    queryVals := strings.Join(inserts, ",")
    query = query + queryVals
    log.Println("query is", query)
    ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancelfunc()
    stmt, err := db.PrepareContext(ctx, query)
    if err != nil {
        log.Printf("Error %s when preparing SQL statement", err)
        return err
    }
    defer stmt.Close()
    res, err := stmt.ExecContext(ctx, params...)
    if err != nil {
        log.Printf("Error %s when inserting row into products table", err)
        return err
    }
    rows, err := res.RowsAffected()
    if err != nil {
        log.Printf("Error %s when finding rows affected", err)
        return err
    }
    log.Printf("%d products created simulatneously", rows)
    return nil
}

你可以看到的一个区别是在第20行。我们将切片作为一个变量参数传递,因为ExecContext 希望有一个变量参数。其余的代码是一样的。

在main函数的末尾添加以下几行,以调用multipleInsert 函数。

func main() {  
...

p1 := product{  
    name:  "Galaxy",
    price: 990,
}
p2 := product{  
    name:  "iPad",
    price: 500,
}
err = multipleInsert(db, []product{p1, p2})  
if err != nil {  
    log.Printf("Multiple insert failed with error %s", err)
    return
}

运行该程序时,你可以看到

query is INSERT INTO product(product_name, product_price) VALUES (?, ?),(?, ?)  
2 products created simultaneously  

打印出来了。 在查询该表时,可以确认插入了两个产品。

整个代码可在github.com/golangbot/m…

至此,本教程就结束了。请留下您的意见和反馈。