{"id":16619914,"url":"https://github.com/cepr0/spring-data-rest-dto","last_synced_at":"2025-07-06T20:32:43.581Z","repository":{"id":130473139,"uuid":"97878839","full_name":"Cepr0/spring-data-rest-dto","owner":"Cepr0","description":"An approach of how to work with DTO in Spring Data REST projects","archived":false,"fork":false,"pushed_at":"2017-07-30T19:53:32.000Z","size":49,"stargazers_count":46,"open_issues_count":0,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-01T07:11:18.165Z","etag":null,"topics":["dto","projection","rest","spring-boot","spring-data-jpa","spring-data-rest"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cepr0.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-07-20T21:14:28.000Z","updated_at":"2025-02-25T08:29:53.000Z","dependencies_parsed_at":"2023-03-27T13:45:46.997Z","dependency_job_id":null,"html_url":"https://github.com/Cepr0/spring-data-rest-dto","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cepr0%2Fspring-data-rest-dto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cepr0%2Fspring-data-rest-dto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cepr0%2Fspring-data-rest-dto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cepr0%2Fspring-data-rest-dto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cepr0","download_url":"https://codeload.github.com/Cepr0/spring-data-rest-dto/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244141471,"owners_count":20404837,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dto","projection","rest","spring-boot","spring-data-jpa","spring-data-rest"],"created_at":"2024-10-12T02:43:04.545Z","updated_at":"2025-03-21T14:31:39.717Z","avatar_url":"https://github.com/Cepr0.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spring Data REST and DTO\n\n_An approach of how to work with [DTO][1] in [Spring Data REST][2] projects_\n\n## Entities\n\nEntities must implement the [Identifiable][3] interface. For example:\n\n```java\n@Entity\npublic class Category implements Identifiable\u003cInteger\u003e {\n    \n    @Id\n    @GeneratedValue\n    private final Integer id;\n    \n    private final String name;\n    \n    @OneToMany\n    private final Set\u003cProduct\u003e products = new HashSet\u003c\u003e();\n    \n    // skipped\n}\n\n@Entity\npublic class Product implements Identifiable\u003cInteger\u003e {\n\n    @Id\n    @GeneratedValue\n    private final Integer id;\n    \n    private final String name;\n    \n    // skipped\n}\n```\n\n## Projections\n\nMake a [projection][4] interface that repository query methods will return:\n\n```java\npublic interface CategoryProjection {\n\n    Category getCategory();\n    Long getQuantity();\n}\n```\n\nIt will be a basement for DTO. DTO will represent a `Category` and the number of `Product`s are belong to it.\n\n## Repository methods\n\nCreate methods return the projection: a single one, a list of DTO and a paged list of DTO.\n\n```java\n@RepositoryRestResource\npublic interface CategoryRepo extends JpaRepository\u003cCategory, Integer\u003e {\n    \n    @RestResource(exported = false)\n    @Query(\"select c as category, count(p) as quantity from Category c join c.products p where c.id = ?1 group by c\")\n    CategoryProjection getDto(Integer categoryId);\n    \n    @RestResource(exported = false)\n    @Query(\"select c as category, count(p) as quantity from Category c join c.products p group by c\")\n    List\u003cCategoryProjection\u003e getDtos();\n    \n    @RestResource(exported = false)\n    @Query(\"select c as category, count(p) as quantity from Category c join c.products p group by c\")\n    Page\u003cCategoryProjection\u003e getDtos(Pageable pageable);\n}\n```\n\n## DTO \n\nImplement DTO from its interface:\n\n```java\n@Relation(value = \"category\", collectionRelation = \"categories\")\npublic class CategoryDto implements CategoryProjection {\n\n    private final Category category;\n    private final Long quantity;\n    \n    // skipped\n}\n``` \n\nAnnotation `Relation` is used when Spring Data REST is rendering the object.\n\n## Controller  \n\nAdd custom methods to `RepositoryRestController` that will serve requests of DTO:\n\n```java\n@RepositoryRestController\n@RequestMapping(\"/categories\")\npublic class CategoryController {\n\n    @Autowired private CategoryRepo repo;\n    @Autowired private RepositoryEntityLinks links;\n    @Autowired private PagedResourcesAssembler\u003cCategoryProjection\u003e assembler;\n\n    /**\n    * Single DTO\n    */\n    @GetMapping(\"/{id}/dto\")\n    public ResponseEntity\u003c?\u003e getDto(@PathVariable(\"id\") Integer categoryId) {\n        CategoryProjection dto = repo.getDto(categoryId);\n        \n        return ResponseEntity.ok(toResource(dto));\n    }\n    \n    /**\n    * List of DTO\n    */\n    @GetMapping(\"/dto\")\n    public ResponseEntity\u003c?\u003e getDtos() {\n        List\u003cCategoryProjection\u003e dtos = repo.getDtos();\n    \n        Link listSelfLink = links.linkFor(Category.class).slash(\"/dto\").withSelfRel();\n        List\u003c?\u003e resources = dtos.stream().map(this::toResource).collect(toList());\n\n        return ResponseEntity.ok(new Resources\u003c\u003e(resources, listSelfLink));\n    }\n\n    /**\n    * Paged list of DTO\n    */\n    @GetMapping(\"/dtoPaged\")\n    public ResponseEntity\u003c?\u003e getDtosPaged(Pageable pageable) {\n        Page\u003cCategoryProjection\u003e dtos = repo.getDtos(pageable);\n\n        Link pageSelfLink = links.linkFor(Category.class).slash(\"/dtoPaged\").withSelfRel();\n        PagedResources\u003c?\u003e resources = assembler.toResource(dtos, this::toResource, pageSelfLink);\n\n        return ResponseEntity.ok(resources);\n    }\n\n    private ResourceSupport toResource(CategoryProjection projection) {\n        CategoryDto dto = new CategoryDto(projection.getCategory(), projection.getQuantity());\n        \n        Link categoryLink = links.linkForSingleResource(projection.getCategory()).withRel(\"category\");\n        Link selfLink = links.linkForSingleResource(projection.getCategory()).slash(\"/dto\").withSelfRel();\n        \n        return new Resource\u003c\u003e(dto, categoryLink, selfLink);\n    }\n}\n```\n\nWhen Projections are received from repository we must make the final transformation from a Projection to DTO \nand 'wrap' it to [ResourceSupport][5] object before sending to the client. \nTo do this we use helper method `toResource`: we create a new DTO, create necessary links for this object, \nand then create a new `Resource` with the object and its links.  \n\n## Result\n\n_See the API docs on the [Postman site](https://documenter.getpostman.com/view/788154/spring-data-rest-dto/6mz3FWE)_  \n\n### Singe DTO\n\n    GET http://localhost:8080/api/categories/6/dto\n    \n```json\n{\n    \"category\": {\n        \"name\": \"category1\"\n    },\n    \"quantity\": 3,\n    \"_links\": {\n        \"category\": {\n            \"href\": \"http://localhost:8080/api/categories/6\"\n        },\n        \"self\": {\n            \"href\": \"http://localhost:8080/api/categories/6/dto\"\n        }\n    }\n}\n```\n\n### List of DTO\n\n    GET http://localhost:8080/api/categories/dto\n    \n```json\n{\n    \"_embedded\": {\n        \"categories\": [\n            {\n                \"category\": {\n                    \"name\": \"category1\"\n                },\n                \"quantity\": 3,\n                \"_links\": {\n                    \"category\": {\n                        \"href\": \"http://localhost:8080/api/categories/6\"\n                    },\n                    \"self\": {\n                        \"href\": \"http://localhost:8080/api/categories/6/dto\"\n                    }\n                }\n            },\n            {\n                \"category\": {\n                    \"name\": \"category2\"\n                },\n                \"quantity\": 2,\n                \"_links\": {\n                    \"category\": {\n                        \"href\": \"http://localhost:8080/api/categories/7\"\n                    },\n                    \"self\": {\n                        \"href\": \"http://localhost:8080/api/categories/7/dto\"\n                    }\n                }\n            }\n        ]\n    },\n    \"_links\": {\n        \"self\": {\n            \"href\": \"http://localhost:8080/api/categories/dto\"\n        }\n    }\n}\n```    \n\n### Paged list of DTO\n\n    GET http://localhost:8080/api/categories/dtoPaged\n\n```json\n{\n    \"_embedded\": {\n        \"categories\": [\n            {\n                \"category\": {\n                    \"name\": \"category1\"\n                },\n                \"quantity\": 3,\n                \"_links\": {\n                    \"category\": {\n                        \"href\": \"http://localhost:8080/api/categories/6\"\n                    },\n                    \"self\": {\n                        \"href\": \"http://localhost:8080/api/categories/6/dto\"\n                    }\n                }\n            },\n            {\n                \"category\": {\n                    \"name\": \"category2\"\n                },\n                \"quantity\": 2,\n                \"_links\": {\n                    \"category\": {\n                        \"href\": \"http://localhost:8080/api/categories/7\"\n                    },\n                    \"self\": {\n                        \"href\": \"http://localhost:8080/api/categories/7/dto\"\n                    }\n                }\n            }\n        ]\n    },\n    \"_links\": {\n        \"self\": {\n            \"href\": \"http://localhost:8080/api/categories/dtoPaged\"\n        }\n    },\n    \"page\": {\n        \"size\": 20,\n        \"totalElements\": 2,\n        \"totalPages\": 1,\n        \"number\": 0\n    }\n}\n```    \n\n[1]: https://en.wikipedia.org/wiki/Data_transfer_object\n[2]: https://projects.spring.io/spring-data-rest/\n[3]: http://docs.spring.io/spring-hateoas/docs/current-SNAPSHOT/api/org/springframework/hateoas/Identifiable.html \n[4]: https://spring.io/blog/2016/05/03/what-s-new-in-spring-data-hopper#projections-on-repository-query-methods\n[5]: http://docs.spring.io/spring-hateoas/docs/current-SNAPSHOT/api/org/springframework/hateoas/ResourceSupport.html","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcepr0%2Fspring-data-rest-dto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcepr0%2Fspring-data-rest-dto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcepr0%2Fspring-data-rest-dto/lists"}