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数据库,下载对应驱动就行,也验证了上面的一个说法。