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


193

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

JPARepository只有一个save方法,它不会告诉我它是否实际上是在创建或更新。例如,我插入一个简单的对象数据库的用户,其中有三个领域: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的,并且现有实体应该更新?覆盖等于不能解决此问题。


1
在数据库中保存现有对象时,您确定ID会被重写吗?在我的项目tbh中从来没有过
拜伦·沃巴赫

@ByronVoorbach是的,您是对的,刚刚测试过。还更新了问题,谢谢
Eugene

2
您好朋友,您可以查看此链接stackoverflow.com/questions/24420572/…您可以像saveOrUpdate()这样的方法
ibrahimKiraz


我认为我们在这里有一个
不错的

Answers:


206

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

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

编辑:

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

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

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

JPA遵循后一种方法。save()Spring Data JPA中的JPA由merge()纯JPA 支持,因此,它使您的实体如上所述受到管理。这意味着调用save()具有预定义ID的对象将更新相应的数据库记录,而不是插入新的数据库记录,并且还说明了为什么save()不调用create()


好吧,我想我知道这一点。我严格指的是spring-data-jpa。我现在对这个答案有两个问题:1)业务价值不应该成为主键的一部分-这是已知的事情,对吗?因此,将名字和姓氏作为主键是不好的。并且2)为什么为什么不将此方法称为create,而是将其保存在spring-data-jpa中呢?
尤金(Eugene)2012年

1
“您实际上在看代码吗?”“ Spring Data JPA中的save()由纯JPA中的merge()支持”)?我只是做了,并且都通过持久或合并来备份。它会根据id(主键)的存在持续存在或更新。我认为,这应该记录在save方法中。因此,保存实际上是合并或持久保存。
尤金(Eugene)

我认为它也称为保存,因为它无论对象处于什么状态都应该保存该对象-它将执行等于保存状态的更新或插入操作。
尤金

1
这对我不起作用。我尝试使用有效的主键保存在实体上。我使用“ order / edit /:id”访问页面,实际上它通过ID为我提供了正确的对象。我为上帝的爱而尝试的任何事情都不会更新实体。它总是发布一个新的实体..我什至尝试制作自定义服务并在我的EntityManager中使用“合并”,但它仍然无法正常工作。它将始终发布新实体。
DtechNet

1
@DTechNet我遇到了一个与您类似的问题,DtechNet,结果是我的问题是我在Spring Data存储库界面中指定了错误的主键类型。它说extends CrudRepository<MyEntity, Integer>而不是extends CrudRepository<MyEntity, String>应该有。有帮助吗?我知道这将近一年后。希望它可以帮助别人。
肯特·布尔

139

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


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


10
确保在交易中运行它
hussachai 2015年

1
嘿! 谢谢我使用spring数据bean。因此它将自动处理我的更新。<S扩展T> S save(S实体); 自动负责更新。我不必使用您的方法!不管怎么说,多谢拉!
bks4line 2015年

3
任何时间:)如果您要保存实体,则save方法可以工作(它将调用委托给幕后的em.persist()或em.merge())。无论如何,当您只想更新数据库中的某些字段时,自定义查询很有用。
husshaii 2015年

当您的参数之一是子实体的ID(manyToOne)时,应该如何更新呢?(这本书有一位作者,您传递了书ID和作者ID来更新书作者)
Mahdi

1
To update an entity by querying then saving is not efficient这些不是唯一的两个选择。有一种方法可以指定id并获取行对象而不查询它。如果您先执行a row = repo.getOne(id)然后row.attr = 42; repo.save(row);查看日志,则将仅看到更新查询。
nurettin

21

您可以简单地将此函数与save()JPAfunction一起使用,但是作为参数发送的对象必须在数据库中包含一个现有的ID,否则它将不起作用,因为当我们发送不带ID的对象时,save()会直接在其中添加一行数据库,但是如果我们发送一个具有现有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);
}

4
如果必须在更新之前从数据库中加载对象,性能不是问题吗?(对不起,我的英语)
august0490

3
有两个查询而不是一个,这是非常不推荐的
Tigran Babajanyan

我知道我只是想出另一种方法来做!但是为什么jpa在id相同的时候实现了update功能呢?
Kalifornium

17

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



5

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

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

@Transaction对多个数据库请求使用上述方法。在这种情况下,不需要中的userRepository.save(inbound);,则自动清除更改。
Grigory Kislin

5

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

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


以这种方式,如果我发送了不同的内容Id将保存它,那么如何避免保存新记录。
Abd Abughazaleh

1
@AbdAbughazaleh检查存储库中是否存在传入ID。您可以使用'repository.findById(id).map(实体-> {//做某事返回repository.save(entity)}).orElseGet(()-> {//做某事返回;}); '
Amir Mhp

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

确实,@ JoshuaTaylor完全错过了:)将删除评论……
尤金(Eugene)

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.