【问题标题】:Mapping HAL URI in @RequestBody to a Spring Data Rest managed entity将 @RequestBody 中的 HAL URI 映射到 Spring Data Rest 托管实体
【发布时间】:2018-09-02 15:45:00
【问题描述】:

我正在使用 Spring Data JPA 来管理 JPA 实体,并且 Spring Data Rest 将存储库导出到 REST API。

我还在构建一个自定义控制器,我想在其中获取实体的 URI(HAL 链接)并通过 CrudRepository 自动将其映射到实体对象。

自定义控制器

@RequestMapping(path = MeLinks.ELEVATE, method = RequestMethod.PUT, consumes = RestMediaTypes.TEXT_URI_LIST_VALUE)
HttpEntity<?> elevate(@RequestBody @Valid CollectionModel<Contact> contact) {
    ...
}

当尝试使用 Content-Type text/uri-list PUThttp://localhost:8080/contacts/1 这样的联系链接时,我可以使用 contact.getLinks() 访问该链接,但不能使用 Contact 对象本身。

Spring Data Rest 是否可以从 URI 中自动推断实体?我知道一个内置的UriToEntityConverter,但我该如何使用它?

编辑

这是一个可行的尝试,但并不能真正优雅地解决问题。

下面的代码初始化一个UriToEntityConverter并将传入的URI转换为域对象。

@Autowired
private ApplicationContext applicationContext;

@Autowired
private MappingContext<?, ?> mappingContext;

@RequestMapping(path = MeLinks.ELEVATE, method = RequestMethod.PUT, consumes = RestMediaTypes.TEXT_URI_LIST_VALUE)
HttpEntity<?> elevate(@RequestBody @Valid CollectionModel<Contact> uris) {

    URI uri = URI.create(uris.getLinks().get(0).getHref());

    Repositories repositories = new Repositories(applicationContext);

    PersistentEntity<?,?> contactPersistentEntity = repositories.getPersistentEntity(Contact.class);

    UriToEntityConverter uriToEntityConverter = new UriToEntityConverter(
        new PersistentEntities(Collections.singleton(mappingContext)), 
        new DefaultRepositoryInvokerFactory(repositories), 
        repositories);

    Contact t = (Contact) uriToEntityConverter.convert(uri, TypeDescriptor.valueOf(URI.class), TypeDescriptor.valueOf(Contact.class));
}

您可以想象,从Repository 获取域对象会比上述操作容易得多。假设 URI 使用 ID 作为链接的唯一部分,这也有效。就我而言,我已将其自定义为使用 UUID。所以UriToEntityConverter 的默认行为不起作用。

注意:Resourceshas been renamedCollectionModel 与 HATEOAS 首次发布。

【问题讨论】:

  • 对我有用,但太糟糕了,它不适用于子实体(例如http://localhost:8080/contacts/1/childEntity)我四处搜索,到目前为止一无所获。
  • @GuiRitter 究竟什么对子实体不起作用?子实体也有存储库还是自定义链接?如果是自定义链接,您是否使用RepositoryRestController
  • 在上面的示例链接中,它尝试将childEntity 解析为Long,就好像我想它是contact id。 childEntity 确实有一个存储库,但没有自定义链接。
  • 啊,我明白了。这可能是因为UriToEntityConverter 仅用于解析根 URI。因此,在您的情况下,要解析的 URI 应该是 http://localhost:8080/child_entities/1 或类似的。
  • 我正在尝试使用您的代码,但是您如何获得 @Autowired private MappingContext, ?> mappingContext;?

标签: spring-mvc spring-data spring-data-rest spring-hateoas


【解决方案1】:

如果您想继续使用UriToEntityConverter,我建议您查看this part of the Spring Data REST documentation,以使用EntityLookupSupport&lt;T&gt; 类将资源URI 自定义为不同的属性。然后UriToEntityConverter 应该正确返回实体。

如果您希望按照您的说明直接使用存储库,请考虑以下事项:

查看UriToEntityConverter的源码,它使用URI的最后一部分作为对象的标识符。你也可以这样做。 (在这段代码 sn-p 中,我刚刚从上面修改了您的代码,假设您正确检索了URI 对象)

private ContactRepository contactRepository;

// You should use constructor injection, as it is preferred over field injection.
@Autowired
public CustomController(ContactRepository contactRepository){
    this.contactRepository = contactRepository;
}


@RequestMapping(path = MeLinks.ELEVATE, method = RequestMethod.PUT, consumes = RestMediaTypes.TEXT_URI_LIST_VALUE)
HttpEntity<?> elevate(@RequestBody @Valid CollectionModel<Contact> uris) {
    URI uri = URI.create(uris.getLinks().get(0).getHref());

    Optional<Contact> optionalContact = contactRepository.findByName(getLastUriPart(uri));

    if (optionalContact.isPresent()){
        return optionalContact.get();
    } else {
        // Error handling
    }
}

private String getLastUriPart(URI uri) {
    String[] uriParts = uri.getPath().split("/");

    if (uriParts.length < 2) {
        // Error handling
    }

    return uriParts[uriParts.length - 1];
}

【讨论】:

    【解决方案2】:

    我已将 UriToEntityConverter 的实例化简化为具有自动关联依赖项的服务的一部分。

    这对我有用:

    @Service
    public class UriToEntityConversionService {
    
        private UriToEntityConverter converter;
    
        @Autowired
        public UriToEntityConversionService(PersistentEntities entities, RepositoryInvokerFactory invokerFactory,
                                            Repositories repositories) {
            converter = new UriToEntityConverter(entities, invokerFactory, repositories);
        }
    
        public <T> T convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType){
            return (T) converter.convert(source, sourceType, targetType);
        }
    
    }
    

    在我的自定义控制器中调用它:

    @RequestMapping(path = "/processes/{id}/protocols", method = { PUT, POST }, consumes = {TEXT_URI_LIST_VALUE})
        HttpEntity<?> overrideLinkProtocolsDefaultEndpoint(@PathVariable("id") Process process,
                                                 @RequestBody Resources<Object> incoming,
                                                 PersistentEntityResourceAssembler assembler) throws URISyntaxException {
        // TODO handle both PUT and POST and all links
        URI uri = new URI(incoming.getLinks().get(0).getHref());
        Protocol protocol = uriToEntityConversionService.convert(uri, TypeDescriptor.valueOf(URI.class), TypeDescriptor.valueOf(Protocol.class));
    
    

    【讨论】:

      猜你喜欢
      • 2014-05-05
      • 2012-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 2021-12-01
      • 2017-05-05
      • 1970-01-01
      相关资源
      最近更新 更多