Oracle中处理超大字符串文本blob为空处理方案

问题:YII框架存储超大报文压缩后写入stream流存储到Oracle数据库一直显示blob 0 bytes(null),使用PDO的bindParam无效,无法转换数据

解决方案:

将超大文件转二进制流之后,转成十六进制,或者存储成二进制流形式,分割之后,写入stream,同时开启事务提交(务必使用事务,否则PDO::PARAM_LOB无法生效),本文提供两个方案

以下是一个示例,展示了如何在 Yii 中将超大二进制流写入 Oracle 数据库的 BLOB 字段

在 Yii 框架中,将超大二进制流写入 Oracle 数据库中的 BLOB 字段,通常需要使用游标(Cursor)来处理。对于大文件,推荐使用分块(chunking)的方法,以避免内存溢出的问题。

方案一:(采用文件形式)

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class BlobController extends Controller
{
    public function actionUploadBlob()
    {
        // 数据库连接信息
        $connection = Yii::$app->db;

        // 假设这是您要写入的文件路径
        $filePath = 'path/to/your/largefile.bin'; // 替换为您的文件路径

        // 检查文件是否存在
        if (!file_exists($filePath)) {
            throw new NotFoundHttpException("文件未找到: $filePath");
        }

        // 打开文件
        $file = fopen($filePath, 'rb');
        if ($file === false) {
            throw new \Exception("无法打开文件: $filePath");
        }

        // 使用游标插入 BLOB
        $sql = "INSERT INTO your_table (your_blob_column) VALUES (EMPTY_BLOB()) RETURNING your_blob_column INTO :blobData";
        $command = $connection->createCommand($sql);

        // 创建一个流
        $blobStream = fopen('php://temp', 'rb+'); // 创建一个内存流
        if ($blobStream === false) {
            fclose($file);
            throw new \Exception("无法创建内存流");
        }

        // 绑定参数
        $command->bindParam(':blobData', $blobStream, \PDO::PARAM_LOB);

        // 开始事务
        $transaction = $connection->beginTransaction();
        try {
            // 执行插入
            $command->execute();

            // 从文件中读取数据并写入流
            while (!feof($file)) {
                $chunk = fread($file, 8192); // 读取 8KB 数据
                fwrite($blobStream, $chunk); // 写入到 BLOB 流
            }

            // 将流指针移动到开头
            rewind($blobStream);

            // 提交事务
            $transaction->commit();
            echo "BLOB 数据成功插入到数据库中。";
        } catch (\Exception $e) {
            $transaction->rollBack();
            echo "错误: " . $e->getMessage();
        } finally {
            // 关闭文件和流
            fclose($file);
            fclose($blobStream);
        }
    }
}

说明

  1. 文件路径:确保将 $filePath 替换为您要插入的实际文件路径。

  2. 打开文件:使用 fopen() 以只读模式打开要写入的文件。

  3. 使用游标插入 BLOB:使用 INSERT INTO ... RETURNING ... INTO 语句插入一个空的 BLOB,并将其返回到绑定的参数中。

  4. 读取文件并写入流

    • 使用 fread() 按块读取文件数据(例如 8KB)。
    • 使用 fwrite() 将读取的数据写入到 BLOB 流中。
  5. 事务处理:使用事务来确保数据一致性。如果在写入过程中出现错误,可以回滚事务。

  6. 关闭资源:确保在操作完成后关闭文件和流,以释放资源。

在 PHP 中,如果您需要将超大二进制流分块写入文件,可以使用 fopen()fwrite()fclose() 函数来实现。以下是一个示例,展示了如何将一个大文件分块写入到目标文件中。

示例代码

<?php
// 源文件路径
$sourceFilePath = 'path/to/your/largefile.bin'; // 替换为您的源文件路径
// 目标文件路径
$destinationFilePath = 'path/to/your/outputfile.bin'; // 替换为您的目标文件路径

// 设置每块的大小(以字节为单位)
$chunkSize = 8192; // 8 KB

// 打开源文件以读取
$sourceFile = fopen($sourceFilePath, 'rb');
if ($sourceFile === false) {
    die("无法打开源文件: $sourceFilePath");
}

// 打开目标文件以写入
$destinationFile = fopen($destinationFilePath, 'wb');
if ($destinationFile === false) {
    fclose($sourceFile);
    die("无法创建目标文件: $destinationFilePath");
}

// 分块读取源文件并写入目标文件
while (!feof($sourceFile)) {
    // 从源文件读取一块数据
    $chunk = fread($sourceFile, $chunkSize);
    if ($chunk === false) {
        fclose($sourceFile);
        fclose($destinationFile);
        die("读取源文件时出错");
    }

    // 将读取的数据写入目标文件
    fwrite($destinationFile, $chunk);
}

// 关闭文件
fclose($sourceFile);
fclose($destinationFile);

echo "文件成功分块写入到目标文件。";
?>

方案二:(采用报文->压缩->Stream->HEX->chunk->bin->fwrite)[依然需要借助事务提交参数]

<?php
try {
            // 开始事务
            /** @var \yii\db\Transaction $transaction */
            $transaction = $db->beginTransaction();
            $updateSql = <<<SQL
update pboc_report_record set response_data = EMPTY_BLOB(), status=:status, updated_at=:now where id = :id RETURNING response_data INTO :blobData
SQL;

            $command = $db->createCommand($updateSql);

            // 创建一个流
            $blobStream = fopen('php://temp', 'rb+'); // 创建一个内存流

            // 打开文件
            if ($blobStream === false) {
                throw new PbocException('进件ID:【'.$orderId.'】创建内存流失败:'.$name.'_'.$idNo,
                    Error::ERROR_PBOC_FAIL);
            }
            $command->bindParam(':blobData', $blobStream, \PDO::PARAM_LOB);
            $command->bindParam(':id', $pbocReportRecordId, \PDO::PARAM_INT);
            $command->bindParam(':now', $now);
            $command->bindParam(':status', $validStatus);

            // 从文件中读取数据并写入流
            $chunks = $this->chunkHexStr($responseData);
            foreach ($chunks as $val) {
                $binStr = hex2bin($val);
                // 写入超大文件
                fwrite($blobStream, $binStr);
            }

            // 将流指针移动到开头
            rewind($blobStream);

            // 执行插入
            $rows = $command->execute();

            if ($rows <= 0) {
                throw new PbocException('更新RECORD记录失败:',
                    Error::ERROR_PBOC_FAIL);
            }

            // 提交事务
            $transaction->commit();

        } catch (Exception $exception) {
            $transaction->rollBack();
            $msg = '进件ID['.$orderId.']更新RECORD记录失败异常,请联系技术排查处理'.$name.'_'.$idNo;
            Log::error($msg, [
                'error' => $exception->getMessage(),
                'sql' => $updateSql ?? null,
                'error_msg' => $exception->getMessage(),
            ]);
        } finally {
            if (isset($blobStream) && $blobStream !== false) {
                fclose($blobStream);
            }
            $transaction->rollBack();
        }

总结

以上两种方案均可实现对Oracle超大文本转二进制流的写入方式,如果有更好的方式,可以留言或者沟通,以上方案如果是小报文不使用事务也可以实现,长度限制在65535以内,是因为oracle自身限制

本作品采用《CC 协议》,转载必须注明作者和本文链接
每天一点小知识,到那都是大佬,哈哈
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!