{"id":2211,"date":"2016-03-30T14:53:18","date_gmt":"2016-03-30T14:53:17","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=2211"},"modified":"2025-06-13T20:28:54","modified_gmt":"2025-06-14T03:28:54","slug":"storing-indexing-searching-files-couchbase-spring-boot","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/pt\/storing-indexing-searching-files-couchbase-spring-boot\/","title":{"rendered":"Armazenamento, indexa\u00e7\u00e3o e pesquisa de arquivos com o Couchbase e o Spring Boot"},"content":{"rendered":"<p>Eu queria mostrar a maioria dos novos recursos de pesquisa do Couchbase dispon\u00edveis na vers\u00e3o 4.5 em um projeto simples. E recentemente houve algum interesse sobre <a href=\"\/blog\/pt\/storing-base64-files-directly-in-couchbase-via-nodejs\/\">armazenamento de arquivos<\/a> ou <a href=\"\/blog\/pt\/storing-blobs-in-couchbase-for-content-management\/\">bin\u00e1rios<\/a> no Couchbase. De uma perspectiva geral e gen\u00e9rica, os bancos de dados n\u00e3o s\u00e3o feitos para armazenar arquivos ou bin\u00e1rios. Normalmente, o que voc\u00ea faria \u00e9 armazenar arquivos em um reposit\u00f3rio bin\u00e1rio e seus metadados associados no banco de dados. Os metadados associados ser\u00e3o o local do arquivo no armazenamento bin\u00e1rio e o m\u00e1ximo poss\u00edvel de informa\u00e7\u00f5es extra\u00eddas do arquivo.<\/p>\n<p>Portanto, este \u00e9 o projeto que mostrarei a voc\u00ea hoje. \u00c9 um aplicativo Spring Boot muito simples que permite que o usu\u00e1rio fa\u00e7a upload de arquivos, armazene-os em um reposit\u00f3rio bin\u00e1rio, onde o texto e os metadados associados ser\u00e3o extra\u00eddos do arquivo, e permite que voc\u00ea pesquise arquivos com base nesses metadados e no texto. No final, voc\u00ea poder\u00e1 pesquisar arquivos por tipo de imagem, tamanho da imagem, conte\u00fado do texto, basicamente qualquer metadado que possa ser extra\u00eddo do arquivo.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/original-assets\/2016\/march\/storing-indexing-searching-files-couchbase-spring-boot\/storesearchindex.png\" \/><\/p>\n<h2>A Loja Bin\u00e1ria<\/h2>\n<p>Essa \u00e9 uma pergunta que recebemos com frequ\u00eancia. Certamente \u00e9 poss\u00edvel armazenar dados bin\u00e1rios em um banco de dados, mas os arquivos devem estar em um armazenamento bin\u00e1rio apropriado. Decidi criar uma implementa\u00e7\u00e3o muito simples para este exemplo. Basicamente, h\u00e1 uma pasta no sistema de arquivos declarada no tempo de execu\u00e7\u00e3o que conter\u00e1 todos os arquivos carregados. Um resumo SHA1 ser\u00e1 calculado a partir do conte\u00fado do arquivo e usado como nome de arquivo nessa pasta. Obviamente, voc\u00ea poderia usar outros armazenamentos bin\u00e1rios mais avan\u00e7ados, como o Manta da Joyent ou o Amazon S3, por exemplo. Mas vamos manter as coisas simples para esta postagem :) Aqui est\u00e1 uma descri\u00e7\u00e3o dos servi\u00e7os usados.<\/p>\n<h3>SHA1Servi\u00e7o<\/h3>\n<p>Esse \u00e9 o mais simples, com um m\u00e9todo que basicamente envia de volta um resumo SHA-1 com base no conte\u00fado do arquivo. Para simplificar ainda mais o c\u00f3digo, estou usando o Apache <a href=\"https:\/\/commons.apache.org\/proper\/commons-codec\/\">commons-codec<\/a>:<\/p>\n<pre><code class=\"language-java\">@Service\r\npublic class SHA1Service {\r\n\r\n    public String getSha1Digest(InputStream is) {\r\n        try {\r\n            return DigestUtils.sha1Hex(is);\r\n        } catch (IOException e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n    }\r\n\r\n}\r\n<\/code><\/pre>\n<h3>DataExtractionService<\/h3>\n<p>Esse servi\u00e7o foi criado para extrair metadados e texto dos arquivos carregados. H\u00e1 muitas maneiras diferentes de fazer isso. Eu optei por usar o <a href=\"https:\/\/www.sno.phy.queensu.ca\/%7Ephil\/exiftool\/\">ExifTool<\/a> e <a href=\"https:\/\/en.wikipedia.org\/wiki\/Poppler_%28software%29\">Poppler<\/a>.<\/p>\n<p>O ExifTool \u00e9 uma excelente ferramenta de linha de comando para ler, gravar e editar metadados de arquivos. Ele tamb\u00e9m pode gerar metadados diretamente em JSON. E, \u00e9 claro, n\u00e3o se limita ao padr\u00e3o Exif. Ele \u00e9 compat\u00edvel com uma grande variedade de formatos. O Poppler \u00e9 uma biblioteca de utilit\u00e1rios para PDF que me permitir\u00e1 extrair o conte\u00fado de texto de um PDF. Como essas ferramentas s\u00e3o de linha de comando, usarei <a href=\"https:\/\/codehaus-plexus.github.io\/plexus-utils\/\">plexus-utils<\/a> para facilitar as chamadas da CLI.<\/p>\n<p>H\u00e1 dois m\u00e9todos. O primeiro \u00e9 <em>extractMetadata<\/em> e \u00e9 respons\u00e1vel pela extra\u00e7\u00e3o de metadados do ExifTool. \u00c9 o equivalente a executar o seguinte comando:<\/p>\n<pre><code> exiftool -n -json somePDFFile\r\n<\/code><\/pre>\n<p>O <code>-n<\/code> est\u00e1 aqui para garantir que todos os valores num\u00e9ricos sejam fornecidos como n\u00fameros e n\u00e3o como strings e <code>-json<\/code> para garantir que a sa\u00edda esteja no formato JSON. Isso pode lhe dar uma sa\u00edda como esta:<\/p>\n<pre><code>[{\r\n  \"SourceFile\": \"Desktop\/someFile.pdf\",\r\n  \"ExifToolVersion\": 10.11,\r\n  \"FileName\": \"someFile.pdf\",\r\n  \"Directory\": \"Desktop\",\r\n  \"FileSize\": 20468,\r\n  \"FileModifyDate\": \"2016:03:29 13:50:29+02:00\",\r\n  \"FileAccessDate\": \"2016:03:29 13:50:33+02:00\",\r\n  \"FileInodeChangeDate\": \"2016:03:29 13:50:33+02:00\",\r\n  \"FilePermissions\": 644,\r\n  \"FileType\": \"PDF\",\r\n  \"FileTypeExtension\": \"PDF\",\r\n  \"MIMEType\": \"application\/pdf\",\r\n  \"PDFVersion\": 1.4,\r\n  \"Linearized\": false,\r\n  \"ModifyDate\": \"2016:03:29 02:42:32-07:00\",\r\n  \"CreateDate\": \"2016:03:29 02:42:32-07:00\",\r\n  \"Producer\": \"iText 2.1.6 by 1T3XT\",\r\n  \"PageCount\": 1\r\n}]\r\n<\/code><\/pre>\n<p>H\u00e1 algumas informa\u00e7\u00f5es interessantes, como o tipo mime, o tamanho, a data de cria\u00e7\u00e3o e muito mais. Se o tipo mime do arquivo for <code>aplicativo\/pdf<\/code> ent\u00e3o podemos tentar extrair o texto dele com o poppler, que \u00e9 o que o segundo m\u00e9todo do servi\u00e7o est\u00e1 fazendo. Ele \u00e9 equivalente \u00e0 seguinte chamada da CLI:<\/p>\n<pre><code>pdftotext -raw somePDFFile -\r\n<\/code><\/pre>\n<p>Esse comando envia o texto extra\u00eddo para a sa\u00edda padr\u00e3o. Que podemos recuperar e colocar em um arquivo <code>texto completo<\/code> em um objeto JSON. C\u00f3digo completo do servi\u00e7o abaixo:<\/p>\n<pre><code class=\"language-java\">package org.couchbase.devex.service;\r\n\r\nimport java.io.File;\r\n\r\nimport org.codehaus.plexus.util.cli.CommandLineException;\r\nimport org.codehaus.plexus.util.cli.CommandLineUtils;\r\nimport org.codehaus.plexus.util.cli.Commandline;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.stereotype.Service;\r\n\r\nimport com.couchbase.client.java.document.json.JsonArray;\r\nimport com.couchbase.client.java.document.json.JsonObject;\r\n\r\n@Service\r\npublic class DataExtractionService {\r\n\r\n    private final Logger log = LoggerFactory.getLogger(DataExtractionService.class);\r\n\r\n    public JsonObject extractMetadata(File file) {\r\n        String command = \"\/usr\/local\/bin\/exiftool\";\r\n        String[] arguments = { \"-json\", \"-n\", file.getAbsolutePath() };\r\n        Commandline commandline = new Commandline();\r\n        commandline.setExecutable(command);\r\n        commandline.addArguments(arguments);\r\n\r\n        CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();\r\n        CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();\r\n\r\n        try {\r\n            CommandLineUtils.executeCommandLine(commandline, out, err);\r\n        } catch (CommandLineException e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n\r\n        String output = out.getOutput();\r\n        if (!output.isEmpty()) {\r\n            JsonArray arr = JsonArray.fromJson(output);\r\n            return arr.getObject(0);\r\n        }\r\n\r\n        String error = err.getOutput();\r\n        if (!error.isEmpty()) {\r\n            log.error(error);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public String extractText(File file) {\r\n        String command = \"\/usr\/local\/bin\/pdftotext\";\r\n        String[] arguments = { \"-raw\", file.getAbsolutePath(), \"-\" };\r\n        Commandline commandline = new Commandline();\r\n        commandline.setExecutable(command);\r\n        commandline.addArguments(arguments);\r\n\r\n        CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();\r\n        CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();\r\n\r\n        try {\r\n            CommandLineUtils.executeCommandLine(commandline, out, err);\r\n        } catch (CommandLineException e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n\r\n        String output = out.getOutput();\r\n        if (!output.isEmpty()) {\r\n            return output;\r\n        }\r\n\r\n        String error = err.getOutput();\r\n        if (!error.isEmpty()) {\r\n            log.error(error);\r\n        }\r\n        return null;\r\n    }\r\n}<\/code><\/pre>\n<p>Coisas bastante simples, como voc\u00ea pode ver depois de usar o plexus-utils.<\/p>\n<h3>BinaryStoreService<\/h3>\n<p>Esse servi\u00e7o \u00e9 respons\u00e1vel por executar a extra\u00e7\u00e3o de dados e armazenar arquivos, excluir arquivos ou recuperar arquivos. Vamos come\u00e7ar com a parte de armazenamento. Tudo acontece no servi\u00e7o <code>storeFile<\/code> m\u00e9todo. A primeira coisa a fazer \u00e9 recuperar o resumo do arquivo e, em seguida, grav\u00e1-lo na pasta de armazenamento bin\u00e1rio declarada na configura\u00e7\u00e3o. Depois que o arquivo \u00e9 gravado, o servi\u00e7o de extra\u00e7\u00e3o de dados \u00e9 chamado para recuperar os metadados como um JsonObject. Em seguida, o local do armazenamento bin\u00e1rio, o tipo de documento, o resumo e o nome do arquivo s\u00e3o adicionados a esse objeto JSON. Se o arquivo carregado for um PDF, o servi\u00e7o de extra\u00e7\u00e3o de dados ser\u00e1 chamado novamente para recuperar o conte\u00fado do texto e armazen\u00e1-lo em um objeto <code>texto completo<\/code> field. Em seguida, um JsonDocument \u00e9 criado com o digest como chave e o JsonObject como conte\u00fado.<\/p>\n<pre><code class=\"language-java\">    public void storeFile(String name, MultipartFile uploadedFile) {\r\n        if (!uploadedFile.isEmpty()) {\r\n            try {\r\n                String digest = sha1Service.getSha1Digest(uploadedFile.getInputStream());\r\n                File file2 = new File(configuration.getBinaryStoreRoot() + File.separator + digest);\r\n                BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file2));\r\n                FileCopyUtils.copy(uploadedFile.getInputStream(), stream);\r\n                stream.close();\r\n                JsonObject metadata = dataExtractionService.extractMetadata(file2);\r\n                metadata.put(StoredFileDocument.BINARY_STORE_DIGEST_PROPERTY, digest);\r\n                metadata.put(\"type\", StoredFileDocument.COUCHBASE_STORED_FILE_DOCUMENT_TYPE);\r\n                metadata.put(StoredFileDocument.BINARY_STORE_LOCATION_PROPERTY, name);\r\n                metadata.put(StoredFileDocument.BINARY_STORE_FILENAME_PROPERTY, uploadedFile.getOriginalFilename());\r\n                String mimeType = metadata.getString(StoredFileDocument.BINARY_STORE_METADATA_MIMETYPE_PROPERTY);\r\n                if (MIME_TYPE_PDF.equals(mimeType)) {\r\n                    String fulltextContent = dataExtractionService.extractText(file2);\r\n                    metadata.put(StoredFileDocument.BINARY_STORE_METADATA_FULLTEXT_PROPERTY, fulltextContent);\r\n                }\r\n                JsonDocument doc = JsonDocument.create(digest, metadata);\r\n                bucket.upsert(doc);\r\n            } catch (Exception e) {\r\n                throw new RuntimeException(e);\r\n            }\r\n        } else {\r\n            throw new IllegalArgumentException(\"File empty\");\r\n        }\r\n    }\r\n<\/code><\/pre>\n<p>Ler ou excluir deve ser bastante simples de entender agora:<\/p>\n<pre><code class=\"language-java\">    public StoredFile findFile(String digest) {\r\n        File f = new File(configuration.getBinaryStoreRoot() + File.separator + digest);\r\n        if (!f.exists()) {\r\n            return null;\r\n        }\r\n        JsonDocument doc = bucket.get(digest);\r\n        if (doc == null)\r\n            return null;\r\n        StoredFileDocument fileDoc = new StoredFileDocument(doc);\r\n        return new StoredFile(f, fileDoc);\r\n    }\r\n\r\n    public void deleteFile(String digest) {\r\n        File f = new File(configuration.getBinaryStoreRoot() + File.separator + digest);\r\n        if (!f.exists()) {\r\n            throw new IllegalArgumentException(\"Can't delete file that does not exist\");\r\n        }\r\n        f.delete();\r\n        bucket.remove(digest);\r\n    }\r\n<\/code><\/pre>\n<p>Lembre-se de que essa \u00e9 uma implementa\u00e7\u00e3o muito ing\u00eanua!<\/p>\n<h2>Indexa\u00e7\u00e3o e pesquisa de arquivos<\/h2>\n<p>Depois de fazer o upload dos arquivos, voc\u00ea deseja recuper\u00e1-los. A primeira maneira muito b\u00e1sica de fazer isso seria exibir a lista completa de arquivos. Em seguida, voc\u00ea poderia usar o N1QL para pesquis\u00e1-los com base em suas metadatas ou o FTS para pesquis\u00e1-los com base em seu conte\u00fado.<\/p>\n<h3>O servi\u00e7o de busca<\/h3>\n<p><code>getFiles<\/code> simplesmente executa a seguinte consulta: <code>SELECT binaryStoreLocation, binaryStoreDigest FROM<\/code>padr\u00e3o<code>WHERE type= 'file'<\/code>. Isso envia a lista completa de arquivos carregados com seu resumo e o local do armazenamento bin\u00e1rio. Observe a op\u00e7\u00e3o de consist\u00eancia definida como <em>statement_plus<\/em>. Como se trata de um aplicativo de documentos, prefiro uma consist\u00eancia forte.<\/p>\n<p>Em seguida, voc\u00ea tem <code>searchN1QLFiles<\/code> que executa uma consulta N1QL b\u00e1sica com uma cl\u00e1usula WHERE adicional. Portanto, o padr\u00e3o \u00e9 a mesma consulta acima com uma parte WHERE adicional. At\u00e9 o momento, n\u00e3o h\u00e1 uma integra\u00e7\u00e3o mais estreita. Poder\u00edamos ter um formul\u00e1rio de pesquisa sofisticado que permitisse ao usu\u00e1rio pesquisar arquivos com base em seus tipos de mime, tamanho ou quaisquer outros campos fornecidos pelo ExifTool.<\/p>\n<p>E, finalmente, voc\u00ea tem <code>searchFulltextFiles<\/code> que recebe uma String como entrada e a usa em um <a href=\"https:\/\/www.blevesearch.com\/docs\/Query\/\">Jogo<\/a> consulta. Em seguida, o resultado \u00e9 enviado de volta com fragmentos de texto em que o termo foi encontrado. Esse fragmento permite destacar o termo no contexto. Tamb\u00e9m solicito o <em>binaryStoreDigest<\/em> e <em>binaryStoreLocation<\/em> campos. Eles s\u00e3o usados para exibir os resultados para o usu\u00e1rio.<\/p>\n<pre><code class=\"language-java\">    public List&lt;Map&lt;String, Object&gt;&gt; getFiles() {\r\n        N1qlQuery query = N1qlQuery\r\n                .simple(\"SELECT binaryStoreLocation, binaryStoreDigest FROM `default` WHERE type= 'file'\");\r\n        query.params().consistency(ScanConsistency.STATEMENT_PLUS);\r\n        N1qlQueryResult res = bucket.query(query);\r\n        List&lt;Map&lt;String, Object&gt;&gt; filenames = res.allRows().stream().map(row -&gt; row.value().toMap())\r\n                .collect(Collectors.toList());\r\n        return filenames;\r\n    }\r\n\r\n    public List&lt;Map&lt;String, Object&gt;&gt; searchN1QLFiles(String whereClause) {\r\n        N1qlQuery query = N1qlQuery.simple(\r\n                \"SELECT binaryStoreLocation, binaryStoreDigest FROM `default` WHERE type= 'file' \" + whereClause);\r\n        query.params().consistency(ScanConsistency.STATEMENT_PLUS);\r\n        N1qlQueryResult res = bucket.query(query);\r\n        List&lt;Map&lt;String, Object&gt;&gt; filenames = res.allRows().stream().map(row -&gt; row.value().toMap())\r\n                .collect(Collectors.toList());\r\n        return filenames;\r\n    }\r\n\r\n    public List&lt;Map&lt;String, Object&gt;&gt; searchFulltextFiles(String term) {\r\n        SearchQuery ftq = MatchQuery.on(\"file_fulltext\").match(term)\r\n                .fields(\"binaryStoreDigest\", \"binaryStoreLocation\").build();\r\n        SearchQueryResult result = bucket.query(ftq);\r\n        List&lt;Map&lt;String, Object&gt;&gt; filenames = result.hits().stream().map(row -&gt; {\r\n            Map&lt;String, Object&gt; m = new HashMap&lt;String, Object&gt;();\r\n            m.put(\"binaryStoreDigest\", row.fields().get(\"binaryStoreDigest\"));\r\n            m.put(\"binaryStoreLocation\", row.fields().get(\"binaryStoreLocation\"));\r\n            m.put(\"fragment\", row.fragments().get(\"fulltext\"));\r\n            return m;\r\n        }).collect(Collectors.toList());\r\n        return filenames;\r\n    }\r\n<\/code><\/pre>\n<p>O <code>TermQuery.on<\/code> define qual \u00edndice estou consultando. Aqui ele est\u00e1 definido como 'file_fulltext'. Isso significa que criei um \u00edndice de texto completo com esse nome:<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/original-assets\/2016\/march\/storing-indexing-searching-files-couchbase-spring-boot\/fulltextconfig.png\" \/><\/p>\n<h2>Colocando tudo junto<\/h2>\n<h3>Configura\u00e7\u00e3o<\/h3>\n<p>Primeiro, uma breve explica\u00e7\u00e3o sobre a configura\u00e7\u00e3o. A \u00fanica coisa configur\u00e1vel at\u00e9 agora \u00e9 o caminho do armazenamento bin\u00e1rio. Como estou usando o Spring Boot, s\u00f3 preciso do seguinte c\u00f3digo:<\/p>\n<pre><code class=\"language-java\">package org.couchbase.devex;\r\n\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.context.annotation.Configuration;\r\n\r\n@Configuration\r\npublic class BinaryStoreConfiguration {\r\n\r\n    @Value(\"${binaryStore.root:upload-dir}\")\r\n    private String binaryStoreRoot;\r\n\r\n    public String getBinaryStoreRoot() {\r\n        return binaryStoreRoot;\r\n    }\r\n\r\n}\r\n<\/code><\/pre>\n<p>Com isso, posso simplesmente adicionar <code>binaryStore.root=\/Usu\u00e1rios\/ldoguin\/binaryStore<\/code> para minha <code>application.properties<\/code> arquivo. Tamb\u00e9m quero permitir o upload de um arquivo de 512 MB, no m\u00e1ximo. Al\u00e9m disso, para aproveitar a autoconfigura\u00e7\u00e3o do Spring Boot Couchbase, preciso adicionar o endere\u00e7o do meu servidor Couchbase. No final, meu <code>application.properties<\/code> se parece com isso:<\/p>\n<pre><code>binaryStore.root=\/Users\/ldoguin\/binaryStore\r\nmultipart.maxFileSize: 512MB\r\nmultipart.maxRequestSize: 512MB\r\nspring.couchbase.bootstrap-hosts=localhost\r\n<\/code><\/pre>\n<p>Para usar o autoconfig do Spring Boot, basta ter o spring-boot-starter-parent como pai e o Couchbase no classpath. Portanto, \u00e9 apenas uma quest\u00e3o de adicionar uma depend\u00eancia java-client do Couchbase. Estou especificando a vers\u00e3o 2.2.4 aqui porque o padr\u00e3o \u00e9 2.2.3 e o FTS est\u00e1 apenas na vers\u00e3o 2.2.4. Voc\u00ea pode dar uma olhada no arquivo pom completo em <a href=\"https:\/\/github.com\/ldoguin\/store-search-files\/blob\/master\/pom.xml\">Github<\/a>. Parab\u00e9ns a <a href=\"https:\/\/twitter.com\/snicoll\/\">St\u00e9phane Nicoll<\/a> da Pivotal e <a href=\"https:\/\/twitter.com\/simonbasle\">Simon Basl\u00e9<\/a> do Couchbase para essa maravilhosa integra\u00e7\u00e3o com o Spring.<\/p>\n<h3>Controlador<\/h3>\n<p>Como esse aplicativo \u00e9 muito simples, coloquei tudo no mesmo <a href=\"https:\/\/github.com\/ldoguin\/store-search-files\/blob\/master\/src\/main\/java\/org\/couchbase\/devex\/controller\/IndexController.java\">controlador<\/a>. O ponto de extremidade mais b\u00e1sico \u00e9 <code>\/arquivos<\/code>. Ele exibe a lista de arquivos j\u00e1 carregados. Basta uma chamada para o searchService, colocar o resultado no modelo da p\u00e1gina e, em seguida, renderizar a p\u00e1gina.<\/p>\n<pre><code class=\"language-java\">    @RequestMapping(method = RequestMethod.GET, value = \"\/files\")\r\n    public String provideUploadInfo(Model model) {\r\n        List&lt;Map&lt;String, Object&gt;&gt; files = searchService.getFiles();\r\n        model.addAttribute(\"files\", files);\r\n        return \"uploadForm\";\r\n    }\r\n<\/code><\/pre>\n<p>Eu uso <a href=\"https:\/\/www.thymeleaf.org\/\">Folha de tomilho<\/a> para renderiza\u00e7\u00e3o e <a href=\"https:\/\/semantic-ui.com\/\">IU sem\u00e2ntica<\/a> como estrutura CSS. Voc\u00ea pode dar uma olhada no modelo usado <a href=\"https:\/\/github.com\/ldoguin\/store-search-files\/blob\/master\/src\/main\/resources\/templates\/uploadForm.html\">aqui<\/a>. Esse \u00e9 o \u00fanico modelo usado no aplicativo.<\/p>\n<p>Quando tiver uma lista de arquivos, voc\u00ea poder\u00e1 fazer o download ou exclu\u00ed-los. Ambos os m\u00e9todos est\u00e3o chamando o m\u00e9todo de servi\u00e7o de armazenamento bin\u00e1rio, e o restante do c\u00f3digo \u00e9 o cl\u00e1ssico Spring MVC:<\/p>\n<pre><code class=\"language-java\">    @RequestMapping(method = RequestMethod.GET, value = \"\/download\/{digest}\")\r\n    public String download(@PathVariable String digest, RedirectAttributes redirectAttributes,\r\n            HttpServletResponse response) throws IOException {\r\n        StoredFile sf = binaryStoreService.findFile(digest);\r\n        if (sf == null) {\r\n            redirectAttributes.addFlashAttribute(\"message\", \"This file does not exist.\");\r\n            return \"redirect:\/files\";\r\n        }\r\n        response.setContentType(sf.getStoredFileDocument().getMimeType());\r\n        response.setHeader(\"Content-Disposition\",\r\n                String.format(\"inline; filename=\"\" + sf.getStoredFileDocument().getBinaryStoreFilename() + \"\"\"));\r\n        response.setContentLength(sf.getStoredFileDocument().getSize());\r\n\r\n        InputStream inputStream = new BufferedInputStream(new FileInputStream(sf.getFile()));\r\n        FileCopyUtils.copy(inputStream, response.getOutputStream());\r\n        return null;\r\n    }\r\n\r\n    @RequestMapping(method = RequestMethod.GET, value = \"\/delete\/{digest}\")\r\n    public String delete(@PathVariable String digest, RedirectAttributes redirectAttributes,\r\n            HttpServletResponse response) {\r\n        binaryStoreService.deleteFile(digest);\r\n        redirectAttributes.addFlashAttribute(\"message\", \"File deleted successfuly.\");\r\n        return \"redirect:\/files\";\r\n    }\r\n<\/code><\/pre>\n<p>Obviamente, voc\u00ea tamb\u00e9m desejar\u00e1 fazer upload de alguns arquivos. \u00c9 um simples POST multiparte. O servi\u00e7o de armazenamento bin\u00e1rio \u00e9 chamado, persiste o arquivo e extrai os dados apropriados e, em seguida, redireciona para o <code>\/arquivos<\/code> ponto final.<\/p>\n<pre><code class=\"language-java\">    @RequestMapping(method = RequestMethod.POST, value = \"\/upload\")\r\n    public String handleFileUpload(@RequestParam(\"name\") String name, @RequestParam(\"file\") MultipartFile file,\r\n            RedirectAttributes redirectAttributes) {\r\n        if (name.isEmpty()) {\r\n            redirectAttributes.addFlashAttribute(\"message\", \"Name can't be empty!\");\r\n            return \"redirect:\/files\";\r\n        }\r\n        binaryStoreService.storeFile(name, file);\r\n        redirectAttributes.addFlashAttribute(\"message\", \"You successfully uploaded \" + name + \"!\");\r\n        return \"redirect:\/files\";\r\n    }\r\n<\/code><\/pre>\n<p>Os dois \u00faltimos m\u00e9todos s\u00e3o usados para a pesquisa. Eles simplesmente chamam o servi\u00e7o de pesquisa, adicionam o resultado ao modelo da p\u00e1gina e o renderizam.<\/p>\n<pre><code class=\"language-java\">    @RequestMapping(method = RequestMethod.POST, value = \"\/fulltext\")\r\n    public String fulltextQuery(@ModelAttribute(value = \"name\") String query, Model model) throws IOException {\r\n        List&lt;Map&lt;String, Object&gt;&gt; files = searchService.searchFulltextFiles(query);\r\n        model.addAttribute(\"files\", files);\r\n        return \"uploadForm\";\r\n    }\r\n\r\n    @RequestMapping(method = RequestMethod.POST, value = \"\/n1ql\")\r\n    public String n1qlQuery(@ModelAttribute(value = \"name\") String query, Model model) throws IOException {\r\n        List&lt;Map&lt;String, Object&gt;&gt; files = searchService.searchN1QLFiles(query);\r\n        model.addAttribute(\"files\", files);\r\n        return \"uploadForm\";\r\n    }\r\n<\/code><\/pre>\n<p>E isso \u00e9 praticamente tudo o que voc\u00ea precisa para armazenar, indexar e pesquisar arquivos com o Couchbase e o Spring Boot. \u00c9 um aplicativo simples e h\u00e1 muitas, muitas outras coisas que voc\u00ea poderia fazer para melhor\u00e1-lo, come\u00e7ando por um formul\u00e1rio de pesquisa adequado que exponha os campos extra\u00eddos do ExifTool. V\u00e1rios uploads de arquivos e arrastar e soltar seriam uma boa vantagem. O que mais voc\u00ea gostaria de ver? Deixe-nos saber nos coment\u00e1rios abaixo!<\/p>","protected":false},"excerpt":{"rendered":"<p>I have been wanting to showcase most of the new Couchbase&#8217;s search features available in 4.5 in one simple project. And there have been some interest recently about storing files or binaries in Couchbase. From a general, generic perspective, databases [&hellip;]<\/p>","protected":false},"author":49,"featured_media":13873,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1815,2165,1818,1812],"tags":[1424],"ppma_author":[9023],"class_list":["post-2211","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-best-practices-and-tutorials","category-full-text-search","category-java","category-n1ql-query","tag-spring"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.7.1 (Yoast SEO v25.7) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Storing and Searching files with Couchbase and Spring Boot<\/title>\n<meta name=\"description\" content=\"Learn the Spring Boot app that let the user upload files, store them in a binary store, let you search files based on the metadata and text.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/pt\/storing-indexing-searching-files-couchbase-spring-boot\/\" \/>\n<meta property=\"og:locale\" content=\"pt_BR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Storing, Indexing and Searching files with Couchbase and Spring Boot\" \/>\n<meta property=\"og:description\" content=\"Learn the Spring Boot app that let the user upload files, store them in a binary store, let you search files based on the metadata and text.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/pt\/storing-indexing-searching-files-couchbase-spring-boot\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2016-03-30T14:53:17+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T03:28:54+00:00\" \/>\n<meta name=\"author\" content=\"Laurent Doguin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ldoguin\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"unstructured.io\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/\"},\"author\":{\"name\":\"Laurent Doguin\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c0aa9b8f1ed51b7a9e2f7cb755994a5e\"},\"headline\":\"Storing, Indexing and Searching files with Couchbase and Spring Boot\",\"datePublished\":\"2016-03-30T14:53:17+00:00\",\"dateModified\":\"2025-06-14T03:28:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/\"},\"wordCount\":1365,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"keywords\":[\"spring\"],\"articleSection\":[\"Best Practices and Tutorials\",\"Full-Text Search\",\"Java\",\"SQL++ \/ N1QL Query\"],\"inLanguage\":\"pt-BR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/\",\"name\":\"Storing and Searching files with Couchbase and Spring Boot\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"datePublished\":\"2016-03-30T14:53:17+00:00\",\"dateModified\":\"2025-06-14T03:28:54+00:00\",\"description\":\"Learn the Spring Boot app that let the user upload files, store them in a binary store, let you search files based on the metadata and text.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#breadcrumb\"},\"inLanguage\":\"pt-BR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"width\":1800,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Storing, Indexing and Searching files with Couchbase and Spring Boot\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"pt-BR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c0aa9b8f1ed51b7a9e2f7cb755994a5e\",\"name\":\"Laurent Doguin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/12929ce99397769f362b7a90d6b85071\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g\",\"caption\":\"Laurent Doguin\"},\"description\":\"Laurent is a nerdy metal head who lives in Paris. He mostly writes code in Java and structured text in AsciiDoc, and often talks about data, reactive programming and other buzzwordy stuff. He is also a former Developer Advocate for Clever Cloud and Nuxeo where he devoted his time and expertise to helping those communities grow bigger and stronger. He now runs Developer Relations at Couchbase.\",\"sameAs\":[\"https:\/\/x.com\/ldoguin\"],\"honorificPrefix\":\"Mr\",\"birthDate\":\"1985-06-07\",\"gender\":\"male\",\"award\":[\"Devoxx Champion\",\"Couchbase Legend\"],\"knowsAbout\":[\"Java\"],\"knowsLanguage\":[\"English\",\"French\"],\"jobTitle\":\"Director Developer Relation & Strategy\",\"worksFor\":\"Couchbase\",\"url\":\"https:\/\/www.couchbase.com\/blog\/pt\/author\/laurent-doguin\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Storing and Searching files with Couchbase and Spring Boot","description":"Learn the Spring Boot app that let the user upload files, store them in a binary store, let you search files based on the metadata and text.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/pt\/storing-indexing-searching-files-couchbase-spring-boot\/","og_locale":"pt_BR","og_type":"article","og_title":"Storing, Indexing and Searching files with Couchbase and Spring Boot","og_description":"Learn the Spring Boot app that let the user upload files, store them in a binary store, let you search files based on the metadata and text.","og_url":"https:\/\/www.couchbase.com\/blog\/pt\/storing-indexing-searching-files-couchbase-spring-boot\/","og_site_name":"The Couchbase Blog","article_published_time":"2016-03-30T14:53:17+00:00","article_modified_time":"2025-06-14T03:28:54+00:00","author":"Laurent Doguin","twitter_card":"summary_large_image","twitter_creator":"@ldoguin","twitter_misc":{"Written by":"unstructured.io","Est. reading time":"12 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/"},"author":{"name":"Laurent Doguin","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c0aa9b8f1ed51b7a9e2f7cb755994a5e"},"headline":"Storing, Indexing and Searching files with Couchbase and Spring Boot","datePublished":"2016-03-30T14:53:17+00:00","dateModified":"2025-06-14T03:28:54+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/"},"wordCount":1365,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","keywords":["spring"],"articleSection":["Best Practices and Tutorials","Full-Text Search","Java","SQL++ \/ N1QL Query"],"inLanguage":"pt-BR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/","url":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/","name":"Storing and Searching files with Couchbase and Spring Boot","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","datePublished":"2016-03-30T14:53:17+00:00","dateModified":"2025-06-14T03:28:54+00:00","description":"Learn the Spring Boot app that let the user upload files, store them in a binary store, let you search files based on the metadata and text.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#breadcrumb"},"inLanguage":"pt-BR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/"]}]},{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","width":1800,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Storing, Indexing and Searching files with Couchbase and Spring Boot"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"Blog do Couchbase","description":"Couchbase, o banco de dados NoSQL","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"pt-BR"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"Blog do Couchbase","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c0aa9b8f1ed51b7a9e2f7cb755994a5e","name":"Laurent Doguin","image":{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/12929ce99397769f362b7a90d6b85071","url":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g","caption":"Laurent Doguin"},"description":"Laurent \u00e9 um nerd metaleiro que mora em Paris. Em sua maior parte, ele escreve c\u00f3digo em Java e texto estruturado em AsciiDoc, e frequentemente fala sobre dados, programa\u00e7\u00e3o reativa e outras coisas que est\u00e3o na moda. Ele tamb\u00e9m foi Developer Advocate do Clever Cloud e do Nuxeo, onde dedicou seu tempo e experi\u00eancia para ajudar essas comunidades a crescerem e se fortalecerem. Atualmente, ele dirige as Rela\u00e7\u00f5es com Desenvolvedores na Couchbase.","sameAs":["https:\/\/x.com\/ldoguin"],"honorificPrefix":"Mr","birthDate":"1985-06-07","gender":"male","award":["Devoxx Champion","Couchbase Legend"],"knowsAbout":["Java"],"knowsLanguage":["English","French"],"jobTitle":"Director Developer Relation & Strategy","worksFor":"Couchbase","url":"https:\/\/www.couchbase.com\/blog\/pt\/author\/laurent-doguin\/"}]}},"authors":[{"term_id":9023,"user_id":49,"is_guest":0,"slug":"laurent-doguin","display_name":"Laurent Doguin","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g","first_name":"Laurent","last_name":"Doguin","user_url":"","author_category":"","description":"Laurent \u00e9 um nerd metaleiro que mora em Paris. Em sua maior parte, ele escreve c\u00f3digo em Java e texto estruturado em AsciiDoc, e frequentemente fala sobre dados, programa\u00e7\u00e3o reativa e outras coisas que est\u00e3o na moda. Ele tamb\u00e9m foi Developer Advocate do Clever Cloud e do Nuxeo, onde dedicou seu tempo e experi\u00eancia para ajudar essas comunidades a crescerem e se fortalecerem. Atualmente, ele dirige as Rela\u00e7\u00f5es com Desenvolvedores na Couchbase."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2211","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/users\/49"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/comments?post=2211"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2211\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/media\/13873"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/media?parent=2211"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/categories?post=2211"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/tags?post=2211"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/ppma_author?post=2211"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}