有關如何自訂 Page 物件與仿照 SpringDataRest 格式
為什麼要仿照? 主要原因
HATEOAS 的入門說明
因為 Hibernate 外鍵策略的複雜,在程式中會盡量簡化或不使用
首先說明如果我們要回傳自定義的 DTO 要怎麼做
假如我們有這個資料庫的物件
@Data @Entity @Table(name = "project") @EntityListeners(AuditingEntityListener.class) public class Project { @Id @Column(name = "projectid") private String projectid; @CreatedDate @Column(name = "createddate") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") private Date createddate; @CreatedBy @Column(name = "createdby") private String createdby; @LastModifiedDate @Column(name = "lastmodifieddate") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") private Date lastmodifieddate; @LastModifiedBy @Column(name = "lastmodifiedby") private String lastmodifiedby; }
因為現在外鍵的部分我們必須自己動手轉
@Data public class ProjectDto { private String projectid; private Date createddate; private String createdby; private Date lastmodifieddate; private String lastmodifiedby; private List<ProjectMember> projectMemberList; }
我們的 Repository
@RepositoryRestResource public interface ProjectRepository extends JpaRepository<Project, String> { Page<Project> findByProjectidIn(@Param("projectids") List<String> projectids, Pageable pageable); }
定義一個轉換器
@Component public class ProjectConverter { @Autowired private ProjectMemberRepository projectMemberRepository; public List<ProjectDto> convert(List<Project> projectList) { ModelMapper modelMapper = new ModelMapper(); List<ProjectDto> projectDtoList = new ArrayList<>(); projectList.forEach(project -> { ProjectDto projectDto = this.convert(project); projectDtoList.add(projectDto); }); return projectDtoList; } public ProjectDto convert(Project project) { ModelMapper modelMapper = new ModelMapper(); ProjectDto projectDto = modelMapper.map(project, ProjectDto.class); List<ProjectMember> projectMemberList = projectMemberRepository.findByProjectid(project.getProjectid()); projectDto.setProjectMemberList(projectMemberList); return projectDto; } }
實際上使用
@ResponseStatus(HttpStatus.OK) @GetMapping(value = "v1/my/projects", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Page<ProjectDto> myProject( @PageableDefault(value = 20, sort = {"createddate"}, direction = Sort.Direction.DESC) Pageable pageable){ Page<Project> projectDtoPage = myService.findMyProject(pageable); return new PageImpl<ProjectDto>(projectConverter.convert(projectDtoPage.getContent()), pageable, projectDtoPage.getTotalElements()); }
就可以取得這樣的回傳資料
{ "content" : [ { "projectid" : "bEDHArwqZu", "demandcode" : "sam-test", "demandname" : "sam-test", "demanddesc" : "sam-test", "presenter" : "sam-test", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-11T01:50:40Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-11T01:50:40Z", "lastmodifiedby" : "admin" }, { "projectid" : "SHO8OTDAu6", "demandcode" : "測試543", "demandname" : "測試543", "demanddesc" : "測試543", "presenter" : "測試543", "priority" : 100, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T07:59:34Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T07:59:34Z", "lastmodifiedby" : "admin" }, { "projectid" : "fPlSt22Xn1", "demandcode" : "测试001", "demandname" : "测试测试", "demanddesc" : "", "presenter" : "俊雄", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T05:02:12Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:02:12Z", "lastmodifiedby" : "admin" }, { "projectid" : "TNvJCo9T22", "demandcode" : "test1", "demandname" : "001", "demanddesc" : "", "presenter" : "俊雄", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T05:01:50Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:01:50Z", "lastmodifiedby" : "admin" }, { "projectid" : "OWWliwK4Rb", "demandcode" : "test001", "demandname" : "001", "demanddesc" : "", "presenter" : "俊雄", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T05:01:18Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:01:18Z", "lastmodifiedby" : "admin" }, { "projectid" : "dAP9Ff6F07", "demandcode" : "123", "demandname" : "123", "demanddesc" : "123", "presenter" : "123", "priority" : 50, "suspended" : false, "completed" : true, "ended" : false, "createddate" : "2017-07-03T09:47:36Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-04T09:08:08Z", "lastmodifiedby" : "admin" } ], "totalPages" : 1, "totalElements" : 6, "last" : true, "number" : 0, "size" : 10, "numberOfElements" : 6, "sort" : [ { "direction" : "DESC", "property" : "createddate", "ignoreCase" : false, "nullHandling" : "NATIVE", "ascending" : false, "descending" : true } ], "first" : true }
上面的方式是以前 Spring Data 的共用物件做出來的
但是如果你已經開始用 @RepositoryRestResource 在操作的話你會發現格式上略有差異 如下
{ "_embedded" : { "kpiTargets" : [ { "kpino" : "345rr", "kpiName" : "34r34r", "oncePoint" : 33, "maxPoint" : 33, "description" : null, "state" : 0, "actionType" : 0, "percentage" : 333.0, "teamId" : 4, "teamName" : "", "targetPoint" : "taskCodeDevelop", "createdDate" : "2017-07-06T01:59:12Z", "createdBy" : "admin", "lastModifiedDate" : "2017-07-06T01:59:12Z", "lastModifiedBy" : "admin", "_links" : { "self" : { "href" : "http://localhost:8080/kpiTargets/345rr" }, "kpiTarget" : { "href" : "http://localhost:8080/kpiTargets/345rr" } } } ] }, "_links" : { "self" : { "href" : "http://localhost:8080/kpiTargets{?page,size,sort}", "templated" : true }, "profile" : { "href" : "http://localhost:8080/profile/kpiTargets" } }, "page" : { "size" : 20, "totalElements" : 1, "totalPages" : 1, "number" : 0 } }
不過上面的格式是 Spring Data Rest 提供的格式,想必要改非常難,那我們就仿照做一樣的提供給前端吧
首先要有 ResourceSupport 的物件
@Data public class ProjectResource extends ResourceSupport { private String projectid; private Date createddate; private String createdby; private Date lastmodifieddate; private String lastmodifiedby; private List<ProjectMember> projectMemberList; }
再來配置我們的轉換工具
import com.ps.controller.resources.ProjectResource; import com.ps.model.Project; import com.ps.model.ProjectMember; import com.ps.repository.ProjectMemberRepository; import lombok.Data; import org.modelmapper.ModelMapper; import org.springframework.hateoas.mvc.ResourceAssemblerSupport; import java.util.List; /** * Created by samchu on 2017/7/11. */ @Data public class ProjectResourceAsm extends ResourceAssemblerSupport<Project, ProjectResource> { private ProjectMemberRepository projectMemberRepository; /** * Creates a new {@link ResourceAssemblerSupport} using the given controller class and resource type. * * @param controllerClass must not be {@literal null}. * @param resourceType must not be {@literal null}. */ public ProjectResourceAsm(Class<?> controllerClass, Class<ProjectResource> resourceType) { super(controllerClass, resourceType); } @Override public ProjectResource toResource(Project entity) { ModelMapper modelMapper = new ModelMapper(); ProjectResource projectResource = modelMapper.map(entity, ProjectResource.class); List<ProjectMember> projectMemberList = projectMemberRepository.findByProjectid(entity.getProjectid()); projectResource.setProjectMemberList(projectMemberList); return projectResource; } }
實際上在 Controller 使用
@Autowired private ProjectMemberRepository projectMemberRepository; @ResponseStatus(HttpStatus.OK) @GetMapping(value = "v1/my/projects", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public PagedResources<ProjectResource> myProject( @PageableDefault(value = 20, sort = {"createddate"}, direction = Sort.Direction.DESC) Pageable pageable) { Page<Project> projectDtoPage = myService.findMyProject(pageable); HateoasPageableHandlerMethodArgumentResolver resolver = new HateoasPageableHandlerMethodArgumentResolver(); PagedResourcesAssembler<Project> projectDtoPagedResourcesAssembler = new PagedResourcesAssembler<Project>(resolver, null); ProjectResourceAsm projectResourceAsm = new ProjectResourceAsm(MyRestController.class, ProjectResource.class); projectResourceAsm.setProjectMemberRepository(projectMemberRepository); return projectDtoPagedResourcesAssembler.toResource(projectDtoPage, projectResourceAsm); }
這樣做出來的元件轉換後的資料格式大致上就跟 SpringDataRest 的一樣了
{ "_embedded" : { "projectResources" : [ { "projectid" : "bEDHArwqZu", "demandcode" : "sam-test", "demandname" : "sam-test", "demanddesc" : "sam-test", "presenter" : "sam-test", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-11T01:50:40Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-11T01:50:40Z", "lastmodifiedby" : "admin", "projectMemberList" : [ { "projectid" : "bEDHArwqZu", "accountid" : "xs7WYNZDUA", "username" : "admin", "name" : "產品人員", "createddate" : "2017-07-11T01:50:41Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-11T01:50:41Z", "lastmodifiedby" : "admin" } ] }, { "projectid" : "SHO8OTDAu6", "demandcode" : "測試543", "demandname" : "測試543", "demanddesc" : "測試543", "presenter" : "測試543", "priority" : 100, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T07:59:34Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T07:59:34Z", "lastmodifiedby" : "admin", "projectMemberList" : [ { "projectid" : "SHO8OTDAu6", "accountid" : "BDyLeICzzM", "username" : "443331", "name" : "貝吉達", "createddate" : "2017-07-10T08:04:32Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T08:04:32Z", "lastmodifiedby" : "admin" }, { "projectid" : "SHO8OTDAu6", "accountid" : "xs7WYNZDUA", "username" : "admin", "name" : "產品人員", "createddate" : "2017-07-10T07:59:35Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T07:59:35Z", "lastmodifiedby" : "admin" } ] }, { "projectid" : "fPlSt22Xn1", "demandcode" : "测试001", "demandname" : "测试测试", "demanddesc" : "", "presenter" : "俊雄", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T05:02:12Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:02:12Z", "lastmodifiedby" : "admin", "projectMemberList" : [ { "projectid" : "fPlSt22Xn1", "accountid" : "xs7WYNZDUA", "username" : "admin", "name" : "產品人員", "createddate" : "2017-07-10T05:02:12Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:02:12Z", "lastmodifiedby" : "admin" } ] }, { "projectid" : "TNvJCo9T22", "demandcode" : "test1", "demandname" : "001", "demanddesc" : "", "presenter" : "俊雄", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T05:01:50Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:01:50Z", "lastmodifiedby" : "admin", "projectMemberList" : [ { "projectid" : "TNvJCo9T22", "accountid" : "xs7WYNZDUA", "username" : "admin", "name" : "產品人員", "createddate" : "2017-07-10T05:01:50Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:01:50Z", "lastmodifiedby" : "admin" } ] }, { "projectid" : "OWWliwK4Rb", "demandcode" : "test001", "demandname" : "001", "demanddesc" : "", "presenter" : "俊雄", "priority" : 50, "suspended" : false, "completed" : false, "ended" : false, "createddate" : "2017-07-10T05:01:18Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:01:18Z", "lastmodifiedby" : "admin", "projectMemberList" : [ { "projectid" : "OWWliwK4Rb", "accountid" : "xs7WYNZDUA", "username" : "admin", "name" : "產品人員", "createddate" : "2017-07-10T05:01:19Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-10T05:01:19Z", "lastmodifiedby" : "admin" } ] }, { "projectid" : "dAP9Ff6F07", "demandcode" : "123", "demandname" : "123", "demanddesc" : "123", "presenter" : "123", "priority" : 50, "suspended" : false, "completed" : true, "ended" : false, "createddate" : "2017-07-03T09:47:36Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-04T09:08:08Z", "lastmodifiedby" : "admin", "projectMemberList" : [ { "projectid" : "dAP9Ff6F07", "accountid" : "BDyLeICzzM", "username" : "443331", "name" : "貝吉達", "createddate" : "2017-07-03T09:48:44Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-03T09:48:44Z", "lastmodifiedby" : "admin" }, { "projectid" : "dAP9Ff6F07", "accountid" : "xs7WYNZDUA", "username" : "admin", "name" : "產品人員", "createddate" : "2017-07-03T09:47:37Z", "createdby" : "admin", "lastmodifieddate" : "2017-07-03T09:47:37Z", "lastmodifiedby" : "admin" } ] } ] }, "_links" : { "self" : { "href" : "http://localhost:8080/api/v1/my/projects?page=0&size=10&sort=createddate,desc" } }, "page" : { "size" : 10, "totalElements" : 6, "totalPages" : 1, "number" : 0 } }
當然還可以配置很多 link 之類的讓前端可以更簡單操作資料,不過目前這樣就解決了兩種 API 格式不同的問題了
← SpringBootAdmin add Line push message