如何使用spring-data-jpa更新实体?

这个问题几乎说明了一切。使用JPARepository我如何更新一个实体?

JPARepository只有一个保存方法,它没有告诉我它实际上是创建还是更新。例如,我插入一个简单的对象到数据库User,它有三个字段:firstnamelastnameage:

 @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,并且现有实体应该被更新?重写等号并不能解决这个问题。

710580 次浏览

实体的标识由它们的主键定义。因为firstnamelastname不是主键的一部分,所以你不能告诉JPA将具有相同firstnameUsers和具有不同__abc5的lastname视为相等。

因此,如果你想要更新一个由firstnamelastname标识的User,你需要通过查询找到那个User,然后改变你找到的对象的适当字段。这些更改将在事务结束时自动刷新到数据库,因此您不需要执行任何操作来显式保存这些更改。

编辑:

也许我应该详细说明JPA的整体语义。持久性api的设计主要有两种方法:

  • <强> insert / update方法> < /强。当你需要修改数据库时,你应该显式地调用持久性API的方法:调用insert来插入一个对象,或者调用update来将对象的新状态保存到数据库中。

  • 工作单元方法. .在这种情况下,您有一组对象managed by持久化库。您对这些对象所做的所有更改将在工作单元结束时自动刷新到数据库中(典型情况下,在当前事务结束时)。当您需要向数据库插入新记录时,您使对应的对象managedManaged对象是由它们的主键来标识的,因此,如果您创建一个具有预定义主键Managed的对象,它将与相同id的数据库记录相关联,并且该对象的状态将自动传播到该记录。

JPA遵循后一种方法。Spring Data JPA中的save()是由普通JPA中的merge()支持的,因此它使您的实体如上所述为管理。这意味着在具有预定义id的对象上调用save()将更新相应的数据库记录,而不是插入新的记录,这也解释了为什么save()不被称为create()

由于@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()

可以将返回类型指定为int,其中包含要更新的记录数量。


请注意:在事务中运行此代码。

使用spring-data-jpa save(),我遇到了与@DtechNet相同的问题。我的意思是每个save()都在创建新对象而不是更新。为了解决这个问题,我必须将version字段添加到实体和相关表中。

我是这样解决这个问题的:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);

你可以简单地将这个函数与save() jpfunction一起使用,但是作为参数发送的对象必须包含数据库中已有的id,否则它将无法工作,因为save()当我们发送一个没有id的对象时,它直接在数据库中添加一行,但如果我们发送一个具有现有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);
}

正如其他人已经提到的,save()本身包含创建和更新操作。

我只是想补充一下save()方法后面的内容。

首先,让我们看看CrudRepository<T,ID>的扩展/实现层次结构, enter image description here < / p >

好的,让我们检查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)方法进行更新,否则,通过persist(entity)方法插入一条新记录。

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));
}

spring data save()方法将帮助你执行这两项:添加新项和更新现有项。

只要调用save()就可以享受生活:))

具体地说,我如何告诉spring-data-jpa用户拥有 相同的用户名和名字实际上是相等的,这是假定的 更新实体。

.重写equals无效

为了这个特殊的目的,我们可以像这样引入一个复合键:

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(),它应该会为你整理所有内容。

如果你的主键是自动递增的,那么你必须设置主键的值。 对于save();方法作为update()工作。否则它将在db中创建一个新记录

如果使用JSP表单,则使用隐藏字段设置主键。

Jsp:

& lt;形式:输入类型=“hidden"路径=“id"值=“$ {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);
}
}

使用@DynamicUpdate注释。它更简洁,您不必为了获取保存的值而查询数据库。

你可以看到下面的例子:

private void updateDeliveryStatusOfEvent(Integer eventId, int deliveryStatus) {
try {
LOGGER.info("NOTIFICATION_EVENT updating with event id:{}", eventId);
Optional<Event> eventOptional = eventRepository.findById(eventId);
if (!eventOptional.isPresent()) {
LOGGER.info("Didn't find any updatable notification event with this eventId:{}", eventId);
}
Event event = eventOptional.get();
event.setDeliveryStatus(deliveryStatus);
event = eventRepository.save(event);
if (!Objects.isNull(event)) {
LOGGER.info("NOTIFICATION_EVENT Successfully Updated with this id:{}", eventId);
}
} catch (Exception e) {
LOGGER.error("Error :{} while updating NOTIFICATION_EVENT of event Id:{}", e, eventId);
}
}

或使用本机查询更新:

public interface YourRepositoryName extends JpaRepository<Event,Integer>{
@Transactional
@Modifying
@Query(value="update Event u set u.deliveryStatus = :deliveryStatus where u.eventId = :eventId", nativeQuery = true)
void setUserInfoById(@Param("deliveryStatus")String deliveryStatus, @Param("eventId")Integer eventId);
}

正如其他人回答的那样,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);
}

也许你想知道本我是从哪里来的。它来自数据库当然,你想要更新现有的数据,对吧?

在java 8中,你可以在UserService中使用存储库的findById

@Service
public class UserServiceImpl {


private final UserRepository repository;


public UserServiceImpl(UserRepository repository) {
this.repository = repository;
}


@Transactional
public void update(User user) {
repository
.findById(user.getId()) // returns Optional<User>
.ifPresent(user1 -> {
user1.setFirstname(user.getFirstname);
user1.setLastname(user.getLastname);


repository.save(user1);
});
}


}