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);
}
}
}
说明
文件路径:确保将
$filePath
替换为您要插入的实际文件路径。打开文件:使用
fopen()
以只读模式打开要写入的文件。使用游标插入 BLOB:使用
INSERT INTO ... RETURNING ... INTO
语句插入一个空的 BLOB,并将其返回到绑定的参数中。读取文件并写入流:
- 使用
fread()
按块读取文件数据(例如 8KB)。 - 使用
fwrite()
将读取的数据写入到 BLOB 流中。
- 使用
事务处理:使用事务来确保数据一致性。如果在写入过程中出现错误,可以回滚事务。
关闭资源:确保在操作完成后关闭文件和流,以释放资源。
在 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 协议》,转载必须注明作者和本文链接