【问题标题】:How do I update an entity using spring-data-jpa?如何使用 spring-data-jpa 更新实体?
【发布时间】:2012-08-06 13:32:31
【问题描述】:

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

JPARepository 只有一个 save 方法,它并没有告诉我它实际上是创建还是更新。比如我在数据库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 并没有解决这个问题。

【问题讨论】:

标签: java jpa spring-data-jpa


【解决方案1】:

实体的身份由它们的主键定义。由于firstnamelastname 不是主键的一部分,因此您不能告诉JPA 将Users 与相同的firstnames 和lastnames 视为相同,如果它们具有不同的userIds。

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

编辑:

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

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

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

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

【讨论】:

  • 好吧,我想我知道这一点。我严格指的是spring-data-jpa。我现在对这个答案有两个问题:1)业务价值不应该是主键的一部分 - 这是一个已知的事情,对吧?因此,将名字和姓氏作为主键并不好。 2)为什么这个方法不叫create,而是保存在spring-data-jpa中?
  • “Spring Data JPA 中的 save() 由纯 JPA 中的 merge() 支持”你真的看过代码吗?我刚刚做了,它都通过持久化或合并来支持。它将根据 id(主键)的存在保持或更新。我认为,这应该记录在 save 方法中。所以保存实际上是合并或持久化。
  • 我认为它也被称为保存,因为它应该保存对象,无论它处于什么状态 - 它都会执行更新或插入等于保存状态。跨度>
  • 这对我不起作用。我尝试保存具有有效主键的实体。我使用“order/edit/:id”访问该页面,它实际上通过 Id 为我提供了正确的对象。我为上帝的爱而尝试的任何事情都不会更新实体。它总是发布一个新实体.. 我什至尝试制作自定义服务并与我的 EntityManager 一起使用“合并”,但它仍然无法正常工作。它总是会发布一个新实体。
  • @DTechNet 我遇到了与您类似的问题,DtechNet,结果我的问题是我在 Spring Data 存储库接口中指定了错误的主键类型。它说的是extends CrudRepository<MyEntity, Integer>,而不是应该有的extends CrudRepository<MyEntity, String>。这有帮助吗?我知道这是将近一年后的事了。希望它可以帮助别人。
【解决方案2】:

由于@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 - 正在更新的记录数。
boolean - 如果有正在更新的记录,则为 true。否则为假。


注意:在Transaction 中运行此代码。

【讨论】:

  • 确保在事务中运行它
  • 嘿!谢谢我正在使用弹簧数据豆。所以它会自动处理我的更新。 S 保存(S 实体);自动处理更新。我不必用你的方法!无论如何,谢谢!
  • Anytime :) 如果您想保存实体,save 方法确实有效(它将在后台将调用委托给 em.persist() 或 em.merge())。无论如何,当您只想更新数据库中的某些字段时,自定义查询很有用。
  • 当您的参数之一是子实体的 id(manyToOne)时如何更新? (书有作者,你通过书 id 和作者 id 来更新书作者)
  • To update an entity by querying then saving is not efficient 这不是仅有的两个选择。有一种方法可以在不查询的情况下指定 id 并获取行对象。如果您执行row = repo.getOne(id) 然后row.attr = 42; repo.save(row); 并查看日志,您将只看到更新查询。
【解决方案3】:

你可以简单地使用这个函数和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);
}

【讨论】:

  • 如果在更新之前必须从数据库加载对象,性能不是问题吗? (对不起我的英语)
  • 有两个查询而不是一个,这是非常不推荐的
  • 我知道我只是出现了另一种方法!但是为什么jpa在id相同的情况下实现了更新功能呢?
  • 您可以使用userRepository.getById(u.getid()) 来避免两次查询(getById 而不是findById)。
【解决方案4】:

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

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

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

好的,让我们在SimpleJpaRepository<T, ID> 上检查save() 的实现,

@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)方法插入新记录.

【讨论】:

    【解决方案5】:

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

    只需拨打save() 即可享受生活:))

    【讨论】:

    • 这样上去如果我发送不同的Id会保存它,我怎样才能避免保存新记录。
    • @AbdAbughazaleh 检查传入的 Id 是否存在于您的存储库中。你可以使用 ' repository.findById(id).map( entity -> { //do something return repository.save(entity) } ).orElseGet( () -> { //do something return; }); '
    • 使用save(),如果目标表有一些唯一的列(比如Name),我们可以避免在目标表中添加新记录(更新操作)。现在传递的要更新的实体应该具有与表相同的Name 值,否则它将创建一个新记录。
    • @AbdAbughazaleh #OneToMany 删除传入请求中不可用的子实体怎么样?我被困了15天。您能提供您宝贵的意见吗?
    • @Ram Id 字段不够吗?那将是唯一的列,对吧?
    【解决方案6】:

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

    【讨论】:

    【解决方案7】:

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

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

    【讨论】:

    • 使用@Transaction上面的方法处理几个数据库请求。在这种情况下不需要userRepository.save(inbound);,更改会自动刷新。
    【解决方案8】:
    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));
    }
    

    【讨论】:

    • 此语句需要稍作改动。 .map(en -> {en.setHumanId(replacement); return en;})
    【解决方案9】:

    具体来说,我如何告诉 spring-data-jpa 拥有 相同的用户名和名字实际上是 EQUAL 并且应该是 更新实体。覆盖等于无效。

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

    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】:

      如果你的主键是自增的,那么你必须设置主键的值。 对于保存();方法作为 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);
          }
        }
      

      【讨论】:

        【解决方案11】:

        你可以看下面的例子:

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

        【讨论】:

          【解决方案12】:

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

          【讨论】:

          • 如果你在这里添加一个简单的例子会很有帮助。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-16
          • 2019-09-21
          • 2017-02-06
          • 1970-01-01
          • 2018-04-20
          • 2019-02-09
          相关资源
          最近更新 更多