如何编写一个RestController来从XML请求更新JPA实体,Spring Data JPA方式?

Lam*_*bda 5 spring spring-mvc spring-data-jpa spring-boot

我有一个名为person的数据库:

 id | first_name | last_name | date_of_birth 
----|------------|-----------|---------------
 1  | Tin        | Tin       | 2000-10-10    
Run Code Online (Sandbox Code Playgroud)

有一个名为JPA的实体Person映射到此表:

@Entity
@XmlRootElement(name = "person")
@XmlAccessorType(NONE)
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @XmlAttribute(name = "id")
    private Long externalId;

    @XmlAttribute(name = "first-name")
    private String firstName;

    @XmlAttribute(name = "last-name")
    private String lastName;

    @XmlAttribute(name = "dob")
    private String dateOfBirth;

    // setters and getters
}
Run Code Online (Sandbox Code Playgroud)

该实体还使用JAXB注释进行注释,以允许将HTTP请求中的XML有效负载映射到实体的实例.

我想实现一个端点,用于检索和更新具有给定的实体id.

根据对类似问题的回答,我需要做的就是实现如下的处理程序方法:

@RestController
@RequestMapping(
        path = "/persons",
        consumes = APPLICATION_XML_VALUE,
        produces = APPLICATION_XML_VALUE
)
public class PersonController {

    private final PersonRepository personRepository;

    @Autowired
    public PersonController(final PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @PutMapping(value = "/{person}")
    public Person savePerson(@ModelAttribute Person person) {
        return personRepository.save(person);
    }

}
Run Code Online (Sandbox Code Playgroud)

但是,这不能按预期工作,因为可以通过以下失败的测试用例进行验证:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class PersonControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    private HttpHeaders headers;

    @Before
    public void before() {
        headers = new HttpHeaders();
        headers.setContentType(APPLICATION_XML);
    }

    // Test fails
    @Test
    @DirtiesContext
    public void testSavePerson() {
        final HttpEntity<Object> request = new HttpEntity<>("<person first-name=\"Tin Tin\" last-name=\"Herge\" dob=\"1907-05-22\"></person>", headers);

        final ResponseEntity<Person> response = restTemplate.exchange("/persons/1", PUT, request, Person.class, "1");
        assertThat(response.getStatusCode(), equalTo(OK));

        final Person body = response.getBody();
        assertThat(body.getFirstName(), equalTo("Tin Tin")); // Fails
        assertThat(body.getLastName(), equalTo("Herge"));
        assertThat(body.getDateOfBirth(), equalTo("1907-05-22"));
    }

}
Run Code Online (Sandbox Code Playgroud)

第一个断言失败了:

java.lang.AssertionError: 
Expected: "Tin Tin"
     but: was "Tin"
Expected :Tin Tin
Actual   :Tin
Run Code Online (Sandbox Code Playgroud)

换一种说法:

  • 没有服务器端异常发生(状态代码是200)
  • Spring成功加载了Person实例 id=1
  • 但它的属性不会更新

有什么想法我在这里缺少什么?


注1

这里提供的解决方案不起作用.

笔记2

此处提供演示此问题的完整工作代码 .

更多细节

预期行为:

  1. 使用加载Person实例 id=1
  2. 使用Jaxb2RootElementHttpMessageConverter或使用XML有效内容填充加载的人员实体的属性MappingJackson2XmlHttpMessageConverter
  3. 将它作为person参数传递给控制器​​的动作处理程序

实际行为:

  1. id=1已加载Person实例
  2. 实例的属性未更新以匹配请求有效内容中的XML
  3. 传递给控制器​​的操作处理程序方法的person实例的属性不会更新

fre*_*man 3

这个 '@PutMapping(value = "/{person}")' 带来了一些魔力,因为在你的例子中 {person} 只是 '1',但它恰好从数据库加载它并放入控制器中的 ModelAttribute 。无论您在测试中进行什么更改(甚至可以为空),spring都会从数据库加载人员(有效地忽略您的输入),您可以在控制器的第一行停止调试器来验证它。

您可以这样使用它:

@PutMapping(value = "/{id}")
public Person savePerson(@RequestBody Person person, @PathVariable("id") Long id ) {
    Person found = personRepository.findOne(id);

    //merge 'found' from database with send person, or just send it with id
    //Person merged..
    return personRepository.save(merged);
   }
Run Code Online (Sandbox Code Playgroud)