导入图片引发出的对图片、视频、文档等上传的思考
哈喽!今天心血来潮给大家带来了《导入图片引发出的对图片、视频、文档等上传的思考》,想必大家应该对数据库都不陌生吧,那么阅读本文就都不会很困难,以下内容主要涉及到MySQL、拦截器、windows-server、java-web、poi,若是你正在学习数据库,千万别错过这篇文章~希望能帮助到你!
导读
我们在开发的过程中,经常会遇到导入和导出:
- 从哪里导入到哪里?我们在客户端选择上传Excel文件,同时调用服务端的某个接口。服务端通过HttpServletRequest获取Excel的数据流,通过poi的相关操作获取单元格的数值,并填充到相应的javabean的实例化对象中。再调用事务的保存方法,利用hibernate框架或mybatis框架,将对象数据保存到数据库中。
- 从哪里导出到哪里?从服务器上的数据库的数据导出到本地,并以Excel文件的方式存储。我们在客户端选择导出Excel文件,同时调用服务端的某个接口。服务端通过HttpServletResponse响应客户端的请求,同时,调用事务层的查询方法,拿到待导出的数据源,过滤我们想要的数据。调用poi的相关操作,将数据填充到Excel表中。
导入
这里以导入材料为主,材料中存有图片,如图所示:

你会发现,Excel表中存储的是图片在服务器上的路径,为什么存储的是图片在服务器的路径,而不是图片的字节码数据?
我们都知道任何文件都可以按照字节码的方式存储,比如视频文件、音乐文件、图片文件、GIF文件、文本框文件等。但是字节码的存储和读取都占用内存,如果在大批量的导入和导出的情况下,势必会占用JVM内存,造成资源阻塞。
因而,我们存储的是图片的路径,这还不是随随便便的路径,而是其所在服务器的路径。为什么选择路径。从上图中的图片路径来看,路径的字符比较短。占用的内存比较少,存储和读取相对来说快。因而,我们读入的图片的路径。比如上图中ENGINE_PLATFORM1TENANTTHUMBNAILTENANT-LOGO_1_1534415695498_1.jpg的图片:

其所对应的服务器的图片地址是http://cw.rosunn.com/upload/i...。因而,我们只要在数据库中存储/ENGINE_PLATFORM/1/TENANT/THUMBNAIL/TENANT-LOGO_1_1534415695498_1.jpg这部分路径就可以的。我们的域名前缀http://cw.rosunn.com/是固定的,其所对应的图片的文件夹upload是固定的,该文件夹下有很多的图片文件夹。每个图片的文件夹都是不一样的。如图所示:

因为我们是Windows服务器,所以服务器是Windows界面化操作。其实,一台电脑就是一台服务器,要不然,怎么说本地服务器呢?在该文件夹下,有三个子文件夹。
- attach文件夹,存储与附件相关的文件夹。
- image文件夹,存储图片的文件夹
- uEditor文件夹,前端会使用uEditor框架,这是多文本编辑器,可以上传图片、视频等。
以后,可能会有视频文件夹,如果做教学软件的话。不管是存储图片文件和附件文件,还是视频文件,我们在数据库中都只存储该文件路径。当我们从数据库中读出图片到前台页面,我门只要拿到其存储的路径,并在前端做如下配置即可:
明白这一点,我们就好往下进行,当我们点击前端代码的导入按钮时,如图所示:

