How to get a binary stream by GridFS ObjectId with

2020-05-26 03:27发布

I can't figure out how to stream a binary file from GridFS with spring-data-mongodb and its GridFSTemplate when I already have the right ObjectId.

GridFSTemplate returns either GridFSResource (getResource()) or GridFSFile (findX()).

I can get the GridFSFile by ID:

// no way to get the InputStream?
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)))

but there is no obvious way how to get an InputStream for that GridFSFile.

Only GridFSResource allows me to get hold of the corresonding InputStream with InputStreamResource#getInputstream. But the only way to get a GridFSResource is by its filename.

// no way to get GridFSResource by ID?
GridFSResource resource = gridFsTemplate.getResource("test.jpeg");
return resource.getInputStream();

Somehow the GridFsTemplate API implies that filenames are unique - which they are not. The GridFsTemplate implementation just returns the first element.

Now I'm using the native MongoDB API and everything makes sense again:

GridFS gridFs = new GridFs(mongo);
GridFSDBFile nativeFile = gridFs.find(blobId);
return nativeFile.getInputStream();

It looks like I'm misunderstanding the fundamental concepts behind the Spring Data Mongo GridFS abstraction. I'd expect (at least) one of the following things to be possible/true:

  • get a GridFSResource by its ID
  • get a GridFSResource or InputStream for a GridFsFile I already have

Am I wrong or is there something odd with this particular piece of the Spring Data MongoDB API?

9条回答
Animai°情兽
2楼-- · 2020-05-26 04:03

Wrap the GridFSFile in a GridFsResource or use this

GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
GridFsResource resource = gridFsTemplate.getResource(file);
return resource.getInputStream();
查看更多
劫难
3楼-- · 2020-05-26 04:09

Have you condidered using Spring Content for Mongo for the content storage piece on your solution?

Assuming you are using Spring Boot as well as Spring Data Mongo then it might look something like the following:

pom.xml

<dependency>
    <groupId>com.github.paulcwarren</groupId>
    <artifactId>spring-content-mongo-boot-starter</artifactId>
    <version>0.0.10</version>
</dependency>
<dependency>
    <groupId>com.github.paulcwarren</groupId>
    <artifactId>spring-content-rest-boot-starter</artifactId>
    <version>0.0.10</version>
</dependency>

Update your Spring Data Mongo entity, with the following attributes:

@ContentId
private String contentId;

@ContentLength 
private long contentLength = 0L;

@MimeType
private String mimeType;

Add a store interface:

@StoreRestResource(path="content")
public interface MongoContentStore extends ContentStore<YourEntity, String> {
}

That's all that you need. When you application starts Spring Content will see the dependencies on the Spring Content Mongo/REST modules and it will inject an implementation of the MongonContenStore store for GridFs as well as an implementation of a controller that supports full CRUD functionality and maps those operations down onto the underlying store interface. The REST endpoint will be available under /content.

i.e.

curl -X PUT /content/{entityId} will create or update an entity's image

curl -X GET /content/{entityId} will fetch the entity's image

curl -X DELETE /content/{entityId} will delete the entity's image

There are a couple of getting started guides here. They use Spring Content for the filesystem but the modules are interchangeable. The Mongo reference guide is here. And there is a tutorial video here.

HTH

查看更多
SAY GOODBYE
4楼-- · 2020-05-26 04:13

i discovered the solution to this problem!

Just wrap the GridFSFile in a GridFsResource! This is designed to be instantiated with a GridFSFile.

public GridFsResource getUploadedFileResource(String id) {
    var file = this.gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));
    return new GridFsResource(file);
}

@GetMapping("/{userId}/files/{id}")
public ResponseEntity<InputStreamResource> getUploadedFile(
    @PathVariable Long userId,
    @PathVariable String id
){
    var user = userService
        .getCurrentUser()
        .orElseThrow(EntityNotFoundException::new);

    var resource = userService.getUploadedFileResource(id);

    try {
        return ResponseEntity
            .ok()
            .contentType(MediaType.parseMediaType(resource.getContentType()))
            .contentLength(resource.contentLength())
            .body(resource);
    } catch (IOException e) {
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }


}

The great advantage of this is, that you can directly pass the GridFsResource to a ResponseEntity due to the fact, that the GridFsResource extends a InputStreamResource.

Hope this helps!

Greetings Niklas

查看更多
迷人小祖宗
5楼-- · 2020-05-26 04:13

Old question I know, but trying to do this in 2019 using WebFlux, I had to do the following

  public Mono<GridFsResource> getImageFromDatabase(final String id) {

    return Mono.fromCallable(
        () ->
            this.gridFsTemplate.getResource(
                Objects.requireNonNull(
                        this.gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id))))
                    .getFilename()));
  }

Which will give you a Mono which can be returned in a controller. I'm sure there is a nicer solution however.

查看更多
Viruses.
6楼-- · 2020-05-26 04:15

There is a bit mess in these types:

From Spring GridFsTemplate source:

public getResource(String location) {

    GridFSFile file = findOne(query(whereFilename().is(location)));
    return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
}

There is an ugly solution:

@Autowired
private GridFsTemplate template;

@Autowired
private GridFsOperations operations;

public InputStream loadResource(ObjectId id) throws IOException {
    GridFSFile file = template.findOne(query(where("_id").is(id)));
    GridFsResource resource = template.getResource(file.getFilename());

    GridFSFile file = operations.findOne(query(where("_id").is(id)));
    GridFsResource resource = operations.getResource(file.getFilename());
    return resource.getInputStream();
}
查看更多
你好瞎i
7楼-- · 2020-05-26 04:16

getResource(com.mongodb.client.gridfs.model.GridFSFile file) function of GridFsTemplate returns the GridFsResource for a GridFSFile.

GridFSFile gridfsFile= gridFsTemplate.findOne(new 
Query(Criteria.where("filename").is(fileName)));
GridFsResource gridFSResource= gridFsTemplate.getResource(gridfsFile);
InputStream inputStream= gridFSResource.getInputStream();

If the above one is not working in some higher version of Spring boot, use the bellow:

GridFSFile gridfsFile= gridFsTemplate.findOne(new 
Query(Criteria.where("filename").is(fileName)));
//or
GridFSFile  gridfsFile = 
gridFsOperations.findOne(Query.query(Criteria.where("filename").is(fileName)));
 return ResponseEntity.ok()
                .contentLength(gridFsdbFile.getLength())
                .contentType(MediaType.valueOf("image/png"))
                .body(gridFsOperations.getResource(gridFsdbFile));
查看更多
登录 后发表回答