这个问题几乎说明了一切。使用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);
}
也许你想知道本我是从哪里来的。它来自数据库当然,你想要更新现有的数据,对吧?
因为@axtavt的回答关注的是JPA而不是spring-data-jpa
通过查询来更新一个实体,然后保存是不高效的,因为它需要两次查询,而且可能查询会非常昂贵,因为它可能会连接其他表并加载任何fetchType= fetchType的集合。急切的
Spring-data-jpa支持更新操作。
您必须在Repository接口中定义该方法。并使用@Query和@Modifying进行注释。
@Modifying
@Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);
@Query用于定义自定义查询,@Modifying用于告诉spring-data-jpa这个查询是一个更新操作,它需要executeUpdate()而不是executeQuery()。
您可以将返回类型指定为int,其中包含正在更新的记录的数量。
注意:在事务中运行此代码。
实体的标识由它们的主键定义。因为姓和名不是主键的一部分,所以您不能告诉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()。
如果你的主键是自动递增的,那么你必须设置主键的值。
对于save();方法作为update()工作。否则它将在db中创建一个新记录。
如果使用JSP表单,则使用隐藏字段设置主键。
Jsp:
<form:input type="hidden" path="id" value="${user.id}"/>
Java:
@PostMapping("/update")
public String updateUser(@ModelAttribute User user) {
repo.save(user);
return "redirect:userlist";
}
再看看这个:
@Override
@Transactional
public Customer save(Customer customer) {
// Is new?
if (customer.getId() == null) {
em.persist(customer);
return customer;
} else {
return em.merge(customer);
}
}