SpringBoot学习笔记(十一:使用MongoDB存储文件 )

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 1.6、控制层

  • 1.7、工具类

  • 1.8、前端页面

  • 1.9、运行效果

  • 2、MongoDB存储大文件

    • 2.1、依赖
  • 2.2、启动类

  • 2.3、配置

  • 2.4、实体类

  • 2.5、服务层

  • 2.6、控制层

  • 2.7、运行效果

一、MongoDB存储文件

===============================================================================

1、MongoDB存储小文件


MongoDB是一个面向文档的数据库,使用BSON(Binary JSON:二进制JSON)格式来存储数据。

BSON格式

在这里插入图片描述

BSON支持在一个文档中最多存储16MB的二进制数据。如果存储的是小于16M的文件,可以直接将文件转换为二进制数据,以文档形式存入集合。

Java中文件和二进制转换也比较简单:

  • 文件转换为byte数组

public static byte[] fileToByte(File file) throws IOException{

byte[] bytes = null;

FileInputStream fis = null;

try{

fis = new FileInputStream(file);

bytes = new bytes[(int) file.length()];

fis.read(bytes);

}catch(IOException e){

e.printStackTrace();

throw e;

}finally{

fis.close();

}

return bytes;

}

  • byte数组转换为文件

public static void bytesToFile(byte[] bFile, String fileDest) {

FileOutputStream fileOuputStream = null;

try {

fileOuputStream = new FileOutputStream(fileDest);

fileOuputStream.write(bFile);

} catch (IOException e) {

e.printStackTrace();

} finally {

if (fileOuputStream != null) {

try {

fileOuputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

如果是实现文件下载功能,可以把字节码直接写进流中。

2、MongoDB存储大文件


MongoDB单个文档的存储限制是16M,如果要存储大于16M的文件,就要用到MongoDB GridFS。

GridFS是Mongo的一个子模块,使用GridFS可以基于MongoDB来持久存储文件。并且支持分布式应用(文件分布存储和读取)。作为MongoDB中二进制数据存储在数据库中的解决方案,通常用来处理大文件。

GridFS不是MongoDB自身特性,只是一种将大型文件存储在MongoDB的文件规范,所有官方支持的驱动均实现了GridFS规范。GridFS制定大文件在数据库中如何处理,通过开发语言驱动来完成、通过API接口来存储检索大文件。

2.1、GridFS存储原理

GridFS使用两个集合(collection)存储文件。一个集合是chunks, 用于存储文件内容的二进制数据;一个集合是files,用于存储文件的元数据。

GridFS会将两个集合放在一个普通的buket中,并且这两个集合使用buket的名字作为前缀。MongoDB的GridFs默认使用fs命名的buket存放两个文件集合。因此存储文件的两个集合分别会命名为集合fs.files ,集合fs.chunks。

当把一个文件存储到GridFS时,如果文件大于chunksize (每个chunk块大小为256KB),会先将文件按照chunk的大小分割成多个chunk块,最终将chunk块的信息存储在fs.chunks集合的多个文档中。然后将文件信息存储在fs.files集合的唯一一份文档中。其中fs.chunks集合中多个文档中的file_id字段对应fs.files集中文档”_id”字段。

读文件时,先根据查询条件在files集合中找到对应的文档,同时得到“_id”字段,再根据“_id”在chunks集合中查询所有“files_id”等于“_id”的文档。最后根据“n”字段顺序读取chunk的“data”字段数据,还原文件。

GridFS存储过程

在这里插入图片描述

fs.files 集合存储文件的元数据,以类json格式文档形式存储。每在GridFS存储一个文件,则会在fs.files集合中对应生成一个文档。

fs.files集合中文档的存储内容

在这里插入图片描述

fs.chunks 集合存储文件文件内容的二进制数据,以类json格式文档形式存储。每在GridFS存储一个文件,GridFS就会将文件内容按照chunksize大小(chunk容量为256k)分成多个文件块,然后将文件块按照类json格式存在.chunks集合中,每个文件块对应fs.chunk集合中一个文档。一个存储文件会对应一到多个chunk文档。

fs.chunks集合中文档的存储内容

在这里插入图片描述

2.2、GridFS使用

2.2.1、使用shell命令

mongoDB提供mingofiles工具,可以使用命令行来操作GridFS。其实有四个主要命令,分别为:

  • put —存储命令

  • get —获取命令

  • list —列表命令

  • delete —删除命令

操作实例:

  • 存储文件

向数据库中存储文件的格式:mongofiles -d 数据库名字 -l “要上传的文件的完整路径名” put “上传后的文件名”

在这里插入图片描述 在filetest数据库中就会多出2个集合,它们存储了GridFS文件系统的所有文件信息,查询这两个集合就能看到上传的文件的一些信息:

在这里插入图片描述

  • 列出文件

查看GridFS文件系统中所有文件:mongofiles -d 数据库名字 list

在这里插入图片描述

  • 获取文件

从GridFS文件系统中下载一个文件到本地:mongofiles -d 数据库名字 -l “将文件保存在本地的完整路径名” get “GridFS文件系统中的文件名” ,如果不写-l以及后面的路径参数,则保存到当前位置。

在这里插入图片描述

  • 删除文件

删除GridFS文件系统中的某个文件:mongofiles -d 数据库名字 delete " 文件名 "

在这里插入图片描述

2.2.2、使用API

MongoDB支持多种编程语言驱动。比如c、java、C#、nodeJs等。因此可以使用这些语言MongoDB驱动API操作,扩展GridFS。

以Java为例:

  • 依赖包和版本:

org.mongodb:3.2.2

mongo-java-driver:3.2.2

  • 公共方法

public MongoDatabase mongoDatabase() throws Exception{

MongoClient mongoClient = new MongoClient(mongoHost, mongoPort);

return mongoClient.getDatabase(dbName);

}

  • 上传文件

@Test

public void upload() throws Exception {

// 获取文件流

File file = new File(“E:/zookeeper-3.4.8.tar.gz”);

InputStream in = new FileInputStream(file);

// 创建一个容器,传入一个MongoDatabase类实例db

GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());

// 上传

ObjectId fileId = bucket.uploadFromStream(UUID.randomUUID().toString(), in);

System.out.println(“上传完成。 文件ID:”+fileId);

}

  • 查找文件

@Test

public void findOne() throws Exception {

// 获取文件ID

String objectId = “57fbaffcec773716ecc54ef4”;

// 创建一个容器,传入一个MongoDatabase类实例db

GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());

// 获取内容

GridFSFindIterable gridFSFindIterable = bucket.find(Filters.eq(“_id”, new ObjectId(objectId)));

GridFSFile gridFSFile = gridFSFindIterable.first();

System.out.println("filename: " + gridFSFile.getFilename());

}

  • 下载文件

@Test

public void download() throws Exception {

// 获取文件ID

String objectId = “57fbaffcec773716ecc54ef4”;

// 获取文件流

File file = new File(“D:/zookeeper-3.4.8.tar.gz”);

// 创建一个容器,传入一个MongoDatabase类实例db

GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());

// 创建输出流

OutputStream os = new FileOutputStream(file);

// 下载

bucket.downloadToStream(new ObjectId(objectId), os);

System.out.println(“下载完成。”);

}

  • 删除文件

@Test

public void delete() throws Exception {

// 获取文件ID

String objectId = “57fbaffcec773716ecc54ef4”;

// 创建一个容器,传入一个MongoDatabase类实例db

GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());

// 删除

bucket.delete(new ObjectId(objectId));

System.out.println(“删除完成。”);

}

