在实际项目中,常常需要将图片或文件以二进制方式存储至数据库中,并能正确读取还原为文件。本文以 C 语言配合 MySQL C API 为例,完整演示如何实现将一张 JPG 图片写入数据库并再读出生成新图片文件的过程。
我们使用如下表结构:
-- 创建用户信息表
CREATE TABLE TBL_USER (
U_ID INT PRIMARY KEY AUTO_INCREMENT,
-- 用户编号,整型,主键,自动递增,系统自动分配唯一ID
U_NAME VARCHAR(50),
-- 用户名称,字符串类型,最大50个字符,用于存储用户名
U_GENGDER VARCHAR(10),
-- 用户性别,字符串类型,最大10个字符,例如 'man'、'woman' 等
U_IMG LONGBLOB
-- 用户图片,超大二进制对象类型,最大可存储4GB数据,适合存储图片、文件、音视频等二进制数据
);
其中 U_IMG
为图片字段。注意:字段类型需为 LONGBLOB
才能容纳较大图片(最大可达 4GB)。若你使用的是 BLOB
(最大 64KB),遇到较大图片会报错甚至发生栈溢出(stack smashing)!
┌───────────┐
│ 1. 连接数据库
└───────────┘
↓
┌────────────────────┐
│ 2. 插入用户普通信息(不含图片)
└────────────────────┘
↓
┌─────────────────────┐
│ 3. 查询用户信息并打印(验证表结构)
└─────────────────────┘
↓
┌──────────────────┐
│ 4. 读取本地图片到内存 buffer
└──────────────────┘
↓
┌───────────────────────────┐
│ 5. 通过 mysql_stmt 预处理存入图片
└───────────────────────────┘
↓
┌────────────────────────┐
│ 6. 从数据库读取图片二进制数据到内存
└────────────────────────┘
↓
┌──────────────────────┐
│ 7. 将内存数据写入新图片文件,还原图片
└──────────────────────┘
mysql_init(&mysql); // 初始化句柄
mysql_real_connect(&mysql, IP, USER, PWD, DB, PORT, NULL, 0); // 连接数据库
用 mysql_init
初始化连接
用 mysql_real_connect
建立与 MySQL 的 TCP 连接
mysql_real_query(&mysql, SQL_INSERT_TBL_USER, strlen(SQL_INSERT_TBL_USER));
执行普通 SQL 语句,插入一条用户数据(不含图片)
king_mysql_select(&mysql);
自定义查询函数:
调用 mysql_real_query
执行 SELECT * FROM TBL_USER
mysql_store_result
把结果缓存到客户端
mysql_fetch_row
遍历每一行,打印每个字段内容
char buffer[FILE_IMAGE_LENGTH] = {0};
int length = read_image("2.jpg", buffer);
if(length < 0) goto Exit;
作用:
通过 fopen
+ fread
将本地 2.jpg
读入内存数组 buffer
返回实际读取的图片字节数
关键点:
确保 buffer 大小足够大(FILE_IMAGE_LENGTH
需大于图片实际大小)
若图片过大,BLOB字段类型必须调整(详见注意事项)
mysql_write(&mysql, buffer, length);
作用:
使用 MYSQL_STMT
结构体准备预处理 SQL
通过参数绑定与 mysql_stmt_send_long_data
安全传输图片二进制数据
关键点:
SQL 语句用 ?
占位符
MYSQL_BIND
结构体指定 MYSQL_TYPE_LONG_BLOB
类型
使用 mysql_stmt_send_long_data
分块发送大数据,避免 SQL 注入
length = mysql_read(&mysql, buffer, FILE_IMAGE_LENGTH);
作用:
预处理 SELECT
语句获取图片字段 U_IMG
循环使用 mysql_stmt_fetch_column
按块读取数据到 buffer
关键点:
需要提前设置 result.length
存储总字节数
注意 offset 偏移计算,确保数据完整拼接到内存
write_image("a.jpg", buffer, length);
作用:
调用 fopen
+ fwrite
将内存数据写入 a.jpg
实现数据库图片数据还原为本地文件
关键点:
确保内存数据完整,文件长度正确
写入成功后可以直接打开 a.jpg
查看效果
#include
#include
#include
#define KING_DB_SERVER_IP "10.0.0.129" // 数据库IP
#define KING_DB_SERVER_PORT 3306 // 数据库端口
#define KING_DB_USERNAME "admin" // 数据库用户名
#define KING_DB_PASSWORD "123456" // 数据库密码
#define KING_DB_DEFAULTDB "KING_DB" // 默认库名
// 插入普通用户信息
#define SQL_INSERT_TBL_USER "INSERT TBL_USER(U_NAME,U_GENGDER) VALUES('charon','man');"
// 查询所有用户信息
#define SQL_SELECT_TBL_USER "SELECT * FROM TBL_USER;"
// 调用存储过程删除用户
#define SQL_DELETE_TBL_USER "CALL PROC_DELETE_USER('charon')"
// 插入含图片的用户信息(U_IMG字段用 ? 占位)
#define SQL_INSERT_IMG_USER "INSERT TBL_USER(U_NAME,U_GENGDER,U_IMG) VALUES('charon','man',?)"
// 查询图片字段
#define SQL_SELECT_IMG_USER "SELECT U_IMG FROM TBL_USER WHERE U_NAME = 'charon';"
#define FILE_IMAGE_LENGTH (64*1024*10) // 设定图片缓冲区大小,视实际图片大小调整
/**
* 查询并打印所有用户信息
*/
int king_mysql_select(MYSQL *handle) {
if(mysql_real_query(handle, SQL_SELECT_TBL_USER, strlen(SQL_SELECT_TBL_USER))){
printf("mysql_real_query : %s\n", mysql_error(handle));
return -1;
}
MYSQL_RES *res = mysql_store_result(handle);
if(res == NULL){
printf("mysql_store_result : %s\n", mysql_error(handle));
return -2;
}
int rows = mysql_num_rows(res);
printf("rows: %d\n", rows);
int fields = mysql_num_fields(res);
printf("fields: %d\n", fields);
MYSQL_ROW row;
while((row = mysql_fetch_row(res))){
for(int i = 0; i < fields; i++){
printf("%s\t", row[i]);
}
printf("\n");
}
mysql_free_result(res);
return 0;
}
/**
* 读取本地图片文件到内存
*/
int read_image(char *filename, char *buffer){
if(filename == NULL || buffer == NULL) return -1;
FILE *fp = fopen(filename, "rb");
if(fp == NULL){
printf("open file %s failed\n", filename);
return -2;
}
fseek(fp, 0, SEEK_END);
int length = ftell(fp);
fseek(fp, 0, SEEK_SET);
int size = fread(buffer, 1, length, fp);
if(size != length){
printf("read file %d failed\n", size);
return -3;
}
fclose(fp);
return size;
}
/**
* 将内存中的数据写入本地图片文件
*/
int write_image(char *filename, char *buffer, int length){
if(filename == NULL || buffer == NULL || length <= 0) return -1;
FILE *fp = fopen(filename, "wb+");
if(fp == NULL){
printf("open file %s failed\n", filename);
return -2;
}
int size = fwrite(buffer, 1, length, fp);
if(size != length){
printf("write file %d failed\n", size);
return -3;
}
fclose(fp);
return size;
}
/**
* 使用预处理将图片数据写入数据库
*/
int mysql_write(MYSQL *handle, char *buffer, int length){
if(handle == NULL || buffer == NULL || length <= 0) return -1;
MYSQL_STMT *stmt = mysql_stmt_init(handle);
int ret = mysql_stmt_prepare(stmt, SQL_INSERT_IMG_USER, strlen(SQL_INSERT_IMG_USER));
if(ret){
printf("mysql_stmt_prepare : %s\n", mysql_error(handle));
return -2;
}
MYSQL_BIND param = {0};
param.buffer_type = MYSQL_TYPE_LONG_BLOB;
param.buffer = buffer; // 数据指针
param.is_null = 0;
param.length = NULL; // 长度通过 send_long_data 传递
ret = mysql_stmt_bind_param(stmt, ¶m);
if(ret){
printf("mysql_stmt_bind_param : %s\n", mysql_error(handle));
return -3;
}
ret = mysql_stmt_send_long_data(stmt, 0, buffer, length);
if(ret){
printf("mysql_stmt_send_long_data : %s\n", mysql_error(handle));
return -4;
}
ret = mysql_stmt_execute(stmt);
if(ret){
printf("mysql_stmt_execute : %s\n", mysql_error(handle));
return -5;
}
ret = mysql_stmt_close(stmt);
if(ret){
printf("mysql_stmt_close : %s\n", mysql_error(handle));
return -6;
}
return ret;
}
/**
* 从数据库读取图片数据到内存
*/
int mysql_read(MYSQL *handle, char *buffer, int length){
if(handle == NULL || buffer == NULL || length <= 0) return -1;
MYSQL_STMT *stmt = mysql_stmt_init(handle);
int ret = mysql_stmt_prepare(stmt, SQL_SELECT_IMG_USER, strlen(SQL_SELECT_IMG_USER));
if(ret){
printf("mysql_stmt_prepare : %s\n", mysql_error(handle));
return -2;
}
MYSQL_BIND result = {0};
result.buffer_type = MYSQL_TYPE_LONG_BLOB;
unsigned long total_length = 0;
result.length = &total_length;
ret = mysql_stmt_bind_result(stmt, &result);
if(ret){
printf("mysql_stmt_bind_result : %s\n", mysql_error(handle));
return -3;
}
ret = mysql_stmt_execute(stmt);
if(ret){
printf("mysql_stmt_execute : %s\n", mysql_error(handle));
return -4;
}
ret = mysql_stmt_store_result(stmt);
if(ret){
printf("mysql_stmt_store_result : %s\n", mysql_error(handle));
return -5;
}
while (1){
ret = mysql_stmt_fetch(stmt);
if(ret != 0 && ret != MYSQL_DATA_TRUNCATED) break;
int start = 0;
while(start < (int)total_length){
result.buffer = buffer + start;
result.buffer_length = 1; // 逐字节读取
mysql_stmt_fetch_column(stmt, &result, 0, start);
start += result.buffer_length;
}
}
mysql_stmt_close(stmt);
return total_length;
}
/**
* 主函数:整体流程控制
*/
int main() {
MYSQL mysql;
if(NULL == mysql_init(&mysql)){
printf("mysql_init : %s\n", mysql_error(&mysql));
return -1;
}
if(!mysql_real_connect(&mysql, KING_DB_SERVER_IP, KING_DB_USERNAME, KING_DB_PASSWORD,
KING_DB_DEFAULTDB, KING_DB_SERVER_PORT, NULL, 0)){
printf("mysql_real_connect : %s\n", mysql_error(&mysql));
goto Exit;
}
printf("case : mysql --> select\n");
if(mysql_real_query(&mysql, SQL_INSERT_TBL_USER, strlen(SQL_INSERT_TBL_USER))){
printf("mysql_real_query : %s\n", mysql_error(&mysql));
goto Exit;
}
king_mysql_select(&mysql);
printf("case : mysql --> read image and write mysql\n");
char buffer[FILE_IMAGE_LENGTH] = {0};
int length = read_image("2.jpg", buffer);
if(length < 0) goto Exit;
mysql_write(&mysql, buffer, length);
printf("case : mysql --> read mysql and write image\n");
memset(buffer, 0, FILE_IMAGE_LENGTH);
length = mysql_read(&mysql, buffer, FILE_IMAGE_LENGTH);
write_image("a.jpg", buffer, length);
Exit:
mysql_close(&mysql);
return 0;
}
https://github.com/0voice