我在上配置了RepositoryRestResource,PageAndSortingRepository用于访问包含复合ID的实体:
RepositoryRestResource
PageAndSortingRepository
@Entity @IdClass(CustomerId.class) public class Customer { @Id BigInteger id; @Id int startVersion; ... } public class CustomerId { BigInteger id; int startVersion; ... } @RepositoryRestResource(collectionResourceRel = "customers", path = "customers", itemResourceRel = "customers/{id}_{startVersion}") public interface CustomerRepository extends PagingAndSortingRepository<Customer, CustomerId> {}
"http://<server>/api/customers/1_1"例如,当我访问服务器时,我以json的形式返回了正确的资源,但是_links部分中针对self的href是错误的,并且对于我查询的任何其他客户也是相同的:"http://<server>/api/customer/1"
"http://<server>/api/customers/1_1"
"http://<server>/api/customer/1"
即:
{ "id" : 1, "startVersion" : 1, ... "firstname" : "BOB", "_links" : { "self" : { "href" : "http://localhost:9081/reps/api/reps/1" <-- This should be /1_1 } } }
我想这是因为我的复合ID,但是我对如何更改此默认行为感到烦恼。
我看过ResourceSupport和ResourceProcessor类,但不确定是否需要更改多少才能解决此问题。
ResourceSupport
ResourceProcessor
不幸的是,所有Spring Data JPA / Rest最高版本为2.1.0.RELEASE都无法立即满足你的需求。该源代码隐藏在Spring Data Commons / JPA本身中。Spring Data JPA仅支持Id和EmbeddedId作为标识符。
Id
摘录JpaPersistentPropertyImpl:
JpaPersistentPropertyImpl
static { // [...] annotations = new HashSet<Class<? extends Annotation>>(); annotations.add(Id.class); annotations.add(EmbeddedId.class); ID_ANNOTATIONS = annotations; }
Spring Data Commons不支持组合属性的概念。它彼此独立地对待一个类的每个属性。
当然,你可以修改Spring Data Rest。但这很麻烦,不能从根本上解决问题,并且降低了框架的灵活性。
这是hack。这应该给你一个解决问题的思路。
在你的配置中覆盖repositoryExporterHandlerAdapter并返回CustomPersistentEntityResourceAssemblerArgumentResolver。此外,覆盖backendIdConverterRegistry并添加CustomBackendIdConverter到已知列表id converter:
repositoryExporterHandlerAdapter
CustomPersistentEntityResourceAssemblerArgumentResolver
backendIdConverterRegistry
CustomBackendIdConverter
id converter
import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.rest.core.projection.ProxyProjectionFactory; import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; import org.springframework.data.rest.webmvc.spi.BackendIdConverter; import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver; import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.hateoas.ResourceProcessor; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.plugin.core.OrderAwarePluginRegistry; import org.springframework.plugin.core.PluginRegistry; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @Configuration @Import(RepositoryRestMvcConfiguration.class) @EnableSpringDataWebSupport public class RestConfig extends RepositoryRestMvcConfiguration { @Autowired(required = false) List<ResourceProcessor<?>> resourceProcessors = Collections.emptyList(); @Autowired ListableBeanFactory beanFactory; @Override @Bean public PluginRegistry<BackendIdConverter, Class<?>> backendIdConverterRegistry() { List<BackendIdConverter> converters = new ArrayList<BackendIdConverter>(3); converters.add(new CustomBackendIdConverter()); converters.add(BackendIdConverter.DefaultIdConverter.INSTANCE); return OrderAwarePluginRegistry.create(converters); } @Bean public RequestMappingHandlerAdapter repositoryExporterHandlerAdapter() { List<HttpMessageConverter<?>> messageConverters = defaultMessageConverters(); configureHttpMessageConverters(messageConverters); RepositoryRestHandlerAdapter handlerAdapter = new RepositoryRestHandlerAdapter(defaultMethodArgumentResolvers(), resourceProcessors); handlerAdapter.setMessageConverters(messageConverters); return handlerAdapter; } private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() { CustomPersistentEntityResourceAssemblerArgumentResolver peraResolver = new CustomPersistentEntityResourceAssemblerArgumentResolver( repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(beanFactory)); return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(), repoRequestArgumentResolver(), persistentEntityArgumentResolver(), resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE, peraResolver, backendIdHandlerMethodArgumentResolver()); } }
创建CustomBackendIdConverter。此类负责呈现你的自定义实体ID:
import org.springframework.data.rest.webmvc.spi.BackendIdConverter; import java.io.Serializable; public class CustomBackendIdConverter implements BackendIdConverter { @Override public Serializable fromRequestId(String id, Class<?> entityType) { return id; } @Override public String toRequestId(Serializable id, Class<?> entityType) { if(entityType.equals(Customer.class)) { Customer c = (Customer) id; return c.getId() + "_" +c.getStartVersion(); } return id.toString(); } @Override public boolean supports(Class<?> delimiter) { return true; } }
CustomPersistentEntityResourceAssemblerArgumentResolver反过来应该返回一个CustomPersistentEntityResourceAssembler:
CustomPersistentEntityResourceAssembler
import org.springframework.core.MethodParameter; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.projection.ProjectionDefinitions; import org.springframework.data.rest.core.projection.ProjectionFactory; import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler; import org.springframework.data.rest.webmvc.config.PersistentEntityResourceAssemblerArgumentResolver; import org.springframework.data.rest.webmvc.support.PersistentEntityProjector; import org.springframework.hateoas.EntityLinks; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; public class CustomPersistentEntityResourceAssemblerArgumentResolver extends PersistentEntityResourceAssemblerArgumentResolver { private final Repositories repositories; private final EntityLinks entityLinks; private final ProjectionDefinitions projectionDefinitions; private final ProjectionFactory projectionFactory; public CustomPersistentEntityResourceAssemblerArgumentResolver(Repositories repositories, EntityLinks entityLinks, ProjectionDefinitions projectionDefinitions, ProjectionFactory projectionFactory) { super(repositories, entityLinks,projectionDefinitions,projectionFactory); this.repositories = repositories; this.entityLinks = entityLinks; this.projectionDefinitions = projectionDefinitions; this.projectionFactory = projectionFactory; } public boolean supportsParameter(MethodParameter parameter) { return PersistentEntityResourceAssembler.class.isAssignableFrom(parameter.getParameterType()); } public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String projectionParameter = webRequest.getParameter(projectionDefinitions.getParameterName()); PersistentEntityProjector projector = new PersistentEntityProjector(projectionDefinitions, projectionFactory, projectionParameter); return new CustomPersistentEntityResourceAssembler(repositories, entityLinks, projector); } }
CustomPersistentEntityResourceAssembler需要覆盖getSelfLinkFor。如你所见,entity.getIdProperty()返回Customer类的id或startVersion属性,而该属性又被用于借助来检索实际值BeanWrapper。在这里,我们使用instanceof运算符将整个框架短路。因此,你的Customer班级应实施Serializable进一步处理。
getSelfLinkFor
ntity.getIdProperty()
Customer
startVersion
BeanWrapper
instanceof
Serializable
import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler; import org.springframework.data.rest.webmvc.support.Projector; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.Link; import org.springframework.util.Assert; public class CustomPersistentEntityResourceAssembler extends PersistentEntityResourceAssembler { private final Repositories repositories; private final EntityLinks entityLinks; public CustomPersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks, Projector projector) { super(repositories, entityLinks, projector); this.repositories = repositories; this.entityLinks = entityLinks; } public Link getSelfLinkFor(Object instance) { Assert.notNull(instance, "Domain object must not be null!"); Class<? extends Object> instanceType = instance.getClass(); PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instanceType); if (entity == null) { throw new IllegalArgumentException(String.format("Cannot create self link for %s! No persistent entity found!", instanceType)); } Object id; //this is a hack for demonstration purpose. don't do this at home! if(instance instanceof Customer) { id = instance; } else { BeanWrapper<Object> wrapper = BeanWrapper.create(instance, null); id = wrapper.getProperty(entity.getIdProperty()); } Link resourceLink = entityLinks.linkToSingleResource(entity.getType(), id); return new Link(resourceLink.getHref(), Link.REL_SELF); } }
而已!你应该看到以下URI:
{ "_embedded" : { "customers" : [ { "name" : "test", "_links" : { "self" : { "href" : "http://localhost:8080/demo/customers/1_1" } } } ] } }
恕我直言,如果你正在从事绿色项目,我建议你IdClass完全放弃并使用基于Long类的技术简单ID。这已通过Spring Data Rest 2.1.0.RELEASE,Spring data JPA 1.6.0.RELEASE和Spring Framework 4.0.3RELEASE进行了测试。
IdClass