Golang如何调用Oracle存储过程并处理自定义类型的输出参数

Golang如何调用Oracle存储过程并处理自定义类型的输出参数 大家好,

我需要调用一个Oracle存储过程,该过程有一个自定义类型的输出参数。该自定义类型的一个属性是BLOB。我无法从该属性获取BLOB内容。

我能够成功调用一个具有非BLOB、非CLOB列的自定义类型过程,并将值映射到Go结构体。但如果Oracle自定义类型包含BLOB属性,则无法正常工作。

以下是我的代码,有人能帮忙吗?

// Oracle 自定义类型

CREATE OR REPLACE TYPE  XX_TEST_OBJ  FORCE AS OBJECT
(
FILE_NAME      VARCHAR2 (200),
FILE_TYPE              VARCHAR2(30),
FILE_CONTENTS            BLOB
)

// Oracle 存储过程

CREATE OR REPLACE PROCEDURE xxtest_procedure(
p_cco_id             IN     VARCHAR2,
p_partner_name       IN     VARCHAR2,
p_tp_ref_num         IN     VARCHAR2,
p_shipind_docs_o        OUT XX_TEST_OBJ,
p_return_code_o         OUT VARCHAR2,
p_return_message_o      OUT VARCHAR2)
IS

l_shipind_docs_o XX_TEST_OBJ;

BEGIN

l_shipind_docs_o :=
XX_TEST_OBJ (NULL,
NULL,
NULL);

     SELECT file_name,file_type, file_contents
     INTO l_shipind_docs_o.file_name,l_shipind_docs_o.file_type,l_shipind_docs_o.file_contents
     FROM xx_test_table

WHERE file_type = ‘e-Invoice’;

p_shipind_docs_o := l_shipind_docs_o;
p_return_code_o := ‘SUCCESS’;
p_return_message_o := ‘SUCCESS’;

END;
/

// Go 代码

package main

import (

    "context"

    "database/sql"

    "fmt"

    "reflect"

    "github.com/godror/godror"

    _ "github.com/godror/godror"
)

// FileData 结构体

type FileData struct {

    *godror.Object

    FileName     string

    FileType     string

    FileContents []byte
}

// SetValues 函数

func (r *FileData) SetValues(src interface{}) error {

    //var reader godror.Lob

    obj, ok := src.(*godror.Object)

    if !ok {

        return fmt.Errorf("Cannot scan from type %T", src)

    }

    fName, err := obj.Get("FILE_NAME")

    if err != nil {

        return err

    }

    r.FileName = string(fName.([]byte))

    fType, err := obj.Get("FILE_TYPE")

    if err != nil {

        return err

    }

    r.FileType = string(fType.([]byte))

    fContents, err := obj.Get("FILE_CONTENTS")

    if err != nil {

        return err

    }

    ff, ok := (fContents.(*godror.Lob))

    fmt.Println(reflect.TypeOf(ff))

    fmt.Println(reflect.TypeOf(fType))

    fmt.Println(reflect.TypeOf(fName))

    ff.Hijack()

    fmt.Println(ff)

    fmt.Println(ok)

    return nil
}

