这个问题几乎说明了一切。使用JPARepository我如何更新一个实体?
JPARepository只有一个save方法,它并没有告诉我它是创建还是更新。例如,我插入一个简单的对象到数据库User,它有三个字段:姓,名和年龄:
@Entity
public class User {
private String firstname;
private String lastname;
//Setters and getters for age omitted, but they are the same as with firstname and lastname.
private int age;
@Column
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
@Column
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
private long userId;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public long getUserId(){
return this.userId;
}
public void setUserId(long userId){
this.userId = userId;
}
}
然后我简单地调用save(),这在这一点上实际上是插入到数据库:
User user1 = new User();
user1.setFirstname("john"); user1.setLastname("dew");
user1.setAge(16);
userService.saveUser(user1);// This call is actually using the JPARepository: userRepository.save(user);
到目前为止一切顺利。现在我要更新这个用户,比如改变他的年龄。为此,我可以使用QueryDSL或NamedQuery之类的查询。但是,考虑到我只想使用spring-data-jpa和JPARepository,我如何告诉它我想做更新而不是插入呢?
具体来说,我如何告诉spring-data-jpa具有相同用户名和名字的用户实际上是EQUAL,并且现有实体应该被更新?重写等号并不能解决这个问题。
正如其他人回答的那样,save()方法是双重函数。它既可以保存也可以更新,如果你提供id它就会自动更新。
对于控制器类中的update方法,我建议使用@PatchMapping。下面是示例。
#保存方法POST
{
"username": "jhon.doe",
"displayName": "Jhon",
"password": "xxxyyyzzz",
"email": "jhon.doe@mail.com"
}
@PostMapping("/user")
public void setUser(@RequestBody User user) {
userService.save(user);
}
#更新方法
{
"id": 1, // this is important. Widly important
"username": "jhon.doe",
"displayName": "Jhon",
"password": "xxxyyyzzz",
"email": "jhon.doe@mail.com"
}
@PatchMapping("/user")
public void patchUser(@RequestBody User user) {
userService.save(user);
}
也许你想知道本我是从哪里来的。它来自数据库当然,你想要更新现有的数据,对吧?
实体的标识由它们的主键定义。因为姓和名不是主键的一部分,所以您不能告诉JPA,如果用户名和姓不同,就把它们当作相同的用户来对待。
因此,如果您想要更新一个由其姓和名标识的User,您需要通过查询找到该User,然后更改所找到的对象的适当字段。这些更改将在事务结束时自动刷新到数据库,因此您不需要执行任何操作来显式保存这些更改。
编辑:
也许我应该详细说明JPA的整体语义。持久性api的设计主要有两种方法:
insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.
JPA遵循后一种方法。Spring Data JPA中的save()是由普通JPA中的merge()支持的,因此它使您的实体像上面描述的那样管理。这意味着在具有预定义id的对象上调用save()将更新相应的数据库记录,而不是插入新的记录,这也解释了为什么save()不调用create()。
具体来说,我如何告诉spring-data-jpa拥有
相同的用户名和名字实际上是相等的,这是假定的
更新实体。重写等号不起作用。
为了这个特殊的目的,我们可以像这样引入一个复合键:
CREATE TABLE IF NOT EXISTS `test`.`user` (
`username` VARCHAR(45) NOT NULL,
`firstname` VARCHAR(45) NOT NULL,
`description` VARCHAR(45) NOT NULL,
PRIMARY KEY (`username`, `firstname`))
映射:
@Embeddable
public class UserKey implements Serializable {
protected String username;
protected String firstname;
public UserKey() {}
public UserKey(String username, String firstname) {
this.username = username;
this.firstname = firstname;
}
// equals, hashCode
}
下面是如何使用它:
@Entity
public class UserEntity implements Serializable {
@EmbeddedId
private UserKey primaryKey;
private String description;
//...
}
JpaRepository看起来是这样的:
public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>
然后,您可以使用下面的习惯用法:接受带有用户信息的DTO,提取姓名和名字并创建UserKey,然后使用这个组合键创建一个UserEntity,然后调用Spring Data save(),它应该为您整理所有内容。