读取数据表中的文件字节流

开发背景

需求背景

  由于以前的文件存在于SQLServer数据表中,而这种对文件的保存方式是十份不友好的,比如说这个项目中的file数据表就是这种情况,由于在其中存入了近10G的文件,这导致该表数据量十份臃肿,比如说我们执行一条简单的SQL,如下:

Select * From file

  由于数据量过大,该条查询语句往往会执行上半天时间,如果再多执行几次这种文件查询操作,系统就直接卡死,因而原来的这种文件存储方式是存在巨大的风险的,所以说我需要将存储于表中的数据文件导出成真正的文件,以此来解决现今所面临的问题。

分析解决

  从上面我们可以看出,整个的业务逻辑是比较的简单的,就是将文件从数据表中读出,然后写成真正的文件。因而这里就无需多做说明。

  这里需要重点说说其技术实现。

  由于上述表中存在大量的信息,单纯的查询操作往往会导致程序卡死,并且一次性的读取上G的文件数据本身也并不合理 ,因而我这里采用的是分页查询。也就是说通过先统计该表信息的总条数,然后循环遍历,每次读取10条,直至全部数据读取完毕。

  至于为什么每次读取10条,其主要是经过测试之后得出的,测试数据如下:

file表:
文件总数:12224
当前文件数:180
当前文件总容量:550M
时间:1小时10分即70分钟
最大文件大小:30M
平均文件大小:
550M/180=3.05M

平均每小时生成文件容量:
550M/(70/60)=471.43M

预估文件总大小:
12224*3.05=37283.2M=36.4G

预估总共用时:
37283.2M/471.43M=79小时
即file表生成完毕,耗时需要:79/34=3.29天

  通过初步测试,我们得出,在该表中,最大的单个文件为30M,平均为3.05M,而一次性读取的数据,控制在100M以内是比较合理的,如果一次性读取过多,会加重系统内存以及数据IO负担,而一次性读取过少,会导致很多时间浪费在查询上,并不合理,所以说最终我将一次性读取的信息条数定为10条。平均每次读取30.5M的数据,最大读取300M数据量。

  由于不同的文件表有不同的规则,比如说案卷附件表,需要在导出附件文件的同时关联查出文件编号,最终将附件文件放在案卷编号文件夹下;而封面、详情页等属于文件表,则需要关联文件表,读取文件名称(类似于书名),将附件文件放入到文件名称文件夹下。

  换言之,不同的附件文件表拥有不同的规则(也就是函数),这对于我来说是一个新的挑战,在这里,我采用的是函数式编程(即使用@FunctionalInterface注解),通过在遍历文件时动态的传入附件文件函数,以此来确定该附件文件到底执行什么样的导出方法。

源码

核心依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <!-- jpa -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- sqlserver -->
    <dependency>
        <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>
</dependencies>

Java源码

工具类

  PathUtil

package com.lyc.sqlserver.util;

import com.lyc.sqlserver.common.CommonConstant;
import com.lyc.sqlserver.entity.ProjectPath;

import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/20 13:48
 * @description: 文件路径工具类
 **/
public class PathUtil {
   

    /**
     * 返回PathUtil实例
     * @return
     */
    public static PathUtil newPathUtil(){
   
        return new PathUtil();
    }

    /**
     * 获取文件完整路径
     * @param file 文件对象
     * @param folderPath 文件夹路径
     * @return
     */
    public String getFileFullPath(Map<String,Object> file, String folderPath) {
   
        // 获取文件路径
        String filePath = getFilePath(file);
        // 返回文件完整路径
        return folderPath
                + CommonConstant.Separators.BACK_SLASH
                + filePath;
    }

    /**
     * 获取文件路径
     * @param file 文件对象
     * @return
     */
    private String getFilePath(Map<String,Object> file) {
   
        // 返回文件路径
        return getString(file.get("fname"));
    }

    /**
     * 获取文件夹路径
     * @param pathMap 项目路径集合
     * @return
     */
    public String getFolderPath(Map<String,Object> pathMap) {
   
        // 文件夹路径 = 系统绝对路径 + 项目名 + 数据表名 + 档案编号
        return CommonConstant.ProjectPathName.SYSTEM_PATH
                + CommonConstant.Separators.BACK_SLASH
                + getProjectName(pathMap.get(CommonConstant.ProjectPropertyName.PROJECT))
                + CommonConstant.Separators.BACK_SLASH
                + getString(pathMap.get(CommonConstant.ProjectPropertyName.TABLE));
    }

    /**
     * 获取项目名
     * @param o
     * @return
     */
    public String getProjectName(Object o) {
   
        ProjectPath projectPath = (ProjectPath) o;
        return projectPath.getName();
    }

