这个问题几乎说明了一切。使用JPARepository如何更新实体?
JPARepository只有 save 方法,它不会告诉我它是否实际创建或更新。例如,我将一个简单的Object插入数据库User,它有三个字段:firstname和lastname以及age:
@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;
}
}
然后我只是“保存”,此时实际上是一个插入:
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,我都可以使用Query。但是,考虑到我只想使用spring-data-jpa和JPARepository,我怎么告诉它而不是插入我想要更新?
具体来说,如何告诉spring-data-jpa具有相同用户名和名字的用户实际上是EQUAL并且应该更新实体。压倒平等不起作用。
谢谢!
答案 0 :(得分:158)
实体的身份由其主键定义。由于firstname
和lastname
不是主键的一部分,因此您无法告诉JPA将User
和firstname
s视为相同的lastname
如果他们有不同的userId
s。
因此,如果您要更新其User
和firstname
标识的lastname
,则需要通过查询找到User
,然后更改相应的字段您找到的对象这些更改将在事务结束时自动刷新到数据库,因此您无需执行任何操作即可明确保存这些更改。
修改强>
也许我应该详细说明JPA的整体语义。设计持久性API有两种主要方法:
插入/更新方法。当您需要修改数据库时,应该显式调用持久性API的方法:调用insert
来插入对象,或update
将对象的新状态保存到数据库。
工作单元方法。在这种情况下,您有一组持久性库托管的对象。您对这些对象所做的所有更改将在工作单元结束时自动刷新到数据库(即在典型情况下在当前事务结束时)。当您需要将新记录插入数据库时,您可以创建相应的对象托管。 托管对象由其主键标识,因此,如果您创建具有预定义主键托管的对象,则它将与具有相同ID的数据库记录关联,并且该对象的状态将自动传播到该记录。
JPA遵循后一种方法。 Spring Data中的save()
JPA由普通JPA中的merge()
支持,因此它会使您的实体托管,如上所述。这意味着在具有预定义id的对象上调用save()
将更新相应的数据库记录而不是插入新记录,并且还解释了为什么save()
未被调用create()
。
答案 1 :(得分:92)
由于@axtavt的回答侧重于JPA
而非spring-data-jpa
要通过查询来更新实体,那么保存效率不高,因为它需要两个查询,并且查询可能非常昂贵,因为它可能会加入其他表并加载任何具有fetchType=FetchType.EAGER
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()
。< / p>
答案 2 :(得分:9)
您可以简单地将此函数与save()JPAfunction一起使用,但作为参数发送的对象必须在数据库中包含一个现有的ID,否则它将不起作用,因为当我们发送不带ID的对象时,save()会直接添加数据库中的一行,但是如果我们发送一个具有现有ID的对象,它将更改数据库中已找到的列。
public void updateUser(Userinfos u) {
User userFromDb = userRepository.findById(u.getid());
//crush the variables of the object found
userFromDb.setFirstname("john");
userFromDb.setLastname("dew");
userFromDb.setAge(16);
userRepository.save(userFromDb);
}
答案 3 :(得分:7)
使用spring-data-jpa save(), 我和@DtechNet有同样的问题。我的意思是每个save()都在创建新对象而不是更新。为了解决这个问题,我不得不在实体和相关表格中添加“版本”。
答案 4 :(得分:7)
正如其他人已经提到的,save()
本身包含创建和更新操作。
我只想补充有关save()
方法背后的内容。
首先,让我们看一下CrudRepository<T,ID>
的扩展/实现层次结构,
好的,让我们在save()
处检查SimpleJpaRepository<T, ID>
的实现,
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
如您所见,它将首先检查ID是否存在,如果该实体已经存在,则仅通过merge(entity)
方法进行更新,否则,通过{{ 1}}方法。
答案 5 :(得分:4)
这就是我解决问题的方法:
User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
答案 6 :(得分:1)
spring data save()
方法将帮助您执行以下两项操作:添加新项目和更新现有项目。
只需致电save()
并享受生活:))
答案 7 :(得分:1)
如果主键是自动增量,则必须设置主键的值。 用于save();用作update()的方法。否则,它将在db中创建新记录。
如果您使用的是jsp表单,请使用隐藏文件设置主键。
Jsp:
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);
}
}
答案 8 :(得分:0)
public void updateLaserDataByHumanId(String replacement, String humanId) {
List<LaserData> laserDataByHumanId = laserDataRepository.findByHumanId(humanId);
laserDataByHumanId.stream()
.map(en -> en.setHumanId(replacement))
.collect(Collectors.toList())
.forEach(en -> laserDataRepository.save(en));
}
答案 9 :(得分:0)
具体来说,我该如何告诉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()应该为您解决所有问题。
答案 10 :(得分:0)
使用 @DynamicUpdate 注释。它更干净,您无需处理查询数据库以获取保存的值。