您当前的位置: 首页 >  sql

将大文件存到Mysql数据库

发布时间:2019-08-09 11:28:15 ,浏览量:5

一、引言

先描述一下需求:

大前提在前面的文章将大文件存到Oracle数据库中已经描述过,不过又要新增一个微服务,数据库使用的Mysql,在编码的过程中,遇到几个坑,在此记录一下。

二、具体代码 1、几点说明

平台数据库:Mysql

数据库字段:file_content为存储文件的字段 在这里插入图片描述

2、代码实现

数据库的xml文件,注意file_content的类型为BINARY

<resultMap id="BaseResultMap" type="com.scorpio.bean.FileContent"> <id column="file_tid" jdbcType="VARCHAR" property="fileTid" /> <result column="file_path" jdbcType="VARCHAR" property="filePath" /> <result column="file_path_md" jdbcType="VARCHAR" property="filePathMd" /> <result column="file_content" jdbcType="BINARY" property="fileContent" />  private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(FileContentServiceImpl.class); @Autowired FileContentMapper contentMapper; @Override public boolean readResourceFromDB(String fileTid) { FileOutputStream fos = null; ByteArrayInputStream bis = null; try{ // 查询资源 FileContent fileContent = contentMapper.selectByFileId(fileTid); if(fileContent == null){ return false; } String filePath = fileContent.getFilePath(); File file = new File(filePath); // 创建资源路径 if (!file.getParentFile().exists()) { boolean succ = file.getParentFile().mkdirs(); if (!succ) { throw new Exception("mkdir failed: " + file.getParentFile().getAbsolutePath()); } } // 获取资源内容的byte[] bis = new ByteArrayInputStream(fileContent.getFileContent()); // 将byte[]输出到文件 fos = new FileOutputStream(file); int len = 0; byte[] buf = new byte[1024]; while ((len = bis.read(buf)) != -1) { fos.write(buf, 0, len); } return true; } catch (Exception e) { LOG.error("readResourceFromDB 异常:", e); return false; } finally { LOG.debug("readResourceFromDB() enter finally"); if (null != fos) { try { fos.close(); } catch (IOException e) { } } if (null != bis) { try { bis.close(); } catch (IOException e) { } } } } @Override public void saveResourceToDB(FileContent fileContent) throws Exception { FileInputStream fis = null; ByteArrayOutputStream bos = null; fileContent.setFilePathMd(Md5Tool.getMd5(fileContent.getFilePath())); try { File file = new File(fileContent.getFilePath()); if(!file.exists()) { return; } fis = new FileInputStream(file); byte[] buffer = null; // 此处用字节输出流 bos = new ByteArrayOutputStream(); byte[] temp = new byte[1024]; int n; while ((n = fis.read(temp)) != -1) { bos.write(temp, 0, n); } // 将字节输出流转化为byte[],保存到数据库中 buffer = bos.toByteArray(); fileContent.setFileContent(buffer); int result = contentMapper.insertFileContent(fileContent); }catch (Exception e){ LOG.error("saveResourceToDB 异常:", e); throw e; }finally { LOG.debug("saveResourceToDB enter finally"); if (null != bos) { try { bos.close(); } catch (IOException e) { } } if (null != fis) { try { fis.close(); } catch (IOException e) { } } } } } 
三、小结

在将文件以流的形式存入Mysql数据库时,我遇到了下面几个问题:

(1)、是将文件以byte[]数组存入还是以String存入?

我一开始以String存入,程序报了heap space异常,很明显,堆内存溢出,这个问题不能通过改变堆内存的大小来解决,所以我放弃String,而是以byte[]数组。此处要注意的是jdbcType类型为BINARY。

(2)、如何获取byte[]数组?

起初,我使用下面的方式来获取byte[]数组,即将文件先放入StringBuffer中,再用getBytes()转为byte[],此种方式将数据存入数据库没有问题,但文件会变大,我原本存储14M的文件,入库后变成了24M,这肯定是有问题的。原因感兴趣的,可以研究一下,这种方式获取byte[]数组,果断放弃。使用的方法是ByteArrayInputStream,具体看上面代码。

FileInputStream fis = new FileInputStream(file); byte[] buf = new byte[1024]; StringBuffer sb = new StringBuffer(); while ((fis.read(buf)) != -1) { sb.append(new String(buf)); buf = new byte[1024];// 重新生成,避免和上次读取的数据重复 } byte[] fileContent = sb.toString().getBytes() 

(3)、LongBlob 和LongText ?

解决上面的两个问题后,原本以为问题就迎刃而解了,但并不是。我一开始设置file_content的Mysql字段是longtext,就报了下面这个错,这问题困惑了我2个小时,因为各种资料都是说字符编码有问题,用的不是utf8,我检查了一遍又一遍啊,最后怀疑是字段类型学的问题,于是使用了longblob,问题解决。这两个类型的具体区别如下:

ERROR 1366 (HY000): Incorrect string value: '\xE8\x8B\xB1\xE5\xAF\xB8...'

(4)、LongBlob 和LongText 主要区别

TEXT与BLOB的主要差别就是BLOB保存二进制数据,TEXT保存字符数据。

BLOB有4种类型:TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB。它们只是可容纳值的最大长度不同。

TEXT也有4种类型:TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT。这些类型同BLOB类型一样,有相同的最大长度和存储需求。

MySQL的四种 BLOB 类型(同TEXT): (单位:字节)

TinyBlob : 最大 255
Blob : 最大 65K
MediumBlob : 最大 16M
LongBlob : 最大 4G

补充!!!! 上面的方法经过测试小文件入库,没有大问题,但是当文件稍大以后,就会报OutOfMemoryError: Java heap space。

问题描述,如这篇文章所示: https://www.v2ex.com/t/562447

其中的原因是,将文件以byte[]数组的方式存入数据库,byte[]数组里要存整个文件,仔细查看下面的代码,可以看出,需要2倍的文件大小,一个是存放文件全部字节的byte[]数组,一个是要存放文件流,这样会非常占用内存。如果一个文件200M,内存将消耗极大,我们也可以通过增加JVM的堆内存大小来解决,但此方法并没有从根本上解决问题,还是不妥。

fis = new FileInputStream(file); byte[] buffer = null; // 此处用字节输出流 bos = new ByteArrayOutputStream(); byte[] temp = new byte[1024]; int n; while ((n = fis.read(temp)) != -1) { bos.write(temp, 0, n); } // 将字节输出流转化为byte[],保存到数据库中 buffer = bos.toByteArray(); 

分析了上述的问题,下面提出改进的方法,我们直接将文件流存入mysql数据库中。

public class FileContentServiceImpl implements FileContentService, ApplicationContextAware { private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(FileContentServiceImpl.class); private ApplicationContext applicationContext; private DruidDataSource dataSource; @Autowired FileContentMapper contentMapper; @Override public boolean readResourceFromDB(String fileTid) { FileOutputStream fos = null; DruidPooledConnection connection = null; try{ connection = dataSource.getConnection(); String sql = "select file_tid,file_path,file_path_md,file_content from t_file_content where file_tid=?"; PreparedStatement pst = connection.prepareStatement(sql); pst.setString(1,fileTid); ResultSet result = pst.executeQuery(); if(result != null && result.next()){ String filePath = result.getString(2); File file = new File(filePath); // 创建资源路径 if (!file.getParentFile().exists()) { boolean succ = file.getParentFile().mkdirs(); if (!succ) { throw new Exception("mkdir failed: " + file.getParentFile().getAbsolutePath()); } } InputStream in = result.getBinaryStream(4); fos = new FileOutputStream(file); int len = 0; byte[] buf = new byte[1024]; while ((len = in.read(buf)) != -1) { fos.write(buf, 0, len); } fos.flush(); in.close(); connection.close(); return true; } return false; } catch (Exception e) { LOG.error("readResourceFromDB 异常:", e); return false; } finally { LOG.debug("readResourceFromDB() enter finally"); if (null != fos) { try { fos.close(); } catch (IOException e) { } } } } @Override public void saveResourceToDB(FileContent fileContent) { DruidPooledConnection connection = null; try { String filePath = fileContent.getFilePath(); fileContent.setFilePathMd(Md5Tool.getMd5(filePath)); File file = new File(filePath); if(!file.exists()) { return; } String sql = "insert into t_file_content (file_tid, file_path, file_path_md, file_content) values (?,?,?,?)"; connection = dataSource.getConnection(); PreparedStatement pst = connection.prepareStatement(sql); pst.setString(1, fileContent.getFileTid()); pst.setString(2, fileContent.getFilePath()); pst.setString(3, fileContent.getFilePathMd()); InputStream in = new FileInputStream(file); pst.setBinaryStream(4,in); pst.execute(); pst.close(); connection.close(); } catch (Exception e) { LOG.error("saveResourceToDB 异常:", e); } } @Override public void setApplicationContext(ApplicationContext applicationContext) { try { this.applicationContext = applicationContext; LOG.info("get applicationContext is : " + applicationContext); dataSource = (DruidDataSource) applicationContext.getBean("dataSource"); LOG.info("get dataSource is : " + dataSource); }catch (Exception e){ LOG.info("get applicationContext exception!"); } } } 

说明: 此处用的是Mybatis和DruidDataSource数据源,我们需要从Spring容器中拿到数据源,所以实现了ApplicationContextAware接口,该接口可以让我们拿到Spring容器;

关注
打赏
1688896170
查看更多评论

暂无认证

  • 5浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0959s