    /**
     * 将对象转换成字符串
     * @param o 文件夹名称
     * @return
     */
    public String getString(Object o) {
   
        return String.valueOf(o);
    }

    /**
     * 获取默认的文件完整路径
     * @param fileName
     * @param folderPath
     * @return
     */
    public String getFileFullPathDefault(String fileName, String folderPath) {
   
        // 返回文件完整路径
        return folderPath
                + CommonConstant.Separators.BACK_SLASH
                + fileName;
    }
}

  FileUtil

package com.lyc.sqlserver.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/20 14:39
 * @description: 文件工具类
 **/
@Slf4j
public class FileUtil {
   

    /**
     * 返回创建的FileUtil对象
     * @return
     */
    public static FileUtil newFileUtil(){
   
        return new FileUtil();
    }

    /**
     * 获取文件字节码并返回
     * @param fileContent 字节码对象
     * @return
     */
    public byte[] getByte(Object fileContent) {
   
        if(fileContent instanceof byte[]){
   
            return (byte[]) fileContent;
        }
        // 如果不存在,就创建一个空对象,防止空指针异常现象的发生
        return new byte[0];
    }

    /**
     * 生成附件文件
     * @param fileFullPath 文件全路径
     * @param fileByteArray 文件字节流
     */
    public void generateFile(String fileFullPath, byte[] fileByteArray) {
   
        FileOutputStream outputStream = null;
        try {
   
            outputStream = new FileOutputStream(fileFullPath);
            // 读取的字节对象
            for(byte eleb : fileByteArray){
   
                outputStream.write(eleb);
            }
        } catch (IOException e) {
   
            log.error("生成文件出错!故障码:{}",e);
            e.printStackTrace();
        } finally {
   
            // 获取文件输入流
            try {
   
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
   
                log.error("文件流关闭出错!故障码:{}",e);
                e.printStackTrace();
            }
        }

    }

    /**
     * 如果文件夹路径不存在,则创建
     * @param fileFullPath 文件夹全路径
     */
    public void generateFolder(String fileFullPath) {
   
        File folder = new File(fileFullPath);
        while(!folder.exists()){
   
            folder.mkdirs();
        }
    }

}

  FieldFormatUtil

package com.lyc.sqlserver.util;

import com.google.common.collect.Maps;
import lombok.var;

import java.util.Date;
import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/29 11:22
 * @description: Map<String,Object> 类型的数据格式化成对应的格式并返回
 **/
public class FieldFormatUtil {
   

    private final Map<String,Object> map;
    private final DataConvertUtil dataConvertUtil;


    /**
     * FieldFormatUtil 构造函数
     * @param map
     */
    public FieldFormatUtil(Map<String,Object> map) {
   
        if(null != map) {
      // 如果不为空,则获取数据
            this.map = map;
        } else {
     // 否则设置一个默认的空对象
            this.map = Maps.newHashMap();
        }
        dataConvertUtil = DataConvertUtil.newDataConvertUtil();
    }

    /**
     * 创建一个FieldFormatUtil 对象
     * @param map
     * @return
     */
    public static FieldFormatUtil newFieldFormatUtil(Map<String,Object> map){
   
        return new FieldFormatUtil(map);
    }

    /**
     * 获取整型数据
     * @param k
     * @return
     */
    public int getInt(String k){
   
        var obj = this.map.get(k);
        return dataConvertUtil.getInt(obj);
    }

    /**
     * 获取Long型数据
     * @param k
     * @return
     */
    public Long getLong(String k) {
   
        var obj = this.map.get(k);
        return dataConvertUtil.getLong(obj);
    }

    /**
     * 获取String型数据
     * @param k
     * @return
     */
    public String getString(String k) {
   
        var obj = this.map.get(k);
        return dataConvertUtil.getString(obj);
    }

    /**
     * 获取Double类型的数据
     * @param k
     * @return
     */
    public Double getDouble(String k) {
   
        var obj = this.map.get(k);
        return dataConvertUtil.getDouble(obj);
    }

    /**
     * 获取时间类型的数据
     * @param k
     * @return
     */
    public Date getDate(String k) {
   
        var obj = this.map.get(k);
        return dataConvertUtil.getDate(obj);
    }

    /**
     * 获取double类型的数据
     * @param k
     * @return
     */
    public float getFloat(String k) {
   
        var obj = this.map.get(k);
        return dataConvertUtil.getFloat(obj);
    }

}

  DataConvertUtil

package com.lyc.sqlserver.util;

import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;

import java.util.Date;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/29 11:37
 * @description: 数据类型转换工具
 **/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值