Spring引导数据休息,Jackson:如何在Jackson序列化期间使用URI代替相关实体的id

时间:2017-01-13 20:39:29

标签: spring-boot jackson spring-data-jpa spring-data-rest resttemplate

我有一对多的关系黑白酒店和房间实体。数据流如下:

  1. 客户端代码创建没有任何房间的酒店对象
  2. 客户端执行HTTP POST(创建& persist酒店实体)
  3. 客户端创建Room对象,将其添加到Hotel
  4. 客户端执行HTTP POST(创建ROOM并更新 酒店对象)
  5. 我在下面的junit测试中模拟了上面的流程,并运行到下面显示的异常中。

    Method: POST
    URI: http://localhost:49515/api/hotels
    Request Body: {"id":100,"name":"hotel1"
    
    Method: POST
    URI: http://localhost:49515/api/rooms
    Request Body: {"id":200,"name":"room1","hotel":100}
    
    o.s.d.r.w.RepositoryRestExceptionHandler : Could not read document: Failed to convert from type [java.net.URI] to type [com.example.entities.Hotel] for value '100'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 100. Is it local or remote? Only local URIs are resolvable. (through reference chain: com.example.entities.Room["hotel"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
    

    问题似乎是在发布Room对象创建时,对Hotel的引用将作为纯ID字段发送(在序列化期间),并且Jackson在反序列化期间无法解析该引用。如何发送酒店的URI而不是其ID?

    {"id":200,"name":"room1", "hotel": "http://localhost:49515/api/hotels/100"}
    

    而不是

        {"id":200,"name":"room1", "hotel":100}
    

    有没有其他方法可以解决我的数据流?

    以下示例代码:

        @Entity
        @Table
        @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
        public class Hotel implements Serializable {
            private static final long serialVersionUID = 1L;
    
            @Id
            @GeneratedValue(strategy= GenerationType.AUTO)
            private Integer id;
            private String name;
    
            @OneToMany(mappedBy="hotel")
            @JsonIgnore
            private Set<Room> rooms;
    
            public Hotel() {
            }
        // --- getters and setters omitted for brevity ---
        }
    
        @Entity
        @Table
        @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
        public class Room implements Serializable {
            private static final long serialVersionUID = 1L;
    
            @Id
            @GeneratedValue(strategy= GenerationType.AUTO)
            private Integer id;
    
    
            @ManyToOne(fetch= FetchType.LAZY)
            @JsonIdentityReference(alwaysAsId=true)
            private Hotel hotel;
    
            public Room() {
            }
        // --- getters and setters omitted for brevity ---
        }
    
        public interface HotelRepo extends JpaRepository<Hotel, Integer> {
        }
        public interface RoomRepo extends JpaRepository<Room, Integer> {
        }
    
    
        @RunWith(SpringRunner.class)
        @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
        @AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
        public class JPACreationTester {
    
            static final String CREATE_HOTEL_URL = "/api/hotels";
            static final String CREATE_ROOM_URL = "/api/rooms";
    
            @Autowired
            private TestRestTemplate restTemplate;
    
            @Test
            public void testDataCreation () throws Exception {
    
                String resName1 = "hotel1";
                Hotel hotel1 = new Hotel(resName1);
                hotel1.setId(100);
    
                String roomName1 = "room1";
                Room room1 = new Room(roomName1);
                room1.setId(200);
                room1.setHotel(hotel1);
    
                Set<Room> rooms = new HashSet<>();
                rooms.add(room1);
                hotel1.setRooms(rooms);
    
                Hotel hotel1saved = this.restTemplate.postForObject(CREATE_HOTEL_URL, hotel1, Hotel.class);
                Room room1saved = this.restTemplate.postForObject(CREATE_ROOM_URL, room1, Room.class);
                assertThat(room1saved).as("Rooms are equal").isEqualToIgnoringNullFields(room1);
                assertThat(hotel1saved).as("Hotels are equal").isEqualToIgnoringNullFields(hotel1);
            }
        }
    

    下面的服务器日志

    2017-01-13 11:51:47.969 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Created POST request for "http://localhost:49515/api/hotels"
       2017-01-13 11:51:48.073 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Setting request Accept header to [application/json, application/json, application/*+json, application/*+json]
       2017-01-13 11:51:48.090 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Writing [com.example.entities.Hotel@1d9bd1d6] using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@10bdfbcc]
       Method: POST
       URI: http://localhost:49515/api/hotels
       Request Body: {"id":100,"name":"hotel1"}
       Response body: java.io.ByteArrayInputStream@23708f14
       2017-01-13 11:51:48.653 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : POST request for "http://localhost:49515/api/hotels" resulted in 201 (null)
       2017-01-13 11:51:48.655 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Reading [class com.example.entities.Hotel] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@10bdfbcc]
       2017-01-13 11:51:48.656 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Created POST request for "http://localhost:49515/api/rooms"
       2017-01-13 11:51:48.659 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Setting request Accept header to [application/json, application/json, application/*+json, application/*+json]
       2017-01-13 11:51:48.660 DEBUG 1217 --- [           main] o.s.web.client.RestTemplate              : Writing [com.example.entities.Room@151659dd] using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@10bdfbcc]
       Method: POST
       URI: http://localhost:49515/api/rooms
       Request Body: {"id":200,"hotel":100,"name":"room1"}
       2017-01-13 11:51:48.697 ERROR 1217 --- [o-auto-1-exec-2] o.s.d.r.w.RepositoryRestExceptionHandler : Could not read document: Failed to convert from type [java.net.URI] to type [com.example.entities.Hotel] for value '100'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 100. Is it local or remote? Only local URIs are resolvable. (through reference chain: com.example.entities.Room["hotel"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type [java.net.URI] to type [com.example.entities.Hotel] for value '100'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 100. Is it local or remote? Only local URIs are resolvable. (through reference chain: com.example.entities.Room["hotel"])
    
       org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Failed to convert from type [java.net.URI] to type [com.example.entities.Hotel] for value '100'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 100. Is it local or remote? Only local URIs are resolvable. (through reference chain: com.example.entities.Room["hotel"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type [java.net.URI] to type [com.example.entities.Hotel] for value '100'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 100. Is it local or remote? Only local URIs are resolvable. (through reference chain: com.example.entities.Room["hotel"])
         at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:240) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readInternal(AbstractJackson2HttpMessageConverter.java:217) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:193) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.read(PersistentEntityResourceHandlerMethodArgumentResolver.java:228) ~[spring-data-rest-webmvc-2.5.6.RELEASE.jar:na]
         at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.read(PersistentEntityResourceHandlerMethodArgumentResolver.java:185) ~[spring-data-rest-webmvc-2.5.6.RELEASE.jar:na]
         at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.resolveArgument(PersistentEntityResourceHandlerMethodArgumentResolver.java:138) ~[spring-data-rest-webmvc-2.5.6.RELEASE.jar:na]
         at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:160) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:129) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
         at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    

1 个答案:

答案 0 :(得分:0)

对初始POST的响应将包含标题“位置”,它将为您提供新创建的酒店的URI:例如

HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:49515/api/hotels/100
Content-Length: 0
Date: Wed, 26 Feb 2014 20:26:55 GMT

然后,您应该能够完全按照建议使用“位置”标题的值来发布到/ rooms端点:

{
 "id":200,
 "name":"room1", 
 "hotel": "http://localhost:49515/api/hotels/100"
}