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

Eug*_*ene 171 java jpa spring-data-jpa

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

JPARepository只有一个save方法,它不会告诉我它是否实际创建或更新.例如,我将一个简单的Object插入数据库User,它有三个字段:firstname和lastname以及age:

 @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;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后我只是"保存",此时实际上是一个插入:

 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);
Run Code Online (Sandbox Code Playgroud)

好的一切都很好.现在我想更新这个用户,比如改变他的年龄.好吧,无论是QueryDSL还是NamedQuery,我都可以使用Query.但是,考虑到我只想使用spring-data-jpa和JPARepository,我怎么告诉它而不是插入我想要更新?

具体来说,我如何告诉spring-data-jpa具有相同用户名和名字的用户实际上是EQUAL并且它应该更新实体.压倒平等不起作用.

谢谢!

axt*_*avt 184

实体的身份由其主键定义.由于firstname并且lastname不是主键的一部分,如果它们具有不同的s ,则不能告诉JPA User使用相同的firstnames和lastnames 来对待userIds.

所以,如果你想更新User确定了其firstnamelastname,你需要找到User一个查询,然后更改对象的相应字段中输入您发现的.这些更改将在事务结束时自动刷新到数据库,因此您无需执行任何操作即可明确保存这些更改.

编辑:

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

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

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

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

  • “Spring Data JPA 中的 save() 由普通 JPA 中的 merge() 支持”您真的看过代码吗?我刚刚这样做了,它都通过持久或合并来支持。它将根据 id(主键)的存在进行持久化或更新。我认为,这应该记录在 save 方法中。所以保存实际上是合并或持久。 (3认同)
  • 这对我不起作用。我试过用有效的主键保存在实体上。我使用“order/edit/:id”访问页面,它实际上通过 Id 为我提供了正确的对象。我为上帝的爱所做的任何努力都不会更新实体。它总是发布一个新实体。我什至尝试制作自定义服务并使用“合并”与我的 EntityManager ,但它仍然无法正常工作。它总是会发布一个新实体。 (2认同)
  • @DTechNet 我遇到了与您类似的问题,DtechNet,结果证明我的问题是我在 Spring Data 存储库接口中指定了错误的主键类型。它说`extends CrudRepository<MyEntity, Integer>` 而不是它应该有的`extends CrudRepository<MyEntity, String>`。这有帮助吗?我知道这已经快一年了。希望它可以帮助别人。 (2认同)

hus*_*hai 117

由于@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);
Run Code Online (Sandbox Code Playgroud)

@Query是用于定义自定义查询,并@Modifying可以告诉spring-data-jpa这个查询是更新操作,它需要executeUpdate()executeQuery().

  • 确保在事务中运行它 (8认同)
  • 任何时候:)如果你想保存实体,它的保存方法是有效的(它会将调用委托给场景后面的em.persist()或em.merge()).无论如何,当您只想更新数据库中的某些字段时,自定义查询很有用. (3认同)
  • `通过查询然后保存来更新实体效率不高`这不是唯一的两个选择。有一种方法可以指定 id 并在不查询的情况下获取行对象。如果你执行 `row = repo.getOne(id)` 然后 `row.attr = 42; repo.save(row);` 并查看日志,您将只看到更新查询。 (2认同)

Kal*_*ium 18

您可以简单地将此函数与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);
}
Run Code Online (Sandbox Code Playgroud)

  • 如果在更新之前必须从数据库加载对象,性能不会有问题吗?(对不起我的英语不好) (8认同)
  • 有两个查询而不是一个,这是非常不受欢迎的 (3认同)

Eug*_*ene 15

正如其他人已经提到的那样,它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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,它将首先检查ID是否存在,如果实体已经存在,则仅通过merge(entity)方法进行更新,否则,通过persist(entity)方法插入新记录.


Jav*_*vid 8

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

}
Run Code Online (Sandbox Code Playgroud)


smi*_*ile 7

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


小智 6

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

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

  • 这样如果我发送不同的“Id”将保存它,我怎样才能避免保存新记录。 (4认同)

Ada*_*dam 5

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

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Run Code Online (Sandbox Code Playgroud)

  • 对多个数据库请求使用上面的“@Transaction”方法。在这种情况下,不需要“userRepository.save(inbound);”,更改会自动刷新。 (2认同)