二、SpringBoot整合MongoDB存储文件

===========================================================================================

MongoDB可以将文件直接存储在文档或者通过GridFS存储大文件,这里同样进行SpringBoot整合MongoDB的两种实现。

1、MongoDB存储小文件


SpringBoot整合MongoDB将文件以文档形式直接存入集合,和普通的MongDB存储区别不大。

1.1、添加依赖

  • spring-boot-starter-data-mongodb:用来操作MongoDB

  • spring-boot-starter-thymeleaf:前端页面采用thymeleaf模板

org.springframework.boot

spring-boot-starter-data-mongodb

org.springframework.boot

spring-boot-starter-thymeleaf

org.springframework.boot

spring-boot-starter-web

1.2、配置

server.address=localhost

server.port=8081

thymeleaf配置,开发环境不启用缓存,正式环境下请启用缓存,提高性能

spring.thymeleaf.cache=false

thymeleaf对html元素格式要求严格,设置它的mode为HTML,忘记结束标签后不会报错

spring.thymeleaf.mode=HTML

编码

spring.http.encoding.charset=UTF-8

spring.http.encoding.enabled=true

MongoDB 配置

连接url

spring.data.mongodb.uri=mongodb://test:test@localhost:27017/filetest

文件上传限制

spring.servlet.multipart.max-file-size=30MB

