sql数据库连接

前言

作为数据存储的数据库,近年来发展飞快,在早期的程序自我存储到后面的独立出中间件服务,再到集群,不可谓不快了。早期的sql数据库一枝独秀,到后面的Nosql,还有azure安全,五花八门,教人学不过来阿。

一、mysql数据库的golang操作

针对数据库操作,往往需要安装实体数据库和对应的数据库驱动,前者要实际安装配置,可参考菜鸟官网的mysql安装,后者则是下载安装第三方库即可。

jam@jam:~$ mysql -h127.0.0.1 -ujam -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 8.0.33 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| somedb             |
+--------------------+
3 rows in set (0.00 sec)

mysql> use somedb;
Database changed
mysql> show tables;
Empty set (0.01 sec)

mysql> 

这边已经准备好了本地数据库实体,也添加了somedb数据库和对应的jam用户,供golang的调用操作练习。

1.1 database/sql使用

用golang的database/sql接口来进行sql硬编码,后续得到的结果自己硬解码,不过golang需要下载数据库驱动go get github.com/go-sql-driver/mysql,下载好了以后,就是编写sql语句和定流程的事了。

大致程序流程就是连接数据库,调用exec类api来执行sql语句,在python用pymysql等第三方库操作的时候,还需要在插入和更新表数据的时候来个commit,golang倒是不用,就直接Exec,具体如下:

const (
	USERNAME = "jam"
	PWD      = "abaaba"
	NETWORK  = "tcp"
	SERVER   = "127.0.0.1"
	PORT     = 3306
	DATABASE = "somedb"
)

type Student struct {
	Id   int
	Name string
}

func main() {
	conn := fmt.Sprintf("%s:%s@%s(%s:%d)/%s", USERNAME, PWD, NETWORK, SERVER, PORT, DATABASE)
	db, err := sql.Open("mysql", conn)
	if err != nil {
		fmt.Println("mysql数据库连接失败:", err)
		return
	}
	//设置连接存活时间
	db.SetConnMaxLifetime(60 * time.Second)
	//设置最大连接数
	db.SetMaxOpenConns(10)

	createTable(db)
	insert(db, 1, "小昏")
	insert(db, 2, "meiyi")
	insert(db, 3, "明也")
	query_single(db, 1)
	//更新数据
	update(db, 1, "baba")
	all_query(db)
	drop(db)
}

程序主体便是这样,几个执行sql语句根据语义包装成函数分开调用,另外因为用的是IDEA的goland,所以踩了不少的坑,关于数据库在ide中的配置出现了不少warning,有点烦。针对sql中的insert,update,delete操作还是使用db.Exec来对应调用:

// 插入,直接Exec一个sql也行
func insert(db *sql.DB, id int, name string) {
	sql_state, err := db.Prepare("insert into students values (?, ?)")
	res, err := sql_state.Exec(id, name)
	if err != nil {
		fmt.Println("插入失败:", err)
		return
	}
	lastInsertID, _ := res.LastInsertId()
	fmt.Println("插入id:", lastInsertID)
	rowsaffected, _ := res.RowsAffected()
	fmt.Println("affected rows:", rowsaffected)
}

// 修改
func update(db *sql.DB, id int, name string) {
	if res, err := db.Exec("update students set name=? where id=?", name, id); err != nil {
		fmt.Println("更新表失败:", err)
	} else {
		fmt.Println("更新结果:", res)
	}
}

// 删除指定id对应的行
func delete(db *sql.DB, id int) {
	if res, err := db.Exec("delete from students where id=?", id); err != nil {
		fmt.Println("删除失败:", err)
		return
	} else {
		fmt.Println("删除结果:", res)
	}
}

然后针对查询的就用db.Query一类接口:

// 查询一行
func query_single(db *sql.DB, id int) {
	student := new(Student)
	row := db.QueryRow("select * from students where id=?", id)
	if err := row.Scan(&student.Id, &student.Name); err != nil {
		fmt.Println("scan失败:", err)
		return
	}
	fmt.Println("第一行数据:", *student)
}

func all_query(db *sql.DB) {
	students := new(Student)
	rows, err := db.Query("select * from students")

	defer func() {
		if rows != nil {
			rows.Close()
		}
	}()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}
	for rows.Next() {
		err = rows.Scan(&students.Id, &students.Name)
		if err != nil {
			fmt.Println("scan失败:", err)
			return
		}
		fmt.Println("scan successed:", *students)
	}
}

另外还有创建表和删除表的操作,也还是用Exec,因为没返回嘛:

func createTable(db *sql.DB) {
	sql_s := `create table if not exists students ( 
        id int unsigned not null auto_increment primary key ,
        name varchar(10) not null unique
        )engine=InnoDB default charset=utf8;`
	if _, err := db.Exec(sql_s); err != nil {
		fmt.Println("创表失败:", err)
		return
	}
}

func drop(db *sql.DB) {
	if _, err := db.Exec("drop table students"); err != nil {
		fmt.Println("删表失败:", err)
	}
}

针对包引入部分,代码中是这样:

