267

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

JPARepository 只有一个保存方法,它并没有告诉我它实际上是创建还是更新。例如,我向数据库 User 中插入了一个简单的 Object,它具有三个字段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 并且应该更新现有实体?覆盖 equals 并没有解决这个问题。

4

12 回答 12

259

实体的身份由它们的主键定义。由于firstnameandlastname不是主键的一部分,因此您不能告诉 JPA 将User具有相同firstnames 的 s 和lastname具有不同 s 的 s 视为相等userId

因此,如果您想更新User由其firstnameand标识的 a lastname,您需要User通过查询找到它,然后更改您找到的对象的适当字段。这些更改将在事务结束时自动刷新到数据库,因此您无需执行任何操作即可显式保存这些更改。

编辑:

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

  • 插入/更新方法。当您需要修改数据库时,您应该显式调用持久性 API 的方法:调用insert以插入对象,或update将对象的新状态保存到数据库。

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

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

于 2012-08-09T10:43:42.270 回答
192

由于@axtavt 的回答侧重于JPAspring-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- 正在更新的记录数。
boolean- 如果有记录被更新,则为真。否则为假。


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

于 2015-06-30T22:28:59.903 回答
43

您可以简单地将此函数与 save() JPA 函数一起使用,但是作为参数发送的对象必须包含数据库中现有的 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);
}
于 2018-09-30T10:20:07.837 回答
32

正如其他人已经提到的,它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)方法更新,否则通过方法插入新记录persist(entity)

于 2018-12-11T03:38:47.220 回答
9

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

于 2015-12-06T18:54:40.890 回答
9

spring datasave()方法将帮助您执行以下两项操作:添加新项目和更新现有项目。

只需致电save()并享受生活 :))

于 2019-06-03T10:06:54.040 回答
8

这就是我解决问题的方法:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
于 2017-09-21T15:40:56.253 回答
2
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));
}
于 2019-03-26T10:42:50.503 回答
1

具体来说,我如何告诉 spring-data-jpa 具有相同用户名和名字的用户实际上是 EQUAL 并且应该更新实体。覆盖 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(),它应该为您整理所有内容。

于 2019-07-25T12:03:50.163 回答
0

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

如果您使用的是 jsp 表单,则使用隐藏字段来设置主键。

jsp:

<form:input type="hidden" path="id" value="${user.id}"/>

爪哇:

@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);
    }
  }
于 2020-10-18T09:37:56.103 回答
0

你可以看到下面的例子:

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 extend 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);
}
于 2021-12-28T10:35:14.960 回答
-1

使用@DynamicUpdate注释。它更干净,您不必处理查询数据库即可获取保存的值。

于 2021-03-29T20:56:56.660 回答