我有一个名为person的表的数据库:
id | first_name | last_name | date_of_birth
----|------------|-----------|---------------
1 | Tin | Tin | 2000-10-10
有一个名为Person
的JPA实体映射到此表:
@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
}
该实体还使用JAXB注释进行注释,以允许XML有效负载 HTTP请求映射到实体的实例。
我想实现一个端点,用于检索和更新具有给定id
的实体。
根据this answer to a similar question, 我需要做的就是按如下方式实现处理程序方法:
@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);
}
}
然而,由于以下失败的测试用例可以验证,这无法正常工作:
@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"));
}
}
第一个断言失败了:
java.lang.AssertionError:
Expected: "Tin Tin"
but: was "Tin"
Expected :Tin Tin
Actual :Tin
换句话说:
200
)id=1
任何想法我在这里缺少什么?
提供的解决方案here无效。
提供了演示此问题的完整工作代码 here
id=1
Jaxb2RootElementHttpMessageConverter
或MappingJackson2XmlHttpMessageConverter
person
参数id=1
的人员实例答案 0 :(得分: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);
}
答案 1 :(得分:1)
以下是修复测试的简单案例:
@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;
}
答案 2 :(得分:0)
问题在于,当您调用personRepository.save(person)
时,您的人员实体没有主键字段(id),因此数据库最终会有两条记录,其中新记录主键由数据库生成。修复方法是为id
字段创建一个setter,并在保存之前使用它来设置实体的id:
@PutMapping(value = "/{id}")
public Person savePerson(@RequestBody Person person, @PathVariable("id") Long id) {
person.setId(id);
return personRepository.save(person);
}
另外,就像@freakman所建议的那样,您应该使用@RequestBody
来捕获原始json / xml并将其转换为域模型。此外,如果您不想为主键字段创建一个setter,另一个选项可能是支持基于任何其他唯一字段(如externalId)的更新操作,而是调用它。
答案 3 :(得分:0)
为了更新任何实体,加载和保存必须在同一个Transaction中,否则它将在save()调用中创建新的实体,或者将抛出重复的主键约束违例异常。
更新我们需要将实体,load()/ find()和save()放在同一个事务中,或者在@Repository类中编写JPQL UPDATE查询,并使用@Modifying注释该方法。
@Modifying注释不会触发额外的选择查询来加载实体对象来更新它,而是假设DB中必须有一条记录,输入pk需要更新。