321

Well the question pretty much says everything. Using JPARepository how do I update an entity?

JPARepository has only a save method, which does not tell me if it's create or update actually. For example, I insert a simple Object to the database User, which has three fields: firstname, lastname and 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;
  }
}

Then I simply call save(), which at this point is actually an insert into database:

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

So far so good. Now I want to update this user, say change his age. For this purpose I could use a Query, either QueryDSL or NamedQuery, whatever. But, considering I just want to use spring-data-jpa and the JPARepository, how do I tell it that instead of an insert I want to do an update?

Specifically, how do I tell spring-data-jpa that users with the same username and firstname are actually EQUAL and that the existing entity supposed to be updated? Overriding equals did not solve this problem.

ilja
  • 351
  • 2
  • 14
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    Are you sure the ID gets rewritten when you save an existing object in the database?? Never had this on my project tbh – Byron Voorbach Aug 09 '12 at 11:27
  • @ByronVoorbach yup you're right, just tested this. update the question also, thx – Eugene Aug 09 '12 at 12:13
  • 2
    Hello friend, you can look this link http://stackoverflow.com/questions/24420572/update-or-saveorupdate-in-crudrespository-is-there-any-options-available you can be a approach like saveOrUpdate() – ibrahimKiraz Dec 06 '15 at 18:58
  • https://stackoverflow.com/questions/39741102/how-to-beautifully-update-a-jpa-entity-in-spring-data/ – Robert Niestroj Jan 11 '20 at 20:58
  • I think that we have a beautiful solution here: [enter link description here](https://stackoverflow.com/a/39746931/6127027) – Clebio Vieira Jun 10 '20 at 16:19

16 Answers16

292

Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.

So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.

EDIT:

Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:

  • insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.

  • Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.

JPA follows the latter approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().

Simeon Leyzerzon
  • 18,658
  • 9
  • 54
  • 82
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • well yes I think I know this. I was strictly referring to spring-data-jpa. I have two problems with this answer now: 1) business values are not supposed to be part of the primary key - that is a known thing, right? So having the firstname and lastname as primary key is not good. And 2) Why is this method then not called create, but save instead in spring-data-jpa? – Eugene Aug 09 '12 at 10:50
  • 3
    "save() in Spring Data JPA is backed by merge() in plain JPA" did you actually look at the code? I just did and it both backed up by either persist or merge. It will persist or update based on the presence of the id (primary key). This, I think, should be documented in the save method. So save is actually EITHER merge or persist. – Eugene Aug 09 '12 at 12:21
  • 1
    I think it is also called save, because it it supposed to save the object no matter what state it is in - it is going to perform an update or insert which is equal to save state. – Eugene Aug 09 '12 at 12:29
  • @Eugene: Yes, I did. As far as I understand it doesn't matter, because `persist()` is called when entity is new, i.e. when `merge()` works as `persist()`. – axtavt Aug 09 '12 at 12:30
  • yup, I guess we are talking about the same thing, in different words. Thx for your time, points given. – Eugene Aug 09 '12 at 12:48
  • 2
    This will not work for me. I have tried save on an entity with a valid primary key. I access the page with "order/edit/:id" and it actually gives me the correct object by Id. Nothing I try for the love of God will update the entity. It always posts a new entity .. I even tried making a custom service and using "merge" with my EntityManager and it still won't work. It will always post a new entity. – DtechNet Aug 15 '15 at 02:27
  • 2
    @DTechNet I was experiencing a similar problem to you, DtechNet, and it turned out my problem was that I had the wrong primary key type specified in my Spring Data repository interface. It said `extends CrudRepository` instead of `extends CrudRepository` like it should have. Does that help? I know this is almost a year later. Hope it helps someone else. – Kent Bull Apr 06 '16 at 20:48
  • @KentJohnson why do you store the primary key as a String? I thought you only ever used Long or Integer. – DtechNet Apr 19 '16 at 16:12
  • @DTechNet it made business sense to those who made the decision. It is a natural key for the dataset so those who made the decision thought it was a good idea. It is for uniquely identifying an object that has a String of digits as its physical identifier. – Kent Bull Apr 19 '16 at 23:33
  • Alright. I don't plan on using a String though. I plan on using Integer. Perhaps Long. – DtechNet Apr 22 '16 at 17:53
  • Your answer is very inspiring thanks. if **Spring data jpa `save()` is actually `merge()` in JPA**, why bother `CascadeType.Persist` on `@XtoX` relationship? – Tiina Jan 05 '18 at 08:05
  • Curious question, but when is said transaction end? Does it end when the calling method finishes? – AKJ May 11 '21 at 15:06
230

Since the answer by @axtavt focuses on JPA not spring-data-jpa

To update an entity by querying then saving is not efficient because it requires two queries and possibly the query can be quite expensive since it may join other tables and load any collections that have fetchType=FetchType.EAGER

Spring-data-jpa supports update operation.
You have to define the method in Repository interface.and annotated it with @Query and @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 is for defining custom query and @Modifying is for telling spring-data-jpa that this query is an update operation and it requires executeUpdate() not executeQuery().

