Windows + Golang + Oracle 11g + GORM(改) + go-oci8 的数据库驱动测试(超详细)

一、系统环境介绍:

  • Windows 10
  • Oracle 11g 64bit
  • OCI -> instantclient-basic-windows.x64-12.2.0.1.0
  • OCI SDK -> instantclient-sdk-windows.x64-12.1.0.2.0
  • MinGW 64bit
  • go1.11 windows/amd64

注意:我在Oracle 11g版本下使用 OCI 及 SDK 11.2.0.4.0版本会报错,改用 OCI 12.2.0.1.0 及 SDK 12.1.0.2.0就可以了。

二、安装MinGW,注意安装系统相应的32/64位的版本

1、下载地址:

https://sourceforge.net/projects/mingw-w64/files/

2、安装MinGW
  • 双击安装程序


    双击安装程序
  • 修改Architecture为:x86_64,点击 Next > 直到安装完成。注意:安装路径,后面需要用到
点击 Next >
  • 安装完成目录结构


    安装完成
  • 设置环境变量Path,添加:D:\Tool\MinGW64\mingw64\bin
设置环境变量
复制位置

三、下载Oracle 的 OCI和SDK

1、下载OCI和SDK

https://www.oracle.com/technetwork/topics/winx64soft-089540.html

下载OCI和SDK文件

2、创建存放目录,如:C:\instantclient_11_2,并将上面的两个压缩文件解压到C:\instantclient_11_2中,如图:
解压的OCI和SDK
  • 设置环境变量Path,添加:C:\instantclient_11_2

    设置环境变量

  • D:\Tool\MinGW64\mingw64\lib\pkg-config目录下新建:oci8.pc文件,pkg-config目录也需要新建。注意修改你的OCI目录地址
    新建的目录结构

    目录结构

    修改oci8.pc中的OCI路径C:\instantclient_11_2

# Package Information for pkg-config
prefix=C:/instantclient_11_2
exec_prefix=C:/instantclient_11_2
libdir=${exec_prefix}
includedir=${prefix}/sdk/include/

Name: OCI
Description: Oracle database engine
Version: 11.2
Libs: -L${libdir} -loci
Libs.private: 
Cflags: -I${includedir}

设置环境变量PKG_CONFIG_PATH

image.png

四、gorm新增Dialect方言

1、在dialects目录下新增oci8/oracle.go文件夹及文件
image.png
oracle.go
package oci8

import _ "github.com/mattn/go-oci8"
2、在根目录下新增dialects_oracle.go文件
image.png
dialects_oracle.go
package gorm

import (
    "crypto/sha1"
    "fmt"
    "reflect"
    "regexp"
    "strconv"
    "strings"
    "time"
    "unicode/utf8"
)

type oracle struct {
    commonDialect
}

func init() {
    RegisterDialect("oci8", &oracle{})
}

func (oracle) GetName() string {
    return "oci8"
}

func (oracle) Quote(key string) string {
    return fmt.Sprintf("\"%s\"", strings.ToUpper(key))
}

func (oracle) SelectFromDummyTable() string {
    return "FROM dual"
}

func (oracle) BindVar(i int) string {
    return fmt.Sprintf(":%d", i)
}

func (s *oracle) DataTypeOf(field *StructField) string {
    var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s)

    if sqlType == "" {
        switch dataValue.Kind() {
        case reflect.Bool:
            sqlType = "CHAR(1)"
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
            sqlType = "INTEGER"
        case reflect.Int64, reflect.Uint64:
            sqlType = "NUMBER"
        case reflect.Float32, reflect.Float64:
            sqlType = "FLOAT"
        case reflect.String:
            if size > 0 && size < 255 {
                sqlType = fmt.Sprintf("VARCHAR(%d)", size)
            } else {
                sqlType = "VARCHAR(255)"
            }
        case reflect.Struct:
            if _, ok := dataValue.Interface().(time.Time); ok {
                sqlType = "TIMESTAMP"
            }
        }
    }

    if sqlType == "" {
        panic(fmt.Sprintf("invalid sql type %s (%s) for oci8", dataValue.Type().Name(), dataValue.Kind().String()))
    }

    if strings.TrimSpace(additionalType) == "" {
        return sqlType
    }
    return fmt.Sprintf("%v %v", sqlType, additionalType)
}

