如何编写RestController来从XML请求更新JPA实体,springdatajpa方式?

如何编写RestController来从XML请求更新JPA实体,springdatajpa方式?,spring,spring-mvc,spring-boot,spring-data-jpa,Spring,Spring Mvc,Spring Boot,Spring Data Jpa,我有一个数据库,其中有一个名为person的表: id | first_name | last_name | date_of_birth ----|------------|-----------|--------------- 1 | Tin | Tin | 2000-10-10 有一个名为Person的JPA实体映射到此表: @实体 @XmlRootElement(name=“person”) @XmlAccessorType(无) 公共阶层人士{

我有一个数据库,其中有一个名为person的表:

 id | first_name | last_name | date_of_birth 
----|------------|-----------|---------------
 1  | Tin        | Tin       | 2000-10-10    
有一个名为
Person
的JPA实体映射到此表:

@实体
@XmlRootElement(name=“person”)
@XmlAccessorType(无)
公共阶层人士{
@身份证
@生成值
私人长id;
@xmltattribute(name=“id”)
私人长期外部化;
@XmlAttribute(name=“first name”)
私有字符串名;
@XmlAttribute(name=“last name”)
私有字符串lastName;
@xmltattribute(name=“dob”)
出生日期的私有字符串;
//二传手和接球手
}
实体还使用JAXB注释进行注释,以允许XML有效负载进入 要映射到实体实例的HTTP请求

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

根据,, 我需要做的就是实现如下处理程序方法:

@RestController
@请求映射(
path=“/persons”,
消费=应用程序\u XML\u值,
生成=应用程序\u XML\u值
)
公共类个人控制器{
私人最终个人储存库个人储存库;
@自动连线
公共人员控制器(最终人员存储库人员存储库){
this.personRepository=personRepository;
}
@PutMapping(value=“/{person}”)
公共人物savePerson(@modeldattribute Person){
返回personRepository.save(个人);
}
}
但是,这并没有按照预期工作,这可以通过以下失败测试用例进行验证:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=RANDOM_端口)
公共类PersonControllerTest{
@自动连线
私有TestRestTemplate;
私有HttpHeader;
@以前
在()之前公开无效{
headers=新的HttpHeaders();
headers.setContentType(应用程序XML);
}
//测试失败
@试验
@肮脏的环境
public void testSavePerson(){
最终HttpEntity请求=新HttpEntity(“,标头);
final ResponseEntity response=restemplate.exchange(“/persons/1”,PUT,request,Person.class,“1”);
断言(response.getStatusCode(),equalTo(OK));
final Person body=response.getBody();
assertThat(body.getFirstName(),equalTo(“Tin”);//失败
资产(body.getLastName(),equalTo(“Herge”);
资产(body.getDateOfBirth(),equalTo(“1907-05-22”);
}
}
第一个断言失败,原因是:

java.lang.AssertionError: 
Expected: "Tin Tin"
     but: was "Tin"
Expected :Tin Tin
Actual   :Tin
换言之:

  • 没有发生服务器端异常(状态代码为
    200
  • Spring成功加载id为1的Person实例
  • 但它的属性不会得到更新
你知道我在这里遗漏了什么吗


附注1 提供的解决方案不起作用

附注2 提供了演示问题的完整工作代码

更多细节 预期行为:
  • 加载id=1的Person实例
  • 使用
    Jaxb2RootElementHttpMessageConverter
    MappingJackson2XmlHttpMessageConverter
  • 将其作为其
    person
    参数传递给控制器的操作处理程序
  • 实际行为:
  • 将加载id为1的Person实例
  • 实例的属性不会更新以匹配请求负载中的XML
  • 传递给控制器的操作处理程序方法的person实例的属性不会更新
  • 这个“@PutMapping(value=“/{person}”)”带来了一些魔力,因为在您的例子中{person}只是“1”,但它恰好从数据库加载它并放到控制器中的ModelAttribute。无论您在测试中做了什么更改(它甚至可以是空的),spring都将从数据库加载person(实际上忽略您的输入),您可以在控制器的第一行使用调试器停止以验证它

    您可以通过以下方式使用它:

    @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);
       }
    

    问题是,当您调用
    personRepository.save(person)
    时,您的person实体没有主键字段(id),因此数据库最终有两条记录,新记录主键由db生成。修复方法是为
    id
    字段创建一个setter,并在保存之前使用它设置实体的id:

    
    @PutMapping(value=“/{id}”)
    public Person savePerson(@RequestBody Person,@PathVariable(“id”)Long id){
    person.setId(id);
    返回personRepository.save(个人);
    }

    此外,正如@freakman所建议的,您应该使用
    @RequestBody
    捕获原始json/xml并将其转换为域模型。另外,如果您不想为主键字段创建setter,另一个选项可能是支持基于任何其他唯一字段(如externalId)的更新操作,并调用它

  • 控制器中的映射错误
  • 要更新实体,首先需要使其处于持久(托管)状态,然后在其上复制所需状态
  • 考虑为业务对象引入DTO,因为稍后使用持久化状态实体进行响应可能会导致问题(例如,不需要的惰性集合获取或实体关系序列化为XML,JSON可能会由于无限方法调用而导致堆栈溢出)
  • 下面是修复测试的简单案例:

    @PutMapping(value = "/{id}")
    public Person savePerson(@PathVariable Long id, @RequestBody Person person) {
        Person persisted = personRepository.findOne(id);
        if (persisted != null) {
            persisted.setFirstName(person.getFirstName());
            persisted.setLastName(person.getLastName());
            persisted.setDateOfBirth(person.getDateOfBirth());
            return persisted;
        } else {
            return personRepository.save(person);
        }
    }
    
    更新

    @PutMapping(value = "/{person}")
    public Person savePerson(@ModelAttribute Person person, @RequestBody Person req) {
        person.setFirstName(req.getFirstName());
        person.setLastName(req.getLastName());
        person.setDateOfBirth(req.getDateOfBirth());
        return person;
    }
    

    要更新任何实体,加载和保存必须在同一事务中,否则它将在save()调用时创建一个新的实体,或者引发重复的主键约束冲突异常

    要更新任何实体,我们需要将实体、load()/find()和save()放在同一事务中,或者在@Repository类中编写JPQL更新查询,并用@modify注释该方法

    @修改注释不会触发额外的select查询来加载实体对象以更新它,而是假定