![](/img/trans.png)
[英]How I Use spring-data-jpa mongoRepository to query and update?
[英]How do I update an entity using spring-data-jpa?
好吧,這個問題幾乎說明了一切。 使用 JPARepository 如何更新實體?
JPARepository 只有一個保存方法,它並沒有告訴我它實際上是創建還是更新。 例如,我向數據庫 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
將對象的新狀態保存到數據庫。
工作單元方法。 在這種情況下,您有一組由持久性庫管理的對象。 您對這些對象所做的所有更改都將在工作單元結束時自動刷新到數據庫中(即在典型情況下在當前事務結束時)。 當您需要向數據庫中插入新記錄時,您將相應的對象設為managed 。 托管對象由它們的主鍵標識,因此如果您使用預定義的主鍵管理對象,它將與相同 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。 否則為假。
注意:在事務中運行此代碼。
您可以簡單地將此函數與 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);
}
正如其他人已經提到的那樣, 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)
方法插入一條新記錄。
使用 spring-data-jpa save()
,我遇到了與@DtechNet 相同的問題。 我的意思是每個save()
都在創建新對象而不是更新。 為了解決這個問題,我必須將version
字段添加到實體和相關表中。
spring data save()
方法將幫助您執行以下兩項操作:添加新項目和更新現有項目。
只需調用save()
並享受生活:))
這就是我解決問題的方法:
User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
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));
}
具體來說,我如何告訴 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(),它應該為您整理所有內容。
使用 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);
});
}
}
如果您的主鍵是自動增量,那么您必須設置主鍵的值。 對於保存(); 方法作為 update() 工作。否則它將在 db 中創建一個新記錄。
如果您使用的是 jsp 表單,則使用隱藏字段來設置主鍵。
jsp:
<form:input type="hidden" path="id" value="${user.id}"/>
爪哇:
@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);
}
}
你可以看到下面的例子:
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);
}
正如其他人的回答所提到的,方法 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 是從哪里來的。 它當然來自數據庫,你想更新現有數據對嗎?
使用@DynamicUpdate注釋。 它更干凈,您不必處理查詢數據庫即可獲取保存的值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.