【问题标题】:Spring Data Rest - _linksSpring Data Rest - _links
【发布时间】:2014-10-06 16:50:46
【问题描述】:

编辑 14/08/14 13:29

我的下一个结论是我的@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 类似乎要求 links 属性是一个 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:无法识别的字段“_links”(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 注释吗?
  • 再次询问..你需要谈谈 HAL 吗?如果你这样做..那么你的客户端解析器需要知道如何反序列化它。
  • 对不起,我会更具体。是的,我确实需要 HAL,显然客户端必须能够反序列化 hal+json。如果您查看我发布的代码,那么据我所知,我已经配置/开发了我的客户端代码来做到这一点。它创建一个 Jackson2HalModule 对象映射器、一个 MappingJackson2HttpMessageConverter(带有 application/hal+json 媒体类型集)并在 RestTemplate 对象上配置两者。谢谢

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


【解决方案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”。
【解决方案2】:

还与mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 一起在每个实体上使用@JsonIgnoreProperties(ignoreUnknown = true)

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

}

【讨论】:

    【解决方案3】:

    感谢您的回复。

    回答你的问题,

    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类似乎没有期望任何_underscore,它似乎只搜索“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;
         }   
    }
    

    感谢您的帮助, 应用程序Java

    【讨论】:

    • 您应该编辑您的问题,而不是发布只是对另一个答案/评论的回复的答案。
    • 对不起,我关于stackoverflow的第一个问题。以后会做的
    猜你喜欢
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    • 2021-12-14
    • 2017-01-18
    • 2018-06-04
    • 2016-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多