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)
}
关键点:
- 使用
godror.Lob类型处理BLOB数据 - 通过
Hijack()方法获取io.Reader来读取BLOB内容 - 使用
io.ReadAll()将BLOB数据读取到字节数组 - 正确处理对象属性的类型转换
- 注意资源的关闭和错误处理

