好了, 用了潮潮的 Spring Data Rest 有 HATEOAS 這樣方便的格式
原本我們使用 Page 物件來回覆前端的 的格式就開始被前端嫌
沒 HATEOAS 好不方便啊, 有兩種不同格式 API 很麻煩耶...等
好吧....拿出後端的尊嚴....
我們來改成跟 Spring Data Rest 提供的 HATEOAS 一樣的效果吧
SpringDataRest 提供的是 HATEOAS (Hypermedia As The Engine Of Application State)
org.springframework.data.domain.Page 轉成 json 跟 SpringDataRest 不一樣
因為 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 格式不同的問題了