其首先会进入到拦截器,然后再进入到服务器的三层架构中。
服务器的三层架构
我们常说服务器有三层架构,即dao层,service层,controller层。实际上这是个通俗的概念,然而,在真正的开发过程中,并非只有三层架构,其中还会有拦截器的概念。如果你用servlet开发,会涉及到过滤器。拦截器和过滤器的功能是一样的,只不过用法是不一样的。它俩到底有什么区别,我想网上的博客非常多。这里就不在细说了。也许,你可以参考这篇博客:拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别
一般项目启动后,首先进入的不是controller层,而是拦截器,controller层只是针对接口而言的。
拦截器,听名知其意,主要做数据的过滤和拦截。对于数据库中不时常改变的数据,比如系统变量和数据字典等,我们可以放到拦截器的缓存中,当我们加载数据字典时,不必再从数据库中读取,而是读取缓存的数据字典。这样,减少了与数据库的连接,从而提升了效率。
曾经在实习时,有个老大教我,说影响服务端的效率一般是db操作、网络调用操作、线程、JVM优化等。至于,我们是用++i,还是i++,哪个效率高一点。当然,是++i效率高一点。i++内部会有一个临时变量,其存储的i改变前值,然后再执行i = i + 1,返回的也是临时变量。++i直接执行的是i = i + 1,并返回改变后的值。但是,不会考虑到这个问题,因为它的影响微乎其微。
同时,我们每打开一个页面,都要经过拦截器,有些页面需要登录才能看,有些页面可以不用登录。这就是拦截器的作用。
我们这个项目使用的是Apache Shiro框架。其是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理的拦截器框架。
除了,我们登录后台需要身份验证,需要shiro的拦截。或者,我们调用第三方支付接口,其要回调我们的接口。但是,我们对每个接口,都要进行拦截,防止其恶意攻击。此时,我们需要忽略第三方回调的我们的接口,也就是说,这个接口不在我们的拦截范围之内,如下代码:
备注,因为涉及到隐私,部分代码省略,或以 ** 代替,望请见谅。
http://域名/upload//image/sys...。如果其在服务器中存在,我们就创建图片的对象,图片的属性remoteRelativeUrl,存储该路径的后半部分,也就是相对路径,并保存到数据库中,返回一个图片对象。并把图片对象放到集合中,然后保存到材料对象中。材料对象再保存到数据中,这就完成一次导入。但是jsonObjectList可能有多个对象,再遍历一次,直到遍历所有的对象。 <p>如下是材料的java类:</p> <p></p><pre class="brush:go;">@Data @AllArgsConstructor @NoArgsConstructor @Entity @Table(name = "zq_material") public class Material extends BaseObj { /** * 材料名称 */ @Column(name = "material_name") private String materialName; /** * 单位 */ @ManyToOne @JoinColumn(name = "unit_code") private DataDict unit; /** * 零售价 */ @Column(name = "retail_price", precision = 12, scale = 2) private BigDecimal retailPrice; /** * 状态 */ @Enumerated(EnumType.STRING) private MaterialStateEnum status; /** * 浏览量 */ @Column(name = "page_view") private Long pageView; /** * 图片 */ @ManyToMany(fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) @JoinTable( name = "zq_material_pictures", joinColumns = {@JoinColumn(name = "zq_material_id")}, inverseJoinColumns = @JoinColumn(name = "core_picture_id") ) @JSONField(serialize = false) private List<picture> pictureList = new ArrayList(); /** * 材料标签 */ @ManyToMany(fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) @JoinTable( name = "zq_material_tag", joinColumns = {@JoinColumn(name = "zq_material_id")}, inverseJoinColumns = @JoinColumn(name = "core_data_dict_code") ) @JSONField(serialize = false) private List<datadict> tagList = new ArrayList(); /** * 备注 */ @Column(name = "note", columnDefinition = "longtext") private String note; }</datadict></picture>
为何向上遍历三次
我们看一下字节码文件在服务器的位置,如图所示:

由图可知。一次次的向上遍历,只为找到根路径,也就是http://域名。这是Tomcat的配置。然后再配置upload文件夹,即http://域名/upload
导入的执行效率
以上导入可以分为两种方式。一种是如果导入的数据中,但凡有一条数据不成功,所有的数据都无法导入。这就涉及到了事务一致性的问题。因而,我们需要放在事务层,也就是service层。为什么spring直到service层是事务层,这和我们的框架配置有关,把service层定义为事务层。如果某一条数据导入失败,并不影响其他数据的导入,我们可以放在controller层。
但是,如果处理的不当,便影响导入的执行效率。为什么这么说?比如,我们现在导入的是材料,材料有单位。单位放置在数据字典中,假设单位有16条数据,如图所示:

