![](/img/trans.png)
[英]Spring boot with spring data jpa. How can i perform partial update of entity from request body?
[英]How to write a RestController to update a JPA entity from an XML request, the Spring Data JPA way?
我有一個名為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注釋進行注釋,以允許將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);
}
}
但是,這不能按預期工作,因為可以通過以下失敗的測試用例進行驗證:
@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
的Person實例 有什么想法我在這里缺少什么?
這里提供的解決方案不起作用。
id=1
的Person實例 Jaxb2RootElementHttpMessageConverter
或MappingJackson2XmlHttpMessageConverter
使用XML有效內容填充加載的人員實體的屬性 person
參數傳遞給控制器的動作處理程序 id=1
的Person實例 這個'@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);
}
以下是修復測試的簡單案例:
@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;
}
問題是,當您調用personRepository.save(person)
您的person實體沒有主鍵字段(id),因此數據庫最終會有兩條記錄,其中新記錄主鍵由db生成。 修復將是為您的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)的更新操作並調用它。
要更新任何實體,加載和保存必須在同一個Transaction中,否則它將在save()調用時創建新的實體,或者將拋出重復的主鍵約束違例異常。
要更新我們需要將實體,load()/ find()和save()放在同一個事務中,或者在@Repository類中編寫JPQL UPDATE查詢,並使用@Modifying注釋該方法。
@Modifying注釋不會觸發額外的選擇查詢來加載實體對象來更新它,而是假設DB中必須有一條帶有輸入pk的記錄,需要更新。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.