Spring Data Rest - _links

4

我的下一个结论是,由我的@RepositoryRestResource CrudRepository 生成的hal+json格式不正确。

教程(http://spring.io/guides/gs/accessing-data-rest/)显示了超媒体Rest JPA实体的输出为:(请注意没有"rel"元素,"links"不是数组)

{
   "_links" : {
       "people" : {
           "href" : "http://localhost:8080/people{?page,size,sort}"
       }
   }
 }

然而,参考文档(http://docs.spring.io/spring-data/rest/docs/1.1.x/reference/html/intro-chapter.html)表明输出应为:
{
    "links" : [ {
        "rel" : "customer",
        "href" : "http://localhost:8080/customer"
      }, {
         "rel" : "profile",
         "href" : "http://localhost:8080/profile"
      }
 }

有人知道这是为什么吗?
=====================================
编辑14/08/14:我已经进一步调试了。通过提供自己实现的org.springframework.hateoas.ResourceSupport类,该类检查json中的“_links”而不是“links”,我向前迈了一步。错误是:
“无法将java.util.ArrayList实例反序列化为START_OBJECT令牌......通过引用链:com.ebs.solas.admin.test.SolicitorDTO [\“_links\”]”
这是因为org.springframework.hateoas.ResourceSupport类似乎要求链接属性是一个json数组。默认情况下,Spring Data为Rest Entity生成的json+hal输出不会生成数组(没有方括号):
"_links" : {
  "self" : {
    "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxxx"
  },
  "solicitors" : {
    "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxxx/solicitor
  }
}

希望Spring论坛的某位大神能在这里帮助我。
==============================================
请看我的Spring Data存储库代码概述:
@RepositoryRestResource
    public interface SolicitorFirmRepository extends CrudRepository<SolicitorFirm, String> {
}

@Entity
@RestResource
@Table(name="XXXX", schema = "XXX")
public class SolicitorFirm implements Serializable {
}

这将成功生成以下HATEOAS资源:
{
"firmNumber" : "FXXXX",
"solicitorType" : "r",
"companyName" : "XXXX",
"address1" : "XXXX",
"address2" : "XXX",
"address3" : "XXX",
"address4" : null,
"phoneNumber" : "XXXXX",
"faxNumber" : "XXXXX",
"county" : "OY",
"_links" : {
    "self" : {
        "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/XXXX"
    },
    "solicitors" : {
        "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/XXXX/solicitors"
    }
 }

然而,当我为客户端/控制器使用定义DTO时:

import org.springframework.hateoas.ResourceSupport;
public class SolicitorFirmDTO extends ResourceSupport {
   .....
}

并使用以下代码

RestTemplate rt = new RestTemplate();
String uri = new String("//xxxxx:9090/solas-admin-data-api/solicitors/Sxxxxx");
SolicitorFirmDTO u = rt.getForObject(uri, SolicitorFirmDTO.class, "SXXXX");

我遇到了以下错误:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "_links" (class com.ebs.solas.admin.test.SolicitorFirmDTO),这个字段没有被标记为可忽略的(已知有7个属性:xx])
由于某种原因,Spring Data Rest 生成的 json 将实体链接添加在 "_links" 下,而 HATEOAS 资源超类却期望 "links"?
是否有人可以帮助解决问题?这是一个版本问题还是我需要一些额外的配置来将 "_links" 映射到 "links" 呢?
我尝试过 MappingJackson2HttpMessageConverter 和各种媒体类型 application/json+hal,但都无法解决问题。

输出是HAL+JSON,但您的输入只期望Spring的JSON数据类型。您需要使用HAL吗?您是否在某个地方有@EnableHypermediaSupport注释? - Chris DaMour
再问一遍,你需要和 HAL 对话吗?如果需要的话,那么你的客户端解析器需要知道如何反序列化它。 - Chris DaMour
抱歉,我会更具体一些。是的,我确实需要HAL,显然客户端必须能够反序列化hal+json。如果您查看我发布的代码,那么据我所知,我已经配置/开发了我的客户端代码来实现这一点。它创建了一个Jackson2HalModule对象映射器,一个MappingJackson2HttpMessageConverter(设置了application/hal+json媒体类型),并将两者都配置在RestTemplate对象上。谢谢 - APD
3个回答

2

同时,在使用mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)的情况下,需要在每个实体上使用@JsonIgnoreProperties(ignoreUnknown = true)

@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    ...

}

1

对于Spring-boot 1.3.3版本,List的exchange()方法可用。

public void test1() {

    RestTemplate restTemplate = restTemplate();

    ParameterizedTypeReference<PagedResources<User>> responseTypeRef = new ParameterizedTypeReference<PagedResources<User>>() {
    };

    String API_URL = "http://localhost:8080/api/v1/user"
    ResponseEntity<PagedResources<User>> responseEntity = restTemplate.exchange(API_URL, HttpMethod.GET,
            (HttpEntity<User>) null, responseTypeRef);

    PagedResources<User> resources = responseEntity.getBody();
    Collection<User> users = resources.getContent();
    List<User> userList = new ArrayList<User>(users);

    System.out.println(userList);

}

private RestTemplate restTemplate() {

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.registerModule(new Jackson2HalModule());

    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
    converter.setObjectMapper(mapper);

    List<HttpMessageConverter<?>> converterList = new ArrayList<HttpMessageConverter<?>>();
    converterList.add(converter);
    RestTemplate restTemplate = new RestTemplate(converterList);

    return restTemplate;
}

谢谢。application/hal+json的转换器似乎完美解决了问题,否则RestTemplate无法解析整个“_link”事物。 - Florian Schaetz

0

感谢您的回复。

针对您的问题,

1)我认为我的输入和输出都是HAL。从我的原始帖子中可以看出,从我的@RepositoryRestResource生成的json是HAL(请注意它包含对自身和相关实体的引用链接):