func (s oracle) HasIndex(tableName string, indexName string) bool {
    var count int
    s.db.QueryRow("SELECT COUNT(*) FROM USER_INDEXES WHERE TABLE_NAME = :1 AND INDEX_NAME = :2", strings.ToUpper(tableName), strings.ToUpper(indexName)).Scan(&count)
    return count > 0
}

func (s oracle) HasForeignKey(tableName string, foreignKeyName string) bool {
    var count int
    s.db.QueryRow("SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'R' AND TABLE_NAME = :1 AND CONSTRAINT_NAME = :2", strings.ToUpper(tableName), strings.ToUpper(foreignKeyName)).Scan(&count)
    return count > 0
}

func (s oracle) HasTable(tableName string) bool {
    var count int
    s.db.QueryRow("SELECT COUNT(*) FROM USER_TABLES WHERE TABLE_NAME = :1", strings.ToUpper(tableName)).Scan(&count)
    return count > 0
}

func (s oracle) HasColumn(tableName string, columnName string) bool {
    var count int
    s.db.QueryRow("SELECT COUNT(*) FROM USER_TAB_COLUMNS WHERE TABLE_NAME = :1 AND COLUMN_NAME = :2", strings.ToUpper(tableName), strings.ToUpper(columnName)).Scan(&count)
    return count > 0
}

func (oracle) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {

    if limit != nil {
        if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 {
            sql += fmt.Sprintf("ROWNUM <= %d", limit)
        }
    }
    return
}

func (s oracle) BuildKeyName(kind, tableName string, fields ...string) string {
    keyName := s.commonDialect.BuildKeyName(kind, tableName, fields...)
    if utf8.RuneCountInString(keyName) <= 30 {
        return keyName
    }
    h := sha1.New()
    h.Write([]byte(keyName))
    bs := h.Sum(nil)

    // sha1 is 40 digits, keep first 24 characters of destination
    destRunes := []rune(regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(fields[0], "_"))
    result := fmt.Sprintf("%s%x", string(destRunes), bs)
    if len(result) <= 30 {
        return result
    }
    return result[:29]
}

在团队开发项目中需要将 gorm 包迁移到项目中 vendor ,否则其他人无法使用 gorm oracle

image.png

五、数据操作测试

测试文件main_test.go,测试前注意先在ggs数据库中添加userinfo表及数据

package example

import (
    "database/sql"
    "encoding/json"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/oci8"
    _ "github.com/mattn/go-oci8"
    "testing"
)

type Userinfo struct {
    ID   int64  `gorm:"column:id" form:"id"`
    Name string `gorm:"column:name" form:"name"`
}

func (Userinfo) TableName() string {
    return "ggs.USERINFO"
}

var driverName = "oci8"
var dataSourceName = "system/123456@127.0.0.1:1521/ORCL"


// gorm model
func TestOracleDBByModel(t *testing.T) {
    db, err := gorm.Open(driverName, dataSourceName)
    defer db.Close()
    if err != nil {
        t.Error(err)
    }
    var list []Userinfo
    if err = db.Model(&Userinfo{}).Find(&list).Error; err != nil {
        t.Error(err)
    }
    if str, err := json.Marshal(list); err != nil {
        t.Error(err)
    } else {
        t.Log(string(str))
    }
    var count int64
    if err := db.Model(&Userinfo{}).Count(&count).Error; err != nil {
        t.Error(err)
    }
    t.Log("总记录数:", count)
}

// gorm sql
func TestOracleDB(t *testing.T) {
    db, err := gorm.Open(driverName, dataSourceName)
    defer db.Close()
    if err != nil {
        t.Error(err)
    }
    rows, err := db.Raw("select * from ggs.userinfo").Rows()
    if err != nil {
        t.Log(err)
    }
    defer rows.Close()
    for rows.Next() {
        var id int
        var name string
        rows.Scan(&id, &name)
        t.Log(id, name)  
    }
}

