Salvando arquivos no MongoDB com GridFS
O MongoDB é um banco de dados NoSQL orientado a documentos. Para armazenar estes documentos o MongoDB utiliza coleções. Um fato importante, a saber, é que cada documento de uma coleção no MongoDB pode ter um tamanho máximo de 16MB. Normalmente 16MB é muito mais do que suficiente para armazenar informações em um documento. Contudo, se você precisa armazenar dados maiores, como por exemplo, arquivos de vídeos, fotos de alta resolução ou uma quantidade grande de dados que faça com que o documento ultrapasse 16MB, será preciso criar uma coleção especifica para armazenar estes documentos. Está coleção será do tipo GridFS.
1. O GridFS
GridFS é uma especificação para armazenar e recuperar arquivos que excedem o tamanho limite de 16MB. Quando se usa esta especificação, ao invés de um arquivo ser armazenado em um único documento, o GridFS divide o arquivo em múltiplas partes chamadas de chunks e armazena cada uma dessas partes, ou chunk, em um documento separado. Por padrão o tamanho limite de um chunk será 256KB. Durante o processo de armazenamento o GridFS irá criar duas coleções que são: files e chunks.
2. O GridFS – Coleção Files
A coleção files será responsável por armazenar os metadados do arquivo (Id, descrição, nome, tamanho, data de modificação, etc). Cada documento nesta coleção representa um arquivo do tipo GridFS. Na Listagem 1 é possível observar o layout de um documento da coleção files
.
{
"_id" : ,
"length" : ,
"chunkSize" :
"uploadDate" :
"md5" :
"filename" : ,
"contentType" : ,
"aliases" : ,
"metadata" : ,
}
Um documento nesta coleção poderá conter alguns ou todos os seguintes campos listados a seguir:
- _id – identificador único para cada documento. Poderá ser gerado automaticamente pelo MongoDB ou programaticamente seguindo regras do desenvolvedor;
- length – o tamanho do documento em bytes;
- chunkSize – o tamanho de cada chunk. O GridFS divide cada chunk em tamanhos iguais especificados nesse campo. Lembrando que o tamanho máximo por padrão será de 256KB;
- uploadDate – data referente o armazenamento deste documento;
- md5 – Um hash do tipo MD5;
- filename – campo opcional para armazenar um nome especifico ao arquivo;
- contentType – campo opcional para armazenar o tipo do arquivo;
- aliases – campo opcional do tipo array para armazenar aliases;
- metadata – campo opcional para armazenar dados extras.
3. O GridFS – Coleção Chunks
Na coleção chunks
, cada documento dentro da coleção representa um diferente chunk
de um mesmo arquivo armazenado através de GridFS. Confira na Listagem 2 um documento que representa um chunk
.
{
"_id" : <id value>,
"files_id" : <files_id value>,
"n" : <n value>,
"data" : <data value>:
}
Um documento chunk contém os seguintes campos:
- _id – um identificador único gerado automaticamente;
- files_id – será o identificador do documento pai, ou seja, o
ID
do documento da coleçãofiles
; - n – o valor deste documento na sequencia de chunks. O valor inicial será sempre zero (0);
- data – representa o payload deste
chunk
.
4. Salvando documentos com GridFS
Agora que já conhecemos a parte teórica de um GridFS, vamos usá-lo na pratica. O primeiro passo é claro, será a conexão com o banco de dados. Diferentemente de uma coleção não-GridFS, desta vez não vamos usar o objeto com.mongodb.DBCollection
para interagir com a coleção. A classe com.mongodb.gridfs.GridFS
será responsável por esta interação, como exemplificado na Listagem 3. Note que o banco de dados será o gridtest
e a coleção será chamada de myfiles
. O método construtor da classe GridFS
recebe como paramentro um objeto com.mongodb.DB
e também o nome da coleção de armazenamento dos documentos.
MongoClient client = new MongoClient("localhost", 27017);
DB db = client.getDB("gridtest");
GridFS gridFS = new GridFS(db, "myfiles");
Em seguida, conforme Listagem 4, para salvar um arquivo no banco de dados devemos transformar este arquivo em um stream, para isso, vamos usar a classe java.io.FileInputStream
, informando nome e local do arquivo. A seguir, o objeto com.mongodb.gridfs.GridFSInputFile
deve receber este stream através do método createFile()
da classe GridFS
. Nesta fase é opcional dar um nome ao arquivo, neste caso o nome escolhido foi gridfs.mp4
.
Demais informações foram adicionadas como contentType
e metadada contendo as tags
referentes ao conteúdo do vídeo e um breve descrição do vídeo. Nesta fase é possível também adicionar um _id
próprio, se não deseja que o MongoDB gere automaticamente este identificador, para isso, seria necessário usar o método gridFSInputFile.setId()
. Por fim, basta executar o método gridFSInputFile.save()
para armazenar o arquivo no banco de dados.
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("video.mp4");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BasicDBObject meta = new BasicDBObject();
meta.put("tags", Arrays.asList("mongodb", "gridfs", "java"));
meta.put("description", "MongoDB GridFS lesson");
GridFSInputFile gridFSInputFile = gridFS.createFile(inputStream, "gridfs.mp4");
gridFSInputFile.setMetaData(meta);
gridFSInputFile.setContentType("video/mp4");
gridFSInputFile.save();
5. Recuperando um documento na coleção
Vamos agora aprender como recuperar um documento armazenado na coleção do tipo GridFS
. O primeiro passo é localizar este documento na coleção files
. Você pode usar qualquer campo desta coleção para localizar um arquivo. No exemplo a seguir – Listagem 5 – usamos o campo filename
. O método gridFS.findOne()
recupera o documento no banco de dados retornando um documento do tipo com.mongodb.gridfs.GridFSDBFile
. Agora podemos salvar uma cópia deste arquivo de vídeo no disco rígido. Para isso, vamos criar um objeto java.io.FileOutputStream
. E para o vídeo ser copiado em disco, vamos usar o método gridFSDBFile.writeTo()
.
DBObject video = new BasicDBObject("filename", "gridfs.mp4");
GridFSDBFile gridFSDBFile = gridFS.findOne(video);
try {
FileOutputStream outputStream = new FileOutputStream("video_copy.mp4");
gridFSDBFile.writeTo(outputStream);
} catch (FileNotFoundException e) {
System.out.println("Can't read file - " + e.getCause());
} catch (IOException e) {
System.out.println("Can't write file copy - " + e.getCause());
}
Também é possível conferir os documentos inseridos pelo Shell do MongoDB. Para isso, basta abrir uma conexão, acessar o banco de dados gridtest
. Entretanto, existe uma diferença entre consultar uma coleção do tipo GridFS para um coleção não-GridFS. Na Listagem 6 foi realizado um acesso ao banco e em seguida o comando show collections
retorna as coleções desse banco. Tivemos como retorno três coleções. A coleção system.indexes
é a responsável por armazenar os índices do banco gridtest
. Para este tutorial devemos dar atenção as coleções myfiles.chunks
e myfiles.files
, porque são elas que armazenam os documentos inseridos através de GridFS
.
C:mongodb-2.4.9> mongo.exe
MongoDB shell version: 2.4.9
connecting to: test
> use gridtest
switched to db gridtest
> show collections
myfiles.chunks
myfiles.files
system.indexes
Na Listagem 7 vamos realizar uma consulta na coleção myfiles.files
e em seguida observe o documento retornado. Este documento armazena os metadados informados no insert como a descrição e tags, além de outros campos como a data de inserção, nome do arquivo, etc. O _id
desta coleção é importante caso você queira rodar um consulta na coleção de chunks
.
{
"_id" : ObjectId("52f13be9076e8f798d76c10b"),
"chunkSize" : NumberLong(262144),
"length" : NumberLong(22858345),
"md5" : "67a0cfe00dce39557fa072d74dccea43",
"filename" : "gridfs.mp4",
"contentType" : "video/mp4",
"uploadDate" : ISODate("2014-02-04T19:13:45.536Z"),
"aliases" : null,
"metadata" : {
"tags" : [
"mongodb",
"gridfs",
"java"
],
"description" : "MongoDB GridFS lesson"
}
}
Vamos agora executar duas consultas na coleção de chunks
. A primeira consulta será um count()
para que saibamos quantos chunks
foram criados para o documento inserido. A consulta seguinte irá retornar os chunks deste documento. A chave files _id
representa o identificador do documento da coleção files
. Este campo se repete em cada um dos documentos da coleção chunks
referente ao documento da coleção files
. Foram retornados 88 documentos, o que significa que o vídeo inserido foi dividido em 88 partes.
Para listar estas partes é possível usar o método find()
. Assim, usamos o _id
(da coleção files) para localizar os documentos referentes, e na mesma consulta um segundo parâmetro chamado data. Este parâmetro vai formatar os documentos da forma que os vemos na Listagem 8. Se o parâmetro data não for usado, o retorno será em código binário.
> db.myfiles.chunks.count("files_id" : ObjectId("52f13be9076e8f798d76c10b"))
88
>
> db.myfiles.chunks.find(
{"files_id" : ObjectId("52f13be9076e8f798d76c10b")},
{data : 0}
).pretty()
{
"_id" : ObjectId("52f13be9076e8f798d76c10c"),
"files_id" : ObjectId("52f13be9076e8f798d76c10b"),
"n" : 0
}
{
"_id" : ObjectId("52f13be9076e8f798d76c10d"),
"files_id" : ObjectId("52f13be9076e8f798d76c10b"),
"n" : 1
}
{
"_id" : ObjectId("52f13be9076e8f798d76c10e"),
"files_id" : ObjectId("52f13be9076e8f798d76c10b"),
"n" : 2
}
// demais documentos foram omitidos.
A coleção do tipo GridFS é sugerida como a melhor pratica de armazenamento de documentos no MongoDB. Tais documentos como PDF, AVI, MP3, BMP, DOC, ou qualquer outro tipo. O tamanho também não precisa ser obrigatoriamente maior que 16MB. Por exemplo, se você tem um blog/site, e deseja armazenar suas imagens no banco, use uma coleção GridFS para as imagens e uma coleção normal para o conteúdo das páginas.
Referencias
Documentação GridFS – http://docs.mongodb.org/manual/core/gridfs/