假如jsonObjectList的集合有1000条数据。我们每次遍历jsonObjectList集合,都要创建一次查询,也就是与数据库创建一次连接,保存之后再关掉连接,势必会减低导入效率:
我们从数据查找出当前单位的行数据,封装成我们想要的数据字典的对象。此时与数据库建立连接和释放数据库的连接,最多需要16000次,这势必会会增加服务器的资源,降低导入的执行效率。最少也需要1000次。 同时,系列也是来源于数据字典,然而,系列是以逗号分割的字符串,也就是说,我们需要将字符串分割成数组,再遍历这个数组,获取数据字典的对象,此时,最少语句数据库的连接数为16000,最多就不大清楚了。因而,严重降低导入的效率。 我们为什么不采用最少的呢?因而,我们在遍历jsonObjectList 之前,就从数据库中的加载出所有的单位的数据字典的集合,同时,也加载出系列的集合。放置在map的键值对当中,根据key值来取value值,如代码所示: 根据数据字典的父code值加载出所有的子code对象。 对于上面的一串代码,我们省略其他的代码,只加载和数据字典相关的代码,于是乎,得到: 这就是数据库导入优化,但是导入图片,和我们上传图片、视频、文档有关系吗? 我们在做java开发时,势必会涉及到文件操作。我们一般会上传图片、文件、视频等,但它们以什么样的格式存储。正如上面提到的,我们上传图片、视频、附件等,会在服务器上创建一个文件夹,他们存储在该文件夹中。我们只要获取文件夹的相对路径即可,就能将其加载出来,这样比较节省数据库的资源。如图所示: //单位
String unitValue = json.getString("I");
if (isNotNull(unitValue)) {
List
/**
* Created By zby on 14:12 2019/3/24
* 将dict封装成map
*/
private Map<string datadict> dict2Map(String parentCode) {
Map<string datadict> dictMap = null;
if (StringUtils.isNotBlank(parentCode)) {
List<datadict> units = dataDictService.getDataDictList(parentCode).getResultData();
dictMap = new HashMap();
if (!CollectionUtils.isEmpty(dictMap)) {
for (DataDict dict : units) {
dictMap.put(dict.getValue(), dict);
}
}
}
return dictMap;
}</datadict></string></string>
/**
* Created By zby on 17:35 2019/2/20
* 导入
*/
@RequestMapping(value = "/import", method = RequestMethod.POST)
public Result importMaterials(HttpServletRequest request) {
JSONObject body = new JSONObject();
int totalNum = 0, successNum = 0;
// 单位
Map<string datadict> unitDict = dict2Map("unit");
// 系列
Map<string datadict> tagDict = dict2Map("material_tag");
synchronized (this) {
try {
List<jsonobject> jsonObjectList = PoiUtil.importSimpleExcel(request, 1, "P");
if (null != jsonObjectList || jsonObjectList.size() > 0) {
totalNum = jsonObjectList.size();
for (JSONObject json : jsonObjectList) {
//单位
String unitValue = json.getString("I");
if (StringUtils.isNotBlank(unitValue)) {
dbMaterial.setUnit(unitDict.get(unitValue));
}
//系列
String tags = json.getString("N");
if (isNotNull(tags)) {
String[] tagArr = StringUtils.split(tags, ",");
List<datadict> tagDicts = new ArrayList();
for (String tag : tagArr) {
if (StringUtils.isNotBlank(tag)) {
tagDicts.add(tagDict.get(tag));
}
}
dbMaterial.setTagList(tagDicts);
}
materialService.saveUpdateMaterialAccount(dbMaterial);
successNum++;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
body.put("totalNum", totalNum);
body.put("successNum", successNum);
body.put("errNum", totalNum - successNum);
return ResultUtil.buildSuccess(body);
}</datadict></jsonobject></string></string>
图片、视频、附件上传
这一般都是异步上传,先将文件的路径以对象的保存到数据库中,再返回文件被保存后的带有主键id的对象。我们拿到持久态的文件对象后,在前端页面展示出来。因而,我们在保存材料时,前端只要向后端传输文件的id,或者是文件.id即可,比如 logo.id。spring会自动创建该文件对象,并将id到注入文件对象中。
总结
我们在开发过程中,要知其然,知其所以然。
以上就是《导入图片引发出的对图片、视频、文档等上传的思考》的详细内容,更多关于mysql的资料请关注golang学习网公众号!

- 上一篇
- php7默认不安装bcmath的扩展,调用bcmath函数会报错

- 下一篇
- 一个JAVA程序员的面试心得
-
- 粗犷的钢铁侠
- 这篇技术文章太及时了,好细啊,赞 ??,mark,关注大佬了!希望大佬能多写数据库相关的文章。
- 2023-04-15 09:07:42
-
- 数据库 · MySQL | 8小时前 |
- MySQL排序优化与性能提升技巧
- 153浏览 收藏
-
- 数据库 · MySQL | 9小时前 |
- MySQL中WHERE与HAVING的区别详解
- 340浏览 收藏
-
- 数据库 · MySQL | 15小时前 |
- MySQL排序优化与性能提升技巧
- 368浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL连接池配置与优化方法
- 297浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQLGROUPBY使用技巧与常见问题
- 306浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL缓存优化技巧分享
- 392浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL安装到D盘教程及路径设置详解
- 279浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL缓存设置及查询作用解析
- 470浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQLcount优化技巧及性能提升方法
- 371浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQLUPDATE替换字段值方法详解
- 292浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL基础:增删改查全教程
- 356浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 千音漫语
- 千音漫语,北京熠声科技倾力打造的智能声音创作助手,提供AI配音、音视频翻译、语音识别、声音克隆等强大功能,助力有声书制作、视频创作、教育培训等领域,官网:https://qianyin123.com
- 95次使用
-
- MiniWork
- MiniWork是一款智能高效的AI工具平台,专为提升工作与学习效率而设计。整合文本处理、图像生成、营销策划及运营管理等多元AI工具,提供精准智能解决方案,让复杂工作简单高效。
- 89次使用
-
- NoCode
- NoCode (nocode.cn)是领先的无代码开发平台,通过拖放、AI对话等简单操作,助您快速创建各类应用、网站与管理系统。无需编程知识,轻松实现个人生活、商业经营、企业管理多场景需求,大幅降低开发门槛,高效低成本。
- 106次使用
-
- 达医智影
- 达医智影,阿里巴巴达摩院医疗AI创新力作。全球率先利用平扫CT实现“一扫多筛”,仅一次CT扫描即可高效识别多种癌症、急症及慢病,为疾病早期发现提供智能、精准的AI影像早筛解决方案。
- 98次使用
-
- 智慧芽Eureka
- 智慧芽Eureka,专为技术创新打造的AI Agent平台。深度理解专利、研发、生物医药、材料、科创等复杂场景,通过专家级AI Agent精准执行任务,智能化工作流解放70%生产力,让您专注核心创新。
- 97次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览
-
- MySQL数据表简单查询
- 2023-01-10 101浏览