You can specify the return type as int, having the number of records being updated.


Note: Run this code in a Transaction.

David Gomes
  • 650
  • 2
  • 10
  • 34
hussachai
  • 4,322
  • 2
  • 16
  • 17
  • 24
    Make sure you run it in transaction – hussachai Oct 20 '15 at 19:23
  • 1
    Hey! Thanks am using spring data beans. So it will automatically takes care of my update. S save(S entity); automatically takes care of update. i didnt have to use your method! Thanks anyways! – bks4line Oct 21 '15 at 00:00
  • 3
    Anytime :) The save method does work if you want to save the entity (It will delegate the call to either em.persist() or em.merge() behind the scene). Anyway, the custom query is useful when you want to update just some fields in database. – hussachai Oct 21 '15 at 10:34
  • how about when one of your parameters is id of sub entity( manyToOne) how should update that? (book have an Author and you passed book id and author id to update book author) – Mahdi Apr 30 '17 at 16:29
  • 2
    `To update an entity by querying then saving is not efficient` these are not the only two choices. There is a way to specify id and get the row object without querying it. If you do a `row = repo.getOne(id)` and then `row.attr = 42; repo.save(row);` and watch the logs, you will see only the update query. – nurettin Dec 06 '18 at 07:16
  • @nurettin Thank you for your comment. I'm just curious. Is that vendor specific optimization? I mean will it work across different JPA imps? and what if you perform a complex query instead of querying by ID, will it still generates a single update query? – hussachai Dec 06 '18 at 18:10
  • @hussachai you can always generate a reference to a row using it's ID. For more complex queries like inner join updates or updates with dissimilar where conditions, it won't be useful. – nurettin Dec 06 '18 at 18:42
  • First, It's not a good practice to eagerly load a collection. Second, if we choose to use Modifiying, we should also manually update the version. So, I don't think it's a good option to update a single entity instance using Modifying if performance is not a real matter. Modifying should be used only for batch updates. – Mohamed Gara May 03 '19 at 09:18
  • @hussachai - Is there any way if we can count=1 of update if successful? Then its very best to implement – PAA Jul 18 '19 at 16:35
  • Thanks just one question - How will I know that the specific entry is updated or not? Can we return something to know it is successfully updated or not found or given input value? – I'm_Pratik May 06 '20 at 03:35
  • Updated the answer. You can return `int` or `boolean` as well. – hussachai May 06 '20 at 05:10
  • @Transactional add on top of the query. – Francis Raj Nov 17 '20 at 09:43
  • Will this still be beneficial if we need to do multiple updates at once as opposed to using `saveAll()`? – linuxNoob Jan 25 '21 at 17:22
  • 1
    Modifying queries can only use void or int/Integer as return type. boolean will not work here – Hari Krishna Apr 09 '21 at 04:53
  • How can i make spring jpa throw exception when the where clause in update query is not specified. ie the number of updated row is zero? – Shantam Mittal Nov 07 '21 at 11:30
  • How do you determine whether to insert a new item or update though? I'm assuming this example is only beneficial when you know you will be updating an existing row.. – Richard Jan 19 '22 at 14:33
  • the documentation for @Modifying @Query and for @Transactional – Joand Jan 25 '22 at 14:26
60

You can simply use this function with save() JPAfunction, but the object sent as parameter must contain an existing id in the database otherwise it will not work, because save() when we send an object without id, it adds directly a row in database, but if we send an object with an existing id, it changes the columns already found in the database.

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);
}
deHaar
  • 17,687
  • 10
  • 38
  • 51
Kalifornium
  • 972
  • 9
  • 10
47

As what has already mentioned by others, the save() itself contains both create and update operation.

I just want to add supplement about what behind the save() method.

Firstly, let's see the extend/implement hierarchy of the CrudRepository<T,ID>, enter image description here

Ok, let's check the save() implementation at 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);
    }
}

As you can see, it will check whether the ID is existed or not firstly, if the entity is already there, only update will happen by merge(entity) method and if else, a new record is inserted by persist(entity) method.

Eugene
  • 10,627
  • 5
  • 49
  • 67
  • should not `merge` be used with detached entities. And to confuse further [`if the entity is transient, it copies upon a newly created persistent entity.`](https://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate#3-merge) . Why use persist at all. If merge can handle all four states. – samshers Aug 04 '22 at 14:42
  • good reference to code +1 – samshers Aug 04 '22 at 14:44
  • Most developers break their head creating routines to process updates when in reality all the hard work is already done. This is the best, most practical answer. – Gi1ber7 Dec 02 '22 at 12:56
13

spring data save() method will help you to perform both: adding new item and updating an existed item.

Just call the save() and enjoy the life :))

Artem Arkhipov
  • 7,025
  • 5
  • 30
  • 52