func main() {

    db, err := sql.Open("godror", "xxeem/*****@ORAPOCDB")

    if err != nil {

        fmt.Println(err)

        return

    }

    defer db.Close()

    fmt.Println("Before Prepare Statement")

    stmt, err := db.Prepare("BEGIN xxtest_procedure( :p_cco_id,:p_partner_name,:p_tp_ref_num,:p_shipind_docs_o,:p_return_code_o,:p_return_message_o); END;")

    fmt.Println("After Prepare", err)

    var code, message string

    conn, err := db.Conn(context.TODO())

    var lFileData FileData

    fObjectType, err := godror.GetObjectType(context.TODO(), conn, "XX_TEST_OBJ")

    fObject, err := fObjectType.NewObject()

    attr0, _ := fObject.Attributes["FILE_NAME"]

    attr1, _ := fObject.Attributes["FILE_TYPE"]

    attr2, _ := fObject.Attributes["FILE_CONTENTS"]

    //fObject.OracleTypeNum = C.DPI_ORACLE_TYPE_LONG_VARCHAR

    //fObject.NativeTypeNum = 2

    fmt.Println(err)

    fmt.Println(code)

    fmt.Println(message)

    fmt.Println("Before Execute Statement")

    _, err = stmt.ExecContext(context.TODO(),

        godror.LobAsReader(),

        sql.Named("p_cco_id", "Test"),

        sql.Named("p_partner_name", "Test"),

        sql.Named("p_tp_ref_num", "Test"),

        sql.Named("p_shipind_docs_o", sql.Out{Dest: fObject}),

        sql.Named("p_return_code_o", sql.Out{Dest: &code}),

        sql.Named("p_return_message_o", sql.Out{Dest: &message}),

    )

    defer stmt.Close()

    fmt.Println(fObject)

    fmt.Println(code)

    fmt.Println(message)

    fmt.Println("Before Scan")

    lFileData.SetValues(fObject)

    fmt.Println("After Scan")

    fmt.Println("File Name Native Type : ", attr0.NativeTypeNum)

    fmt.Println("File Name Oracle Type : ", attr0.OracleTypeNum)

    fmt.Println("File Type Native Type : ", attr1.NativeTypeNum)

    fmt.Println("File Type Oracle Type : ", attr1.OracleTypeNum)

    fmt.Println("File Contents Native Type : ", attr2.NativeTypeNum)

    fmt.Println("File Contents Oracle Type : ", attr2.OracleTypeNum)
}

更多关于Golang如何调用Oracle存储过程并处理自定义类型的输出参数的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang如何调用Oracle存储过程并处理自定义类型的输出参数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中调用Oracle存储过程并处理包含BLOB的自定义类型输出参数,需要使用godror库的特殊处理方式。以下是解决方案:

package main

import (
    "context"
    "database/sql"
    "fmt"
    "io"
    "github.com/godror/godror"
    _ "github.com/godror/godror"
)

type FileData struct {
    FileName     string
    FileType     string
    FileContents []byte
}

func main() {
    db, err := sql.Open("godror", "xxeem/*****@ORAPOCDB")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer db.Close()

    ctx := context.Background()
    conn, err := db.Conn(ctx)
    if err != nil {
        fmt.Println("Connection error:", err)
        return
    }
    defer conn.Close()

    // 获取对象类型
    objType, err := godror.GetObjectType(ctx, conn, "XX_TEST_OBJ")
    if err != nil {
        fmt.Println("GetObjectType error:", err)
        return
    }

    // 创建对象实例
    obj, err := objType.NewObject()
    if err != nil {
        fmt.Println("NewObject error:", err)
        return
    }
    defer obj.Close()

    var code, message string
    
    // 准备调用语句
    stmt, err := conn.PrepareContext(ctx, 
        "BEGIN xxtest_procedure(:1, :2, :3, :4, :5, :6); END;")
    if err != nil {
        fmt.Println("Prepare error:", err)
        return
    }
    defer stmt.Close()

    // 执行存储过程
    _, err = stmt.ExecContext(ctx,
        "Test",           // p_cco_id
        "Test",           // p_partner_name
        "Test",           // p_tp_ref_num
        sql.Out{Dest: &obj}, // p_shipind_docs_o
        sql.Out{Dest: &code}, // p_return_code_o
        sql.Out{Dest: &message}, // p_return_message_o
    )
    if err != nil {
        fmt.Println("Exec error:", err)
        return
    }

    // 从对象中提取数据
    var fileData FileData
    
    // 获取FILE_NAME
    fileNameVal, err := obj.Get("FILE_NAME")
    if err != nil {
        fmt.Println("Get FILE_NAME error:", err)
        return
    }
    if fileNameBytes, ok := fileNameVal.([]byte); ok {
        fileData.FileName = string(fileNameBytes)
    }

    // 获取FILE_TYPE
    fileTypeVal, err := obj.Get("FILE_TYPE")
    if err != nil {
        fmt.Println("Get FILE_TYPE error:", err)
        return
    }
    if fileTypeBytes, ok := fileTypeVal.([]byte); ok {
        fileData.FileType = string(fileTypeBytes)
    }

    // 获取FILE_CONTENTS (BLOB)
    fileContentsVal, err := obj.Get("FILE_CONTENTS")
    if err != nil {
        fmt.Println("Get FILE_CONTENTS error:", err)
        return
    }

    // 处理BLOB数据
    if lob, ok := fileContentsVal.(*godror.Lob); ok {
        // 方法1: 直接读取到字节数组
        blobData, err := io.ReadAll(lob)
        if err != nil {
            fmt.Println("Read BLOB error:", err)
            return
        }
        fileData.FileContents = blobData
        
        // 方法2: 使用Hijack获取底层Reader
        // reader := lob.Hijack()
        // blobData, err := io.ReadAll(reader)
        // if err != nil {
        //     fmt.Println("Read BLOB error:", err)
        //     return
        // }
        // fileData.FileContents = blobData
    }

    fmt.Println("Return Code:", code)
    fmt.Println("Return Message:", message)
    fmt.Println("File Name:", fileData.FileName)
    fmt.Println("File Type:", fileData.FileType)
    fmt.Printf("File Contents Size: %d bytes\n", len(fileData.FileContents))
}