// 原生操作 database/sql
func TestMattnOracle(t *testing.T) {
    var db *sql.DB
    var err error
    if db, err = sql.Open(driverName, dataSourceName); err != nil {
        t.Error(err)
        return
    }
    var rows *sql.Rows
    if rows, err = db.Query("select * from ggs.userinfo"); err != nil {
        t.Error(err)
        return
    }
    defer rows.Close()
    for rows.Next() {
        var id int
        var name string
        rows.Scan(&id, &name)
        println(id, name)
    }
}
1、TestOracleDBByModel测试结果
=== RUN   TestOracleDBByModel
--- PASS: TestOracleDBByModel (0.05s)
    oracle_test.go:39: [{"ID":1,"Name":"张三"},{"ID":2,"Name":"李四"}]
    oracle_test.go:45: 总记录数: 2
PASS

Process finished with exit code 0
2、TestOracleDB测试结果
=== RUN   TestOracleDB
--- PASS: TestOracleDB (0.05s)
    oracle_test.go:64: 1 张三
    oracle_test.go:64: 2 李四
PASS

Process finished with exit code 0

3、TestMattnOracle测试结果

=== RUN   TestMattnOracle
--- PASS: TestMattnOracle (0.04s)
    oracle_test.go:86: 1 张三
    oracle_test.go:86: 2 李四
PASS

Process finished with exit code 0

六、错误问题及解决方案

1、OCI版本不一致,报错的相关内容:
In file included from D:/worktools/instantclient_11_2/sdk/include/oci.h:541, from ......\mattn\go-oci8\oci8.go:4: 
D:/worktools/instantclient_11_2/sdk/include/oratypes.h:236:25: error: expected ' =', ',', ';', 'asm' or 'attribute' before 'ubig_ora' typedef unsigned _int64 ubig_ora; ^~~~ 
D:/worktools/instantclient_11_2/sdk/include/oratypes.h:237:25: error: expected ' =', ',', ';', 'asm' or 'attribute' before 'sbig_ora' typedef signed _int64 sbig_ora; ^~~~ In file included from 
D:/worktools/instantclient_11_2/sdk/include/oci.h:3045, from ......\mattn\go-oci8\oci8.go:4: D:/worktools/instantclient_11_2/sdk/include/ociap.h:7459:40: error: unknown type name 'ubig_ora' OraText path, ubig_ora lenp ); ^~~~ 
D:/worktools/instantclient_11_2/sdk/include/ociap.h:7471:36: error: unknown type name 'ubig_ora' uword origin, ubig_ora offset, sb1 dir ); ^~~~ 
D:/worktools/instantclient_11_2/sdk/include/ociap.h:8278:23: error: unknown type name 'sbig_ora' sbig_ora bufferLength, sbig_ora returnLength, ^~~~ 
D:/worktools/instantclient_11_2/sdk/include/ociap.h:8278:46: error: unknown type name 'sbig_ora' sbig_ora bufferLength, sbig_ora returnLength

错误原因:OCI版本问题,我是在Oracle 11g版本下使用 OCI 及 SDK 11.2.0.4.0版本会报错
解决方法:改用 OCI 12.2.0.1.0 及 SDK 12.1.0.2.0就可以了。

2、fatal error: oci.h: No such file or directory
# github.com/mattn/go-oci8
/slview/nms/go/src/github.com/mattn/go-oci8/oci8.go:4:17: fatal error: oci.h: No such file or directory
 #include <oci.h>
                 ^
compilation terminated.

错误原因:D:\Tool\MinGW64\mingw64\lib\pkg-config\oci8.pc 配置文件中的路径未修改
解决方法:修改oci8.pc中的路径C:/instantclient_11_2为你的OCI存放路径,如下:

# Package Information for pkg-config
prefix=C:/instantclient_11_2
exec_prefix=C:/instantclient_11_2
libdir=${exec_prefix}
includedir=${prefix}/sdk/include/

Name: OCI
Description: Oracle database engine
Version: 11.2
Libs: -L${libdir} -loci
Libs.private: 
Cflags: -I${includedir}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269