Amir Mhp
  • 147
  • 1
  • 3
  • 4
    Up in this way if i sent different `Id` will save it , how can i avoid save new record . – Abd Abughazaleh Apr 28 '20 at 10:14
  • 1
    @AbdAbughazaleh check whether incoming Id exists in your repository or not. you can use ' repository.findById(id).map( entity -> { //do something return repository.save(entity) } ).orElseGet( () -> { //do something return; }); ' – Amir Mhp May 04 '20 at 14:38
  • Using `save()` we can avoid adding new record in the targeted table (update operation) if that table has some unique column (say `Name`). Now the passed entity to be updated should have same `Name` value as table otherwise it will create a new record. – Ram Jul 22 '20 at 06:08
  • @AbdAbughazaleh What about #OneToMany deleting the child entities that are not available in incoming request? I'm stuck since 15 days. Can you please provide your precious inputs? – Pratik Ambani May 05 '21 at 05:20
  • @Ram Isn't Id field sufficient? That will be the unique column right? – Leena Dec 18 '21 at 18:21
9

Using spring-data-jpa save(), I was having same problem as @DtechNet. I mean every save() was creating new object instead of update. To solve this I had to add version field to entity and related table.

ilja
  • 351
  • 2
  • 14
smile
  • 498
  • 7
  • 18
8

This is how I solved the problem:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adam
  • 2,616
  • 33
  • 29
  • 2
    Use `@Transaction` above method for several db request. An in this case no need in `userRepository.save(inbound);`, changes flushed automatically. – Grigory Kislin Sep 17 '18 at 19:04
5

With java 8 you can use repository's findById in UserService

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

}
Javid
  • 145
  • 1
  • 10
2
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));
}
BinLee
  • 39
  • 4
  • This statement needs a bit change. .map(en -> {en.setHumanId(replacement); return en;}) – Atul Nov 01 '20 at 17:09
1

Specifically how do I tell spring-data-jpa that users that have the same username and firstname are actually EQUAL and that it is supposed to update the entity. Overriding equals did not work.

For this particular purpose one can introduce a composite key like this:

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`))

Mapping:

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

Here is how to use it:

@Entity
public class UserEntity implements Serializable {
    @EmbeddedId
    private UserKey primaryKey;

    private String description;

    //...
}

JpaRepository would look like this:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Then, you could use the following idiom: accept DTO with user info, extract name and firstname and create UserKey, then create a UserEntity with this composite key and then invoke Spring Data save() which should sort everything out for you.

Andreas Gelever
  • 1,736
  • 3
  • 19
  • 25
1

You can see the example below:

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

Or Update Using Native Query:

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);
}
Waize
  • 143
  • 3
  • 14
Golam Kibria
  • 114
  • 7
1

As mentioned by others answer, method save() is dual function. It can both do save or update, it's automatically update if you provide the id.

for update method in controller class I suggested to use @PatchMapping. below is the example.

#Save method POST

{
    "username": "jhon.doe",
    "displayName": "Jhon",
    "password": "xxxyyyzzz",
    "email": "jhon.doe@mail.com"
}
@PostMapping("/user")
public void setUser(@RequestBody User user) {
    userService.save(user);
}

#Update method 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);
}

Maybe you're wondering where the id's come from. It comes from the database of course, you want to update the existing data right?

Johan
  • 207
  • 4
  • 14
  • What is UserService business logic here? repository.save() will add new entry right. – Thamaraiselvam Nov 01 '22 at 16:45
  • @Thamaraiselvam, you are right, it is for saving. May be you are wondering why I'm using UserService instead of UserRepository, eventhough both class have the same function. It's for internal convention and may be "culture" of the previous experience it's always using service when handling business logic. It's not simple saving though, we are adding AOP before and after the "service" classes, like "update by", "created by", "modified date", "created date". – Johan Nov 02 '22 at 01:58
0

If your primary key is autoincrement then, you have to set the value for the primary key. for the save(); method to work as a update().else it will create a new record in db.

if you are using jsp form then use hidden filed to set primary key.

Jsp:

<form:input type="hidden" path="id" value="${user.id}"/>

Java:

@PostMapping("/update")
public String updateUser(@ModelAttribute User user) {
    repo.save(user);
    return "redirect:userlist";
}

also look at this:

@Override
  @Transactional
  public Customer save(Customer customer) {

    // Is new?
    if (customer.getId() == null) {
      em.persist(customer);
      return customer;
    } else {
      return em.merge(customer);
    }
  }
sanku
  • 9
  • 2
0

Use @DynamicUpdate annotation. it is cleaner and you don't have to deal with querying the database in order to get the saved values.

0

I did this for my Entity UserModel:

In the Controller:

        @PutMapping("/{id}")
        public Optional<UserModel> update(@RequestBody UserModel user, @PathVariable Long id) {
            return this.userService.update(user, id);
        }

And in the Service:

    public Optional<UserModel> update(UserModel req, Long id){
        Optional<UserModel> user = userRepository.findById(id);
        if (user != null) {
            userRepository.save(req);
        }
        return user;
    }

Example with postman: Postman method PUT example

gjuliane
  • 1
  • 1
0

just use

userRepository.save(user)

if any user of this user's primary-key (i.e. id in your case) is found then this statement will update existing user otherwise this statement will save new user.

Muhammad Taha
  • 137
  • 1
  • 6