对于BLOB属性的处理,这里提供另一种更详细的实现方式:

func getBlobFromObject(obj *godror.Object, attrName string) ([]byte, error) {
    val, err := obj.Get(attrName)
    if err != nil {
        return nil, fmt.Errorf("failed to get attribute %s: %w", attrName, err)
    }

    switch v := val.(type) {
    case *godror.Lob:
        // 使用Hijack获取Reader
        reader := v.Hijack()
        defer v.Close()
        
        data, err := io.ReadAll(reader)
        if err != nil {
            return nil, fmt.Errorf("failed to read BLOB: %w", err)
        }
        return data, nil
        
    case []byte:
        // 如果已经是字节数组,直接返回
        return v, nil
        
    default:
        return nil, fmt.Errorf("unexpected type for BLOB: %T", v)
    }
}

// 在main函数中使用
func main() {
    // ... 前面的代码相同
    
    // 获取BLOB内容
    blobData, err := getBlobFromObject(obj, "FILE_CONTENTS")
    if err != nil {
        fmt.Println("Error getting BLOB:", err)
        return
    }
    
    fileData.FileContents = blobData
    
    // 输出结果
    fmt.Printf("Successfully retrieved file: %s (Type: %s, Size: %d bytes)\n",
        fileData.FileName, fileData.FileType, len(fileData.FileContents))
}

如果遇到BLOB为空的情况,可以添加空值检查:

func getBlobFromObjectSafe(obj *godror.Object, attrName string) ([]byte, error) {
    val, err := obj.Get(attrName)
    if err != nil {
        return nil, fmt.Errorf("failed to get attribute %s: %w", attrName, err)
    }

    // 检查是否为空
    if val == nil {
        return nil, nil
    }

    if lob, ok := val.(*godror.Lob); ok {
        // 检查Lob是否为空
        if lob == nil || lob.IsNull() {
            return nil, nil
        }
        
        reader := lob.Hijack()
        defer lob.Close()
        
        data, err := io.ReadAll(reader)
        if err != nil {
            return nil, fmt.Errorf("failed to read BLOB: %w", err)
        }
        return data, nil
    }
    
    return nil, fmt.Errorf("unexpected type for BLOB: %T", val)
}

关键点:

  1. 使用godror.Lob类型处理BLOB数据
  2. 通过Hijack()方法获取io.Reader来读取BLOB内容
  3. 使用io.ReadAll()将BLOB数据读取到字节数组
  4. 正确处理对象属性的类型转换
  5. 注意资源的关闭和错误处理
回到顶部