spring.servlet.multipart.max-request-size=50MB

1.3、模型层

文件模型类:

/**

  • @Author 三分恶

  • @Date 2020/1/11

  • @Description 文档类

*/

@Document

public class FileModel {

@Id // 主键

private String id;

private String name; // 文件名称

private String contentType; // 文件类型

private long size;

private Date uploadDate;

private String md5;

private Binary content; // 文件内容

private String path; // 文件路径

/**

*省略getter/setter

*/

protected FileModel() {

}

public FileModel(String name, String contentType, long size, Binary content) {

this.name = name;

this.contentType = contentType;

this.size = size;

this.content = content;

}

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (!(o instanceof FileModel)) return false;

FileModel fileModel = (FileModel) o;

return size == fileModel.size &&

Objects.equals(id, fileModel.id) &&

Objects.equals(name, fileModel.name) &&

Objects.equals(contentType, fileModel.contentType) &&

Objects.equals(uploadDate, fileModel.uploadDate) &&

Objects.equals(md5, fileModel.md5) &&

Objects.equals(content, fileModel.content) &&

Objects.equals(path, fileModel.path);

}

@Override

public int hashCode() {

return Objects.hash(id, name, contentType, size, uploadDate, md5, content, path);

}

}

1.4、持久层

采用MongoRepository的方式操作MongoDB,只需在接口里继承MongoRepository,就可以调用MongoRepository的内置方法。

public interface FileRepository extends MongoRepository<FileModel,String> {

}

1.5、服务层

服务层接口:

public interface FileService {

/**

  • 保存文件

*/

FileModel saveFile(FileModel file);

/**

  • 删除文件

*/

void removeFile(String id);

/**

  • 根据id获取文件

*/

Optional getFileById(String id);

/**

  • 分页查询,按上传时间降序

  • @return

*/

List listFilesByPage(int pageIndex, int pageSize);

}

服务层实现类:

@Service

public class FileServiceImpl implements FileService {

@Autowired

private FileRepository fileRepository;

@Override

public FileModel saveFile(FileModel file) {

return fileRepository.save(file);

}

@Override

public void removeFile(String id) {

fileRepository.deleteById(id);

}

@Override

public Optional getFileById(String id) {

return fileRepository.findById(id);

}

@Override

public List listFilesByPage(int pageIndex, int pageSize) {

Page page = null;

List list = null;

Sort sort = Sort.by(Sort.Direction.DESC,“uploadDate”);

Pageable pageable = PageRequest.of(pageIndex, pageSize, sort);

page = fileRepository.findAll(pageable);

list = page.getContent();

return list;

}

}

1.6、控制层

@Controller

public class FileController {

@Autowired

private FileService fileService;

@Value(“${server.address}”)

private String serverAddress;

@Value(“${server.port}”)

private String serverPort;

@RequestMapping(value = “/”)

public String index(Model model) {

// 展示最新二十条数据

model.addAttribute(“files”, fileService.listFilesByPage(0, 20));

return “index”;

}

/**

  • 分页查询文件

*/

@GetMapping(“files/{pageIndex}/{pageSize}”)

@ResponseBody

public List listFilesByPage(@PathVariable int pageIndex, @PathVariable int pageSize) {

return fileService.listFilesByPage(pageIndex, pageSize);

}

/**

  • 获取文件片信息

*/

@GetMapping(“files/{id}”)

@ResponseBody

public ResponseEntity serveFile(@RequestParam(“id”) String id) throws UnsupportedEncodingException {

Optional file = fileService.getFileById(id);

if (file.isPresent()) {

return ResponseEntity.ok()

.header(HttpHeaders.CONTENT_DISPOSITION, “attachment; fileName=” + new String(file.get().getName().getBytes(“utf-8”),“ISO-8859-1”))

.header(HttpHeaders.CONTENT_TYPE, “application/octet-stream”)

.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + “”).header(“Connection”, “close”)

.body(file.get().getContent().getData());

} else {

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(“File was not fount”);

}

}

