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数据库,下载对应驱动就行,也验证了上面的一个说法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程