{"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\/ko\/storing-indexing-searching-files-couchbase-spring-boot\/","title":{"rendered":"Couchbase \ubc0f Spring Boot\ub85c \ud30c\uc77c \uc800\uc7a5, \uc0c9\uc778 \ubc0f \uac80\uc0c9\ud558\uae30"},"content":{"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 <a href=\"storing-base64-files-directly-in-couchbase-via-nodejs\">storing files<\/a> or <a href=\"storing-blobs-in-couchbase-for-content-management\">binaries<\/a> in Couchbase. From a general, generic perspective, databases are not made to store files or binaries. Usually what you would do is store files in a binary store and their associated metadata in the DB. The associated metadata will be the location of the file in the binary store and as much informations as possible extracted from the file.<\/p>\n<p>So this is the project I will show you today. It&#8217;s a very simple Spring Boot app that let the user upload files, store them in a binary store, where associated text and metadata will be extracted from the file, and let you search files based on those metadata and text. At the end you&#8217;ll be able to search files by mimetype, image size, text content, basically any metadata you can extract from the file.<\/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>The Binary Store<\/h2>\n<p>This is a question we get often. You can certainly store binary data in a DB but files should be in an appropriate binary store. I decided to create a very simple implementation for this example. There is basically a folder on filesystem declared at runtime that will contained all the uploaded files. A SHA1 digest will be computed from the file&#8217;s content and used as filename in that folder. You could obviously use other, more advanced binary stores like Joyent&#8217;s Manta or Amazon S3 for instance. But let&#8217;s keep things simple for this post :) Here&#8217;s a description of the services used.<\/p>\n<h3>SHA1Service<\/h3>\n<p>This is the simplest one, with one method that bascially sends back a SHA-1 digest based on the content of the file. To simplify the code even more I am using 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>This service is here to to extract metadata and text from the uploaded files. There are a lot of different ways to do so. I have choosen to rely on <a href=\"https:\/\/www.sno.phy.queensu.ca\/%7Ephil\/exiftool\/\">ExifTool<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/Poppler_%28software%29\">Poppler<\/a>.<\/p>\n<p>ExifTool is a great command line tool that to read, write and edit file metadata. It can also output metadata directly in JSON. And it&#8217;s of course not limited to the Exif standard. It supports a wide variety of formats. Poppler is a PDF utility library that will allow me to extract the text content of a PDF. As these are command line tools, I will use <a href=\"https:\/\/codehaus-plexus.github.io\/plexus-utils\/\">plexus-utils<\/a> to ease the CLI calls.<\/p>\n<p>There are two methods. The first one is <em>extractMetadata<\/em> and is responsible for ExifTool metadata extraction. It&#8217;s the equivalent of running the following command:<\/p>\n<pre><code> exiftool -n -json somePDFFile\r\n<\/code><\/pre>\n<p>The <code>-n<\/code> option is here to make sure all numerics value will be given as numbers and not Strings and <code>-json<\/code> to make sure the output is in JSON format. This can give you an output like this:<\/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>There are some interesting informations like the mime-type, the size, the creation date and more. If the mime-type of the file is <code>application\/pdf<\/code> then we can try to extract text from it with poppler, which is what the second method of the service is doing. It&#8217;s equivalent to the following CLI call:<\/p>\n<pre><code>pdftotext -raw somePDFFile -\r\n<\/code><\/pre>\n<p>This command sends the extracted text to the standard output. Which we can retrieve and put in a <code>fulltext<\/code> field in a JSON object. Full code of the service below:<\/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>Fairly Simple stuff as you can see once you use plexus-utils.<\/p>\n<h3>BinaryStoreService<\/h3>\n<p>This service is reponsible for running the data extraction and storing files, deleting files or retrieving files. Let&#8217;s start with the storing part. Everything happens in the <code>storeFile<\/code> method. First thing to do is retrieve the digest of the file, than write it in the binary store folder declared in the configuration. Once the file is written the data extraction service is called to retrieve Metadata as a JsonObject. Then binary store location, document type, digest and filename are added to that JSON object. If the uploaded file is a PDF, the data extraction service is called again to retrieve the text content and store it in a <code>fulltext<\/code> field. Then a JsonDocument is created with the digest as key and the JsonObject as content.<\/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>Reading or deleting should be pretty straight forward to understand now:<\/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>Please keep in mind that this is a very na\u00efve inplementation!<\/p>\n<h2>Indexing and Searching Files<\/h2>\n<p>Once you have uploaded files, you want to retrieve them. The first very basic way of doing so would be to display the full list of files. Then you could use N1QL to search them based on their metadatas or FTS to search them based on their content.<\/p>\n<h3>The Search Service<\/h3>\n<p><code>getFiles<\/code> method simply runs the following query: <code>SELECT binaryStoreLocation, binaryStoreDigest FROM<\/code>default<code>WHERE type= 'file'<\/code>. This sends the full list of uploaded files with their digest and binary store location. Notice the consistency option set to <em>statement_plus<\/em>. It&#8217;s a document application so I prefer strong consistency.<\/p>\n<p>Next you have <code>searchN1QLFiles<\/code> that runs a basic N1QL query with an additional WHERE clause. So the default is the same query as above with an additional WHERE part. There is no tighter integration so far. We could have a fancy search form allowing the user to search for files based on their mime-types, size or any other fields given by ExifTool.<\/p>\n<p>And finally you have <code>searchFulltextFiles<\/code> that takes a String as input and use it in a <a href=\"https:\/\/www.blevesearch.com\/docs\/Query\/\">Match<\/a> query. Then the result is sent back with fragments of text where the term was found. This fragment allow highlighting of the term in context. I also ask for the <em>binaryStoreDigest<\/em> and <em>binaryStoreLocation<\/em> fields. They are the one used to display the results to the user.<\/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>The <code>TermQuery.on<\/code> method defines which index I am querying. Here it&#8217;s set to &#8216;file_fulltext&#8217;. It means I have created a full text index with that name:<\/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>Putting Everything Together<\/h2>\n<h3>Configuration<\/h3>\n<p>A quick word about configuration first. The only thing configurable so far is the binary store path. Since I am using Spring Boot, I just need the following code:<\/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>With that I can simply add <code>binaryStore.root=\/Users\/ldoguin\/binaryStore<\/code> to my <code>application.properties<\/code> file. I also want to allow upload of 512MB file max. Also, to leverage Spring Boot Couchbase autoconfig, I need to add the address of my Couchbase Server. In the end my <code>application.properties<\/code> looks like this:<\/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>Using Spring Boot autoconfig simply requires to have spring-boot-starter-parent as parent and Couchbase in the classpath. So it&#8217;s just a matter of adding a Couchbase java-client dependency. I am specifying the 2.2.4 version here because it defaults to 2.2.3 and FTS is only in 2.2.4. You can take a look at the full pom file on <a href=\"https:\/\/github.com\/ldoguin\/store-search-files\/blob\/master\/pom.xml\">Github<\/a>. Kudos to <a href=\"https:\/\/twitter.com\/snicoll\/\">St\u00e9phane Nicoll<\/a> from Pivotal and <a href=\"https:\/\/twitter.com\/simonbasle\">Simon Basl\u00e9<\/a> from Couchbase for this wonderful Spring integration.<\/p>\n<h3>Controller<\/h3>\n<p>Since this application is very simple, I have put everything under the same <a href=\"https:\/\/github.com\/ldoguin\/store-search-files\/blob\/master\/src\/main\/java\/org\/couchbase\/devex\/controller\/IndexController.java\">controller<\/a>. The most basic endpoint is <code>\/files<\/code>. It display the list of files already uploaded. Just one call to the searchService, put the result in the page Model and then render the page.<\/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>I use <a href=\"https:\/\/www.thymeleaf.org\/\">Thymeleaf<\/a> for rendering and <a href=\"https:\/\/semantic-ui.com\/\">Semantic UI<\/a> as CSS framework. You can take a look at the template used <a href=\"https:\/\/github.com\/ldoguin\/store-search-files\/blob\/master\/src\/main\/resources\/templates\/uploadForm.html\">here<\/a>. It&#8217;s the only template used in the application.<\/p>\n<p>Once you have a list of files, you can download or delete them. Both method are calling the binary store service method, the rest of the code is classic 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>Obviously you&#8217;ll want to upload some files too. It&#8217;s a simple Multipart POST. The binary store service is called, persist the file and extract the appropriate data, then redirect to the <code>\/files<\/code> endpoint.<\/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>The last two methods are used for the search. They simply call the search service and add the result to the page Model and render it.<\/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>And this is roughly all you need to store, index and search files with Couchbase and Spring Boot. It is a simple app and there are many, many other things you could do to improve it, starting by a proper search form exposing ExifTool extracted fields. Multiple file uploads and drag and drop would be a nice plus. What else would you like to see? Let us know in the comments bellow!<\/p>\n","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>\n","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"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\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\/ko\/storing-indexing-searching-files-couchbase-spring-boot\/\" \/>\n<meta property=\"og:locale\" content=\"ko_KR\" \/>\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\/ko\/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\ubd84\" \/>\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\":\"ko-KR\",\"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\":\"ko-KR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/storing-indexing-searching-files-couchbase-spring-boot\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@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\":\"ko-KR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ko-KR\",\"@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\":\"ko-KR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g12929ce99397769f362b7a90d6b85071\",\"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\\\/ko\\\/author\\\/laurent-doguin\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Storing and Searching files with Couchbase and Spring Boot","description":"\uc0ac\uc6a9\uc790\uac00 \ud30c\uc77c\uc744 \uc5c5\ub85c\ub4dc\ud558\uace0, \ubc14\uc774\ub108\ub9ac \uc2a4\ud1a0\uc5b4\uc5d0 \uc800\uc7a5\ud558\uace0, \uba54\ud0c0\ub370\uc774\ud130\uc640 \ud14d\uc2a4\ud2b8\ub97c \uae30\ubc18\uc73c\ub85c \ud30c\uc77c\uc744 \uac80\uc0c9\ud560 \uc218 \uc788\ub294 Spring Boot \uc571\uc5d0 \ub300\ud574 \uc54c\uc544\ubcf4\uc138\uc694.","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\/ko\/storing-indexing-searching-files-couchbase-spring-boot\/","og_locale":"ko_KR","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\/ko\/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\ubd84"},"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":"ko-KR","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":"\uc0ac\uc6a9\uc790\uac00 \ud30c\uc77c\uc744 \uc5c5\ub85c\ub4dc\ud558\uace0, \ubc14\uc774\ub108\ub9ac \uc2a4\ud1a0\uc5b4\uc5d0 \uc800\uc7a5\ud558\uace0, \uba54\ud0c0\ub370\uc774\ud130\uc640 \ud14d\uc2a4\ud2b8\ub97c \uae30\ubc18\uc73c\ub85c \ud30c\uc77c\uc744 \uac80\uc0c9\ud560 \uc218 \uc788\ub294 Spring Boot \uc571\uc5d0 \ub300\ud574 \uc54c\uc544\ubcf4\uc138\uc694.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/#breadcrumb"},"inLanguage":"ko-KR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/storing-indexing-searching-files-couchbase-spring-boot\/"]}]},{"@type":"ImageObject","inLanguage":"ko-KR","@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":"\uce74\uc6b0\uce58\ubca0\uc774\uc2a4 \ube14\ub85c\uadf8","description":"NoSQL \ub370\uc774\ud130\ubca0\uc774\uc2a4, Couchbase","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":"ko-KR"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"\uce74\uc6b0\uce58\ubca0\uc774\uc2a4 \ube14\ub85c\uadf8","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"ko-KR","@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":"\ub85c\ub791 \ub3c4\uadc4","image":{"@type":"ImageObject","inLanguage":"ko-KR","@id":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g12929ce99397769f362b7a90d6b85071","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\ub294 \ud30c\ub9ac\uc5d0 \uc0ac\ub294 \uad34\uc9dc \uae08\uc18d\uacf5\ud559\ub3c4\uc785\ub2c8\ub2e4. \uc8fc\ub85c Java\ub85c \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0 AsciiDoc\uc73c\ub85c \uad6c\uc870\ud654\ub41c \ud14d\uc2a4\ud2b8\ub97c \uc791\uc131\ud558\uba70 \ub370\uc774\ud130, \ub9ac\uc561\ud2f0\ube0c \ud504\ub85c\uadf8\ub798\ubc0d \ubc0f \uae30\ud0c0 \uc720\ud589\uc5b4\uc5d0 \ub300\ud574 \uc790\uc8fc \uc774\uc57c\uae30\ud569\ub2c8\ub2e4. \ub610\ud55c Clever Cloud\uc640 Nuxeo\uc758 \uac1c\ubc1c\uc790 \uc639\ud638\uc790\ub85c \ud65c\ub3d9\ud558\uba70 \ud574\ub2f9 \ucee4\ubba4\ub2c8\ud2f0\uac00 \ub354 \ud06c\uace0 \uac15\ub825\ud558\uac8c \uc131\uc7a5\ud560 \uc218 \uc788\ub3c4\ub85d \uc790\uc2e0\uc758 \uc2dc\uac04\uacfc \uc804\ubb38\uc131\uc744 \ubc14\ucce4\uc2b5\ub2c8\ub2e4. \ud604\uc7ac Couchbase\uc5d0\uc11c \uac1c\ubc1c\uc790 \uad00\uacc4\ub97c \uc6b4\uc601\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4.","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\/ko\/author\/laurent-doguin\/"}]}},"acf":[],"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","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/posts\/2211","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/users\/49"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/comments?post=2211"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/posts\/2211\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/media\/13873"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/media?parent=2211"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/categories?post=2211"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/tags?post=2211"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/ko\/wp-json\/wp\/v2\/ppma_author?post=2211"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}