package main

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

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

但实际中关于golang项目还有不少go modules的设置,这里只是实际记录,不做项目前准备的记录。走完一圈,可以发现database/sql提供的也就只是一个连接和对应的exec执行sql语句的接口,对于专门的coder来说,需要另外兼顾sql中的语法,未免有点麻烦,所以有了ORM技术--各玩各的。

二、gorm操作mysql

ORM技术,就是让高级语言的代码编辑专注于该语言的语法,而不用去另外理会sql的语义,具体实现就是,用类似c中的struct,cpp中的class,golang中的struct这类存储结构性信息来对接数据库中的关系型数据,中间的转换问题由专门的库负责,python中的是sqlalchemy,golang中目前接触的就是gorm了。

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

在项目中go get一下下载gorm库和对应mysql驱动即可,不同数据库对应不同驱动。

连接一下,然后进行设置:

const (
	Host     = "127.0.0.1"
	User     = "jam"
	Pwd      = "abaaba"
	Port     = 3306
	Net      = "tcp"
	Database = "somedb"
)

//空配置
func main() {
	dst_uri := fmt.Sprintf("%s:%s@%s(%s:%d)/%s", User, Pwd, Net, Host, Port, Database)
	//简单无配置
	//db, err := gorm.Open(mysql.Open(dst_uri), &gorm.Config{})

	//配置mysql驱动
	//db, err := gorm.Open(mysql.New(mysql.Config{
	//	DSN:                       dst_uri,
	//	DefaultStringSize:         256,   //string类型字符串默认长度
	//	DisableDatetimePrecision:  true,  //禁用datetime精度
	//	DontSupportRenameIndex:    true,  //重命名索引时使用删除并新建的方式
	//	DontSupportRenameColumn:   true,  //用change重命名列
	//	SkipInitializeWithVersion: false, //是否根据当前mysql版本自适应配置
	//}), &gorm.Config{})

	//gorm
	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: dst_uri,
	}), &gorm.Config{
		SkipDefaultTransaction: false,
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名复数形式
			TablePrefix:   "t_", //表名前缀
		},
		DisableForeignKeyConstraintWhenMigrating: true, //设置逻辑外键,体现在代码上
	})

	//获取通用数据库对象sql.DB
	sql_db, err := db.DB()
	//设置连接池中空闲连接的最大数量
	sql_db.SetMaxIdleConns(10)
	//设置打开数据库连接的最大数
	sql_db.SetConnMaxLifetime(time.Hour)
	fmt.Println(sql_db, err)
}

建表

//通过自动迁移来建表,说白了就是提供好golang中的数据结构,然后调用接口给gorm就行,转换的问题交给gorm了
func auto_migrate(db *gorm.DB) {
	type book struct {
		id    int
		name  string
		price float32
	}

	err := db.AutoMigrate(&book{})
	if err != nil {
		fmt.Println(err)
	}
}

//手动迁移的方式建表
func migrate(db *gorm.DB) {
	type view struct {
		Id   int
		Name string
	}

	m := db.Migrator()
	if !m.HasTable(&view{}) {
		m.CreateTable(&view{})
	} else {
		fmt.Println("已存在同名表")
	}
}

测试了一下,自动迁移的方式会针对同名表的创建有个判断,判断同名表是否存在的步骤,其实自动迁移的实现就是内部调用了Migrator接口的一个方法:

func (db *DB) AutoMigrate(dst ...interface{}) error {
	return db.Migrator().AutoMigrate(dst...)
}
// Migrator migrator interface
type Migrator interface {
	//下一小节
}

记,作为gorm的golang结构体的构造,内部成员名要大写开头

后续操作,也是基于Migrator这个接口的基础,因为它有许多方法:

        ...
        //重命名
	m := db.Migrator()
	type view2 struct {
		Id   int
		Name string
	}
	m.RenameTable(&view{}, &view2{})
	fmt.Println("是否存在view2:", m.HasTable(&view2{}))
	m.RenameTable(&view2{}, "view3")

	type view3 struct {
		Id   int
		Name string
	}
        //删表
        m.DropTable(&view3{})
	fmt.Println("是否存在表view3:", m.HasTable(&view3{}))

插入,更新,删除,查询操作

        ...
	type view struct {
		Id   int
		Name string
	}
	type book struct {
		Id    int
		Name  string
		Price float32
	}

	//插入
	views := []view{{1, "wind"}, {2, "rain"}}
	db.Create(&views)
	//查询, 获取第一条记录,并且更新
	v := new(View)
	db.First(&v)
	fmt.Println(v.Id, v.Name)
	//更新
	v.Id = 4
	v.Name = "river"
	db.Save(&v)
	//再查找然后删除表记录
	db.Where("id=?", 3).Delete(&View{})

差不多就这样,更多api可以查看官方中文文档

另外,除了上面的mysql以外,还可以连接本地的sqlite数据库,下载对应驱动就行,也验证了上面的一个说法。

posted @ 2023-04-26 10:54  夏目&贵志  阅读(105)  评论(0编辑  收藏  举报