好吧,这个问题几乎说明了一切。使用 JPARepository 如何更新实体?
JPARepository 只有一个 save 方法,它并没有告诉我它实际上是创建还是更新。例如,我向数据库 User 中插入一个简单的 Object,它具有三个字段: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;
}
}
然后我简单地调用 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 并没有解决这个问题。
实体的身份由它们的主键定义。由于 firstname
和 lastname
不是主键的一部分,因此您不能告诉 JPA 将具有相同 firstname
和 lastname
的 User
视为相同,如果它们具有不同的 userId
。
因此,如果您要更新由其 firstname
和 lastname
标识的 User
,您需要通过查询找到该 User
,然后更改您找到的对象的相应字段。这些更改将在事务结束时自动刷新到数据库,因此您无需执行任何操作来显式保存这些更改。
编辑:
也许我应该详细说明 JPA 的整体语义。设计持久性 API 有两种主要方法:
插入/更新方法。当您需要修改数据库时,您应该显式调用持久性 API 的方法:调用 insert 插入对象,或调用 update 将对象的新状态保存到数据库。
工作单元方法。在这种情况下,您有一组由持久性库管理的对象。您对这些对象所做的所有更改都将在工作单元结束时自动刷新到数据库中(即在典型情况下在当前事务结束时)。当您需要向数据库中插入新记录时,您可以管理相应的对象。托管对象由它们的主键标识,因此如果您将具有预定义主键的对象进行托管,它将与相同id的数据库记录相关联,并且该对象的状态将自动传播到该记录。
JPA 遵循后一种方法。 Spring Data JPA 中的 save()
由普通 JPA 中的 merge()
支持,因此它使您的实体托管如上所述。这意味着在具有预定义 id 的对象上调用 save()
将更新相应的数据库记录而不是插入新记录,并解释了为什么不调用 save()
的原因是 create()
。
由于@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 中运行此代码。
To update an entity by querying then saving is not efficient
这不是仅有的两个选择。有一种方法可以在不查询的情况下指定 id 并获取行对象。如果您执行 row = repo.getOne(id)
,然后执行 row.attr = 42; repo.save(row);
并查看日志,您将只看到更新查询。
您可以简单地将此函数与 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);
}
userRepository.getById(u.getid())
来避免两个查询(getById
而不是 findById
)。
正如其他人已经提到的,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)
方法插入新记录。
spring data save()
方法将帮助您执行以下两项操作:添加新项目和更新现有项目。
只需拨打 save()
即可享受生活 :))
Id
会保存它,我怎样才能避免保存新记录。
save()
我们可以避免在目标表中添加新记录(更新操作),如果该表有一些唯一列(例如 Name
)。现在传递的要更新的实体应该具有与表相同的 Name
值,否则它将创建一条新记录。
使用 spring-data-jpa save()
,我遇到了与@DtechNet 相同的问题。我的意思是每个 save()
都在创建新对象而不是更新。为了解决这个问题,我必须将 version
字段添加到实体和相关表中。
version
,save()
仍然会创建一个新实体。
这就是我解决问题的方法:
User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
@Transaction
。在这种情况下,userRepository.save(inbound);
中不需要,更改会自动刷新。
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));
}
使用 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);
});
}
}
具体来说,我如何告诉 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(),它应该为您整理所有内容。
如果您的主键是自动增量,那么您必须设置主键的值。对于保存();方法作为 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);
}
}
正如其他人的回答所提到的,方法 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 是从哪里来的。它当然来自数据库,你想更新现有数据对吗?
你可以看到下面的例子:
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);
}
不定期副业成功案例分享
extends CrudRepository<MyEntity, Integer>
而不是应有的extends CrudRepository<MyEntity, String>
。这有帮助吗?我知道这是将近一年后的事了。希望它可以帮助别人。