/**

  • 在线显示文件

*/

@GetMapping(“/view”)

@ResponseBody

public ResponseEntity serveFileOnline(@RequestParam(“id”) String id) {

Optional file = fileService.getFileById(id);

if (file.isPresent()) {

return ResponseEntity.ok()

.header(HttpHeaders.CONTENT_DISPOSITION, “fileName=”" + file.get().getName() + “”")

.header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())

.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + “”).header(“Connection”, “close”)

.body(file.get().getContent().getData());

} else {

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(“File was not fount”);

}

}

/**

  • 上传

*/

@PostMapping(“/”)

public String handleFileUpload(@RequestParam(“file”) MultipartFile file, RedirectAttributes redirectAttributes) {

try {

FileModel f = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getSize(),

new Binary(file.getBytes()));

f.setMd5(MD5Util.getMD5(file.getInputStream()));

fileService.saveFile(f);

System.out.println(f);

} catch (IOException | NoSuchAlgorithmException ex) {

ex.printStackTrace();

redirectAttributes.addFlashAttribute(“message”, “Your " + file.getOriginalFilename() + " is wrong!”);

return “redirect:/”;

}

redirectAttributes.addFlashAttribute(“message”,

"You successfully uploaded " + file.getOriginalFilename() + “!”);

return “redirect:/”;

}

/**

  • 上传接口

*/

@PostMapping(“/upload”)

@ResponseBody

public ResponseEntity handleFileUpload(@RequestParam(“file”) MultipartFile file) {

FileModel returnFile = null;

try {

FileModel f = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getSize(),

new Binary(file.getBytes()));

f.setMd5(MD5Util.getMD5(file.getInputStream()));

returnFile = fileService.saveFile(f);

String path = “//” + serverAddress + “:” + serverPort + “/view/” + returnFile.getId();

return ResponseEntity.status(HttpStatus.OK).body(path);

} catch (IOException | NoSuchAlgorithmException ex) {

ex.printStackTrace();

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());

}

}

/**

  • 删除文件

*/

@GetMapping(“/delete”)

@ResponseBody

public ResponseEntity deleteFile( @RequestParam(“id”) String id) {

try {

fileService.removeFile(id);

return ResponseEntity.status(HttpStatus.OK).body(“DELETE Success!”);

} catch (Exception e) {

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());

}

}

}

1.7、工具类

md5工具类:

public class MD5Util {

/**

  • 获取该输入流的MD5值

*/

public static String getMD5(InputStream is) throws NoSuchAlgorithmException, IOException {

StringBuffer md5 = new StringBuffer();

MessageDigest md = MessageDigest.getInstance(“MD5”);

byte[] dataBytes = new byte[1024];

int nread = 0;

while ((nread = is.read(dataBytes)) != -1) {

md.update(dataBytes, 0, nread);

};

byte[] mdbytes = md.digest();

// convert the byte to hex format

for (int i = 0; i < mdbytes.length; i++) {

md5.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));

}

return md5.toString();

}

}

1.8、前端页面

前端页面index.html:

文件服务


首页




上传文件:



文件列表

文件名 文件ID contentType 文件大小 上传时间 md5 操作 没有文件信息!! 预览| 删除

1.9、运行效果

  • 上传文件:

在这里插入图片描述

在这里插入图片描述

  • 预览

在这里插入图片描述

  • 下载

在这里插入图片描述

  • 删除

在这里插入图片描述

在文件的操作过程中,可以通过可视化工具或shell来查看存储在MongoDB中的文件:

  • 可以看到,在fileModel集合中存储了我们上传的文件,文件的内容是以二进制的形式存储

在这里插入图片描述

2、MongoDB存储大文件


Spring Data MongoDB提供了GridFsOperations接口以及相应的实现GridFsTemplate,可以和GridFs交互。

这里在上一个工程的基础上进行改造。

2.1、依赖

和上一个工程相比,这里添加开源工具包hutool的依赖:

cn.hutool

hutool-all

4.5.1

2.2、启动类

@SpringBootApplication

