以 Spring MVC 模式编写 MongoDB 的 CURD

日拱一卒,功不唐捐。

一、MongoDB 介绍

  • MongoDB 是文档数据库,NoSQL数据库中的一种,本篇文章,分享使用Spring MVC的方式去编写MongoDB的CURD操作代码,简化问价存储的开发流程。

二、具体操作

A. 小文件存储

  1. 引入MongoDB依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- SpringBoot 用户 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>


<!--SpringBoot 用户 -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>3.1.5</version>
</dependency>
  1. 编写Entity代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@Document // 指定对应的 collection 的名称
public class TemplateDocument implements Serializable {
private static final long serialVersionUID = 5701973075364468575L;

@Id // 文档主键ID
private String id;

// 定义文档属性
private String name;

/**
* 二进制文件内容
*
*/
@JsonIgnore
private Binary content;
}
  1. 编写持久层代码:
1
2
3
4
5
6
7
8
public interface TemplateDao extends MongoRepository<TemplateDocument, String> {

/**
* 根据文件名称查询
*/
@Query("{'name':?0}")
List<TemplateDocument> findByName(String fileName);
}
  1. 编写服务层代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Service
public class TemplateService {

@Autowired
private TemplateDao templateDao;

/**
* 查询文档
*/
public List<TemplateDocument> queryDocumentList(){
return templateDao.findAll();
}

/**
* 新增文档
*/
public TemplateDocument addTemplateDocument(TemplateDocument entity){
return templateDao.insert(entity);
}

/**
* 修改文档
*/
public TemplateDocument modifyTemplateDocument(TemplateDocument entity){
TemplateDocument preModifyDocument = templateDao.findById(entity.getId()).get();
preModifyDocument.setName(entity.getName());
return templateDao.save(preModifyDocument);
}

/**
* 删除文档
*/
public void deleteDocumentById(String id){
templateDao.deleteById(id);
}

public Optional<TemplateDocument> getFileById(String id){
return templateDao.findById(id);
}

public TemplateDocument findOneByName(String fileName){
List<TemplateDocument> result = templateDao.findByName(fileName);
if (CollectionUtil.isEmpty(result)){
return new TemplateDocument();
}
return result.get(0);
}
}
  1. 编写控制层代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Api(tags = "MongoDB 文件管理")
@RestController
@RequestMapping("/template")
public class TemplateController {
@Autowired
private TemplateService templateService;

@ApiOperation("小文件上传接口")
@PostMapping("/upload")
public ServerResponse mongoFileUploda(@RequestParam("file") MultipartFile file) throws IOException {
TemplateDocument entity = new TemplateDocument();
entity.setId(UUID.fastUUID().toString());
entity.setName(file.getOriginalFilename());
entity.setContent(new Binary(file.getBytes()));


return ServerResponse.createBySuccess(templateService.saveFile(entity));
}

@ApiOperation("小文件下载接口")
@GetMapping("/download")
public ResponseEntity<Object> mongoFileDownload(@RequestParam String id) {
Optional<TemplateDocument> file = templateService.getFileById(id);
if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + file.get().getName() + "\"")
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.body(file.get().getContent().getData());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
}
}

@ApiOperation("文档列表查询")
@GetMapping("/queryDocumentList")
public ServerResponse queryDocumentList(){
return ServerResponse.createBySuccess(templateService.queryDocumentList());
}

@ApiOperation("文件名搜索")
@GetMapping("/searchByFileName")
public ServerResponse searchByFileName(String fileName){
return ServerResponse.createBySuccess(templateService.findOneByName(fileName));
}

@ApiOperation("根据文件ID读取文件内容并返回")
@GetMapping("/getDataByFileId")
public ServerResponse getDataByFileId(@RequestParam String fileId){
return ServerResponse.createBySuccess(templateService.getDataByFileId(fileId));
}
}

B. 大文件存储

  • 大文件存储,使用有 Spring Data 依赖中提供的 GridFsTemplate,跟 gridFs 进行交互。
  1. 配置信息:
    1. 设置 MongoDB 文件上传的上限
    2. 往 Spring 容器中注入 GridFSBucket, 用于打开下载流
1
2
3
4
5
6
# application.yml
spring:
servlet: # 设置文件上传上限
multipart:
max-file-size: 1024MB
max-request-size: 1024MB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# MongoConfig.java
// MongoDB 配置文件
@Configuration
public class MongoConfig {
@Value("${spring.data.mongodb.database}")
private String dbName;

/**
* 往 Spring 容器中注入 GridFSBucket, 用于打开下载流
*/
@Bean
public GridFSBucket getGridFsBucket(MongoClient mongoClient) {
MongoDatabase mongoDatabase = mongoClient.getDatabase(dbName);
return GridFSBuckets.create(mongoDatabase);
}
}
  1. 实体类代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 大文件存储实体类
*/
@Data
@Document
public class HugeFileEntity {
/**
* 主键ID
*/
@Id
private String id;
/**
* 创建时间
*/
private Date creationDate;
/**
* 最后修改时间
*/
private Date lastUpdateTime;
/**
* 文件名
*/
private String name;
/**
* 文件大小
*/
private Long size;
/**
* 文件 MD5 加密值
*/
private String md5;
/**
* 文件类型
*/
private String contentType;
/**
* 文件名后缀
*/
private String fileType;
/**
* mongo gridFS 的ID
*/
private String gridFsId;
/**
* 文件内容
*/
private byte[] content;
}
  1. Service 层:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
