ChatGPT解决这个技术问题 Extra ChatGPT

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

好吧,这个问题几乎说明了一切。使用 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 并没有解决这个问题。

当您在数据库中保存现有对象时,您确定 ID 会被重写吗?我的项目中从来没有这个
@ByronVoorbach 是的,你是对的,刚刚测试了这个。也更新问题,谢谢
你好朋友,你可以看看这个链接stackoverflow.com/questions/24420572/…你可以是像 saveOrUpdate() 这样的方法
我认为我们在这里有一个很好的解决方案:enter link description here

S
Simeon Leyzerzon

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

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

编辑:

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

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

工作单元方法。在这种情况下,您有一组由持久性库管理的对象。您对这些对象所做的所有更改都将在工作单元结束时自动刷新到数据库中(即在典型情况下在当前事务结束时)。当您需要向数据库中插入新记录时,您可以管理相应的对象。托管对象由它们的主键标识,因此如果您将具有预定义主键的对象进行托管,它将与相同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>。这有帮助吗?我知道这是将近一年后的事了。希望它可以帮助别人。
h
hussachai

由于@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 实体);自动处理更新。我不必用你的方法!不管怎么说,多谢拉!
任何时候 :) 如果您想保存实体,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); 并查看日志,您将只看到更新查询。
d
deHaar

您可以简单地将此函数与 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)。
E
Eugene

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

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

https://i.stack.imgur.com/ZCGvJ.png

好的,让我们检查 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)方法插入新记录。


A
Artem Arkhipov

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 字段不够吗?那将是唯一的列,对吗?
i
ilja

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


将版本字段添加到实体和相关表是什么意思。
对我来说,即使添加了 versionsave() 仍然会创建一个新实体。
@ParagKadam,在保存之前确保您的实体上有 ID。
A
Adam

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

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

对多个数据库请求使用上述方法 @Transaction。在这种情况下,userRepository.save(inbound); 中不需要,更改会自动刷新。
B
BinLee
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;})
J
Javid

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

}

A
Andreas Gelever

具体来说,我如何告诉 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(),它应该为您整理所有内容。


s
sanku

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

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

jsp:

爪哇:

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

J
Johan

正如其他人的回答所提到的,方法 save() 是双重功能。它既可以保存也可以更新,如果您提供 id,它会自动更新。

对于控制器类中的更新方法,我建议使用@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);
}

#更新方法PATCH

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

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


W
Waize

你可以看到下面的例子:

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

T
Tarek Sahalia

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


如果您在此处添加一个简单的示例,将会很有帮助。