public class SpringbootFileGridfsApplication {

public static void main(String[] args) {

SpringApplication.run(SpringbootFileGridfsApplication.class, args);

}

//Tomcat large file upload connection reset

@Bean

public TomcatServletWebServerFactory tomcatEmbedded() {

TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();

tomcat.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {

if ((connector.getProtocolHandler() instanceof AbstractHttp11Protocol<

?>)) {

//-1 means unlimited

((AbstractHttp11Protocol<?>) connector.getProtocolHandler()).setMaxSwallowSize(-1);

}

});

return tomcat;

}

}

TomcatServletWebServerFactory() ⽅法主要是为了解决上传文件较大时出现连接重置的问题,这个异常后台是捕捉不到的:

在这里插入图片描述

2.3、配置

  • application.properties

MongoDB 配置

连接uri

#spring.data.mongodb.uri=mongodb://test:test@localhost:27017/filetest

spring.data.mongodb.host=localhost

spring.data.mongodb.port=27017

spring.data.mongodb.database=filetest

spring.data.mongodb.username=test

spring.data.mongodb.password=test

文件上传限制

spring.servlet.multipart.max-file-size=1020MB

spring.servlet.multipart.max-request-size=1020MB

  • 配置类

/**

  • @Author 三分恶

  • @Date 2020/1/11

  • @Description

*/

@Configuration

public class MongoConfig {

//获取配置文件中数据库信息

@Value(“${spring.data.mongodb.database}”)

String db;

GridFSBucket用于打开下载流

@Bean

public GridFSBucket getGridFSBucket(MongoClient mongoClient){

MongoDatabase mongoDatabase = mongoClient.getDatabase(db);

GridFSBucket bucket = GridFSBuckets.create(mongoDatabase);

return bucket;

}

}

2.4、实体类

  • 文件实体类

/**

  • @Author 三分恶

  • @Date 2020/1/11

  • @Description

*/

@Document

public class FileDocument {

@Id // 主键

private String id;

private String name; // 文件名称

private long size; // 文件大小

private Date uploadDate; // 上传时间

private String md5; // 文件MD5值

private byte[] content; // 文件内容

private String contentType; // 文件类型

private String suffix; // 文件后缀名

private String description; // 文件描述

private String gridfsId; // 大文件管理GridFS的ID

/**

  • 省略getter、setter、equales、hashCode、toString方法

*/

最后

做任何事情都要用心,要非常关注细节。看起来不起眼的、繁琐的工作做透了会有意想不到的价值。
当然要想成为一个技术大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。

更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
ng.data.mongodb.password=test

文件上传限制

spring.servlet.multipart.max-file-size=1020MB

spring.servlet.multipart.max-request-size=1020MB

  • 配置类

/**

  • @Author 三分恶

  • @Date 2020/1/11

  • @Description

*/

@Configuration

public class MongoConfig {

//获取配置文件中数据库信息

@Value(“${spring.data.mongodb.database}”)

String db;

GridFSBucket用于打开下载流

@Bean

public GridFSBucket getGridFSBucket(MongoClient mongoClient){

MongoDatabase mongoDatabase = mongoClient.getDatabase(db);

GridFSBucket bucket = GridFSBuckets.create(mongoDatabase);

return bucket;

}

}

2.4、实体类

  • 文件实体类

/**

  • @Author 三分恶

  • @Date 2020/1/11

  • @Description

*/

@Document

public class FileDocument {

@Id // 主键

private String id;

private String name; // 文件名称

private long size; // 文件大小

private Date uploadDate; // 上传时间

private String md5; // 文件MD5值

private byte[] content; // 文件内容

private String contentType; // 文件类型

private String suffix; // 文件后缀名

private String description; // 文件描述

private String gridfsId; // 大文件管理GridFS的ID

/**

  • 省略getter、setter、equales、hashCode、toString方法

*/

最后

做任何事情都要用心,要非常关注细节。看起来不起眼的、繁琐的工作做透了会有意想不到的价值。
当然要想成为一个技术大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。

更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务

[外链图片转存中…(img-s6dxeEDr-1714757053959)]

[外链图片转存中…(img-rAwDG9GC-1714757053960)]

[外链图片转存中…(img-YfMnpDg3-1714757053960)]

[外链图片转存中…(img-GpVQEFrS-1714757053960)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值