@Service
public class HugeFileService {
@Autowired
private MongoTemplate mongoTemplate;

@Autowired
private GridFsTemplate gridFsTemplate;

@Autowired
private GridFSBucket gridFSBucket;

/**
* 文件新增
*/
public HugeFileEntity fileAdd(MultipartFile uploadFile) throws IOException {
if (uploadFile == null) {
return null;
}
String fileName = uploadFile.getOriginalFilename();
Builder<HugeFileEntity> entityBuilder = Builder.of(HugeFileEntity::new);
entityBuilder
.with(HugeFileEntity::setContentType, uploadFile.getContentType())
.with(HugeFileEntity::setFileType, StrUtil.subAfter(fileName, ".", true))
.with(HugeFileEntity::setCreationDate, new Date())
.with(HugeFileEntity::setName, fileName)
.with(HugeFileEntity::setSize, uploadFile.getSize())
.with(HugeFileEntity::setLastUpdateTime, new Date())
.with(HugeFileEntity::setMd5, MD5.create().digestHex(uploadFile.getInputStream()));
String gridFsId = this.uploadFile(uploadFile.getInputStream(), uploadFile.getContentType());
entityBuilder.with(HugeFileEntity::setGridFsId, gridFsId);
return mongoTemplate.save(entityBuilder.build());
}

/**
* 上传文件到 gridFS
*/
public String uploadFile(InputStream inputStream, String contentType) {
String fileName = IdUtil.objectId();
return gridFsTemplate.store(inputStream, fileName, contentType).toString();
}

/**
* 文件删除
*/
public void deleteFile(String id) {
// 查询待删除文件
HugeFileEntity entity = mongoTemplate.findById(id, HugeFileEntity.class);
if (entity != null) {
// 删除文件在gridFS中对应的fs.chunk和fs.files记录
Query deleteFileQuery = new Query().addCriteria(Criteria.where("_id").is(entity.getGridFsId()));
gridFsTemplate.delete(deleteFileQuery);
// 删除存储在collection中的文档记录
Query query = new Query(Criteria.where("id").is(entity.getId()));
mongoTemplate.remove(query, HugeFileEntity.class);
}
}

/**
* 主键ID查询文件
*/
public Optional<HugeFileEntity> findFileById(String id, boolean useContent) throws IOException {
HugeFileEntity entity = mongoTemplate.findById(id, HugeFileEntity.class);
if (entity == null) {
return Optional.empty();
}
if (!useContent) {
return Optional.of(entity);
}
Query gridFsQuery = new Query().addCriteria(Criteria.where("_id").is(entity.getGridFsId()));
GridFSFile file = gridFsTemplate.findOne(gridFsQuery);
// 打开流下载对象
GridFSDownloadStream ins = gridFSBucket.openDownloadStream(file.getObjectId());
if (ins.getGridFSFile().getLength() <= 0) {
return Optional.empty();
}
// 获取流对象
GridFsResource resource = new GridFsResource(file, ins);
// 获取数据
entity.setContent(IoUtil.readBytes(resource.getInputStream()));
return Optional.of(entity);
}

public Optional<HugeFileEntity> findFileById(String id) throws IOException {
return this.findFileById(id, false);
}

/**
* 分页查询
*/
public List<HugeFileEntity> queryPage(int pageNum, int pageSize) {
Query query = new Query().with(Sort.by(Sort.Direction.DESC, "creationDate"));
long offset = (long) (pageNum - 1) * pageSize;
query.skip(offset);
query.limit(pageSize);
// 排除附件实际内容
Field field = query.fields();
field.exclude("content");
return mongoTemplate.find(query, HugeFileEntity.class);
}
}
  1. Controller 层:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Api(tags = "大文件管理")
@RequestMapping("/hugeFile")
@RestController
public class HugeFileController {
@Autowired
private HugeFileService hugeFileService;

/**
* 分页查询
*/
@ApiOperation("分页查询接口")
@GetMapping("/queryPage")
public ServerResponse<List<HugeFileEntity>> index(int pageNum, int pageSize) {
return ServerResponse.createBySuccess(hugeFileService.queryPage(pageNum, pageSize));
}

/**
* 文件上传
*/
@ApiOperation("文件上传接口")
@PostMapping("/upload")
public ServerResponse<HugeFileEntity> uploadFile(MultipartFile uploadFile) throws IOException {
return ServerResponse.createBySuccess(hugeFileService.fileAdd(uploadFile));
}

/**
* 文件下载
*/
@ApiOperation("文件下载接口")
@PostMapping("/downloadFile")
public ResponseEntity<Object> downloadFile(String id) throws IOException {
// 根据文档主键ID下载文件
Optional<HugeFileEntity> entity = hugeFileService.findFileById(id, true);
return entity.<ResponseEntity<Object>>map(hugeFileEntity -> ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "fileName=" + hugeFileEntity.getName())
.header(HttpHeaders.CONTENT_TYPE, hugeFileEntity.getContentType())
.header(HttpHeaders.CONTENT_LENGTH, hugeFileEntity.getSize() + "")
.header("Connection", "close")
.body(hugeFileEntity.getContent())).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).body("不存在的文件"));
}

/**
* 文件删除
*/
@ApiOperation("文件删除接口")
@DeleteMapping("/delete")
public ServerResponse<HugeFileEntity> deleteFile(String id) {
hugeFileService.deleteFile(id);
return ServerResponse.createBySuccessMessage("删除成功");
}
}

三、大文件存储关系图

实体类关系图

entity-relay