GO SQL驱动接口调用

285 阅读4分钟

SQL 驱动接口调用

Go官方没有提供数据库驱动,而是为开发数据库驱动定义了一些标准接口(即database/sql ),开发者可以根据定义的接口来开发相应的数据库驱动。Go中支持MySQL的驱动比较多,如

  • github.com/go-sql-driver/mysql 支持 database/sql
  • github.com/ziutek/mymysql 支持 database/sql,也支持自定义的接口
  • github.com/Philio/GoMySQL 不支持 database/sql,自定义接口

增删查改

  • 连接数据库
    func sql.Open(driverName string, dataSourceName string) (*sql.DB, error)
    
    • driverName :驱动名称,如mysql
    • DataSourceName:[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
      • IP 地址和接口号若为默认(如MySQL:localhost:3306)可省略
      • 要支持完整的UTF-8编码,需要将charset=utf8更改为charset=utf8mb4   想要正确的处理time.Time ,需要带上parseTime参数
      • loc=Local采用机器本地的时区,或者直接写死,比如loc=Asia%2FShanghai
    db, err := sql.Open("mysql", "tester:123456@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai")
    
  • DB 结构体
    type DB struct{
        /执行一次命令(查询、删除、更新、插入等),不返回任何执行结果。参数args表示query中的占位参数。
        Exec(query string, args ...interface{}) (Result, error)
        //执行一次查询,返回多行结果(即Rows),一般用于执行select命令。参数args表示query中的占位参数。
        Query(query string, args ...interface{}) (*Rows, error)
        //定义一句sql模板
        Prepare(query string) (*Stmt, error)
    }
    
  • Result 接口
    type Result interface {
         //返回由数据库执行插入操作得到的自增ID号。如果使用单个INSERT将多行插入到表中,则LastInsertId是第一条数据使用的id
        LastInsertId() (int64, error) 
        RowsAffected() (int64, error)  //返回操作影响的数据条目数
    }
    
  • Insert操作
    func insert(db *sql.DB){
            result,err := db.Exec("insert into student (name,province,city,enrollment) values ('小明', '深圳', '深圳', '2022-07-03'), ('小红', '上海', '上海', '2022-07-03')")
            lastInsertID,err := result.LastInsertId()
            fmt.Printf("插入的第一个id为:%d\n",lastInsertID)
            rowAffected,err := result.RowsAffected()
            fmt.Printf("影响的行数为:%d\n",rowAffected)
    }
    
  • Rows 接口
    type Rows interface {
        Columns() []string  //查询所需要的表字段
        Close() error  //关闭迭代器
        Next(dest []Value) error  //返回下一条数据,把数据赋值给dest,dest里面的元素必须是 driver.Value的值。如果最后没数据了,Next 函数返回 io.EOF
        Scan(dest ...interface{}) error
    }
    
  • Query 操作
    func query(db *sql.DB){
            rows,err := db.Query("select id,name,city,score,enrollment from student where enrollment>=20220702 limit 5")
            CheckError(err)
            for rows.Next(){
                    var id int
                    var score float32
                    var name, city string
                    var enrollment time.Time
                    err := rows.Scan(&id, &name,&city,&score,&enrollment)
                    CheckError(err)
                    fmt.Printf("id=%d, score=%.2f, name=%s, city=%s, enrollment=%s \n", id, score, name, city, enrollment.Format("2006-01-02 15:04:05"))
            }
    }
    

Statement

sql 注入

  • 首先看两个sql注入攻击的例子。

    sql = "select username,password from user where username='" + username + "' and password='" + password + "'"; 
    
    • 变量usernamepassword从前端输入框获取,如果用户输入的usernamelilypasswordaaa' or '1'='1,则完整的sql语句 为select username,password from user where username='lily' and password='aaa' or '1'='1'。会返回表里的所有记录,如果记录数大于0就允许登录,则lily的账号被盗。
    sql="insert into student (name) values ('"+username+" ') ";
    

  变量username从前端输入框获取,如果用户输入的usernamelily'); drop table student;--。完整sql语句 为insert into student (name) values ('lily'); drop table student;--')。通过注释符--屏蔽掉了末尾的'),删除了整个表。

  • 防止sql注入的方法:
    • 前端输入要加正则校验、长度限制。
    • 对特殊符号(<>&*; '"等)进行转义或编码转换,Go 的text/template 包里面的HTMLEscapeString 函数可以对字符串进行转义处理。
    • 不要将用户输入直接嵌入到sql 语句中,而应该使用参数化查询接口,如Prepare、Query、Exec(query string, args ...interface{})。
    • 使用专业的SQL 注入检测工具进行检测,如sqlmap、SQLninja。
    • 避免网站打印出SQL错误信息,以防止攻击者利用这些错误信息进行SQL注入。

sql 预编译

   DB执行sql分为3步:

  1. 词法和语义解析。
  2. 优化SQL语句,制定执行计划。
  3. 执行并返回结果。
  • SQL 预编译技术是指将用户输入用占位符?代替,先对这个模板化的sql进行预编译,实际运行时再将用户输入代入。除了可以防止SQL注入,还可以对预编译的SQL语句进行缓存,之后的运行就省去了解析优化SQL语句的过程。
  • Stmt 接口
    type Stmt interface {
        Close() error  //关闭当前的链接状态
        NumInput() int  //返回当前预留参数的个数
        Exec(args []Value) (Result, error)  //执行Prepare准备好的 sql,传入参数执行 update/insert 等操作,返回 Result 数据
        Query(args []Value) (Rows, error)  //执行Prepare准备好的 sql,传入需要的参数执行 select 操作,返回 Rows 结果集
    }
    
  • 通过Stmt 修改记录
    const TIME_LAYOUT = "2006-01-02"
    
    var (
            loc *time.Location
    )
    
    func init() {
            loc, _ = time.LoadLocation("Asia/Shanghai")
    }
    func replace (db *sql.DB){
            stmt, err := db.Prepare("replace into student(name,province,city,enrollment) values(?,?,?,?),(?,?,?,?)")
            CheckError(err)
            date1, _ := time.ParseInLocation(TIME_LAYOUT, "2021-04-18", loc)
            date2, _ := time.ParseInLocation(TIME_LAYOUT, "2021-04-26", loc)
            res, err := stmt.Exec("小明", "深圳", "深圳", date1, "小红", "上海", "上海", date2)
            funcResult(res) //即前面对result的操作
    }