这个问题几乎说明了一切。使用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,并且现有实体应该被更新?重写等号并不能解决这个问题。


实体的标识由它们的主键定义。因为姓和名不是主键的一部分,所以您不能告诉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()。

因为@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,其中包含正在更新的记录的数量。


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

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

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

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>,

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

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拥有 相同的用户名和名字实际上是相等的,这是假定的 更新实体。重写等号不起作用。

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

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:

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

使用@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);
                });
    }

}