{
  "firmNumber" : "Fxx",
  "solicitorType" : "r",
  "companyName" : "xxx",
  "address1" : "xxx,",
  "address2" : "xx,",
  "address3" : "xxx,",
  "_links" : {
      "self" : {
         "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxx"
      },
      "solicitors" : {
         "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxx/solicitors
     }
  }
}

然而,参考链接位于属性名称“_links”下,但客户端的RestSupport类似乎不期望任何下划线“_”,它似乎只搜索“links”

2)是的,我已经指定了@EnableHypermediaSupport(type = HypermediaType.HAL),

请参见下面我的完整配置(JavaConfig):

@Configuration
@ComponentScan("com.ebs.solas.admin")
@EnableJpaRepositories("com.ebs.solas.admin")
@EnableTransactionManagement
@Import(RepositoryRestMvcConfiguration.class)
class ApplicationConfig {

    @Bean
    public DataSource dataSource() {

          DriverManagerDataSource dataSource = new DriverManagerDataSource();
          dataSource.setDriverClassName("com.ibm.db2.jcc.DB2Driver");
          dataSource.setUrl("jdbc:db2://xxxx:52001/xxxx");
          dataSource.setUsername( "xxx" );
          dataSource.setPassword( "xxx" );
          return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.DB2);
        vendorAdapter.setGenerateDdl(false);
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.ebs.solas.admin");
        factory.setDataSource(dataSource());
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return txManager;
    }
}


public class RestWebApplicationInitializer implements WebApplicationInitializer { 

    public void onStartup(ServletContext context) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(ApplicationConfig.class);

        context.addListener(new ContextLoaderListener(rootContext));

        RepositoryRestDispatcherServlet exporter = new RepositoryRestDispatcherServlet();
        ServletRegistration.Dynamic reg = context.addServlet("exporter", exporter);
        reg.setLoadOnStartup(1);
        reg.addMapping("/*");
    }
}


@Configuration
@ComponentScan("com.ebs.solas.admin")
@EnableWebMvc
@EnableHypermediaSupport(type = HypermediaType.HAL)
class WebMVCConfiguration extends WebMvcConfigurationSupport {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer c) {
        c.defaultContentType(MediaType.APPLICATION_JSON);
    }

    @Bean
    public MultipartResolver multipartResolver() { 
        return new StandardServletMultipartResolver();
    }
}

我的 RestController 还指定了 RestTemplate 应该使用 hal+json 消息转换格式,请参见下文。
@RestController
public class TestController {

     @RequestMapping(value="/test", method=RequestMethod.GET, produces={"application/hal+json"})
     @ResponseStatus(HttpStatus.OK)
     public SolicitorDTO doTest() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Jackson2HalModule());

        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
         converter.setSupportedMediaTypes(org.springframework.http.MediaType.parseMediaTypes("application/hal+json"));
        converter.setObjectMapper(mapper);

        RestTemplate rt = new RestTemplate();
        rt.getMessageConverters().add(converter);


        String uri = new String("http://localhost:9090/solas-admin-data-api/solicitors/{id}");
        SolicitorDTO u = rt.getForObject(uri, SolicitorDTO.class, "Sxxxxx");
        return u;
     }   
}

感谢您的帮助, appdJava


你应该编辑你的问题,而不是发布一个只是回复另一个答案/评论的答案。 - Leonard Brünings
抱歉,这是我在StackOverflow上的第一个问题。以后会注意的。 - APD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接