How to beautifully update a JPA entity in Spring Data?

JavaSpringJpaSpring DataSpring Data-Jpa

Java Problem Overview


So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.

Assume there is the follwing entity:

package stackoverflowTest.dao;

import javax.persistence.*;

@Entity
@Table(name = "customers")
public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private long id;

@Column(name = "name")
private String name;

public Customer(String name) {
    this.name = name;
}

public Customer() {
}

public long getId() {
    return id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
}

We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.

package stackoverflowTest.dto;

public class CustomerDto {

private long id;
private String name;

public CustomerDto(long id, String name) {
    this.id = id;
    this.name = name;
}

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
}

So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.

Now I have to save this updated DTO to the database.

Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)

However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:

  1. make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.

or

  1. add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)

So my question is wether there is a general rule how to do this? Any maybe what the drawbacks of the 2 methods I explained are?

Java Solutions


Solution 1 - Java

Even better then @Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)

Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);

Is's better because getOne(ID id) gets you only a reference (proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find() like in @Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.

Solution 2 - Java

In Spring Data you simply define an update query if you have the ID

  @Repository
  public interface CustomerRepository extends JpaRepository<Customer , Long> {

     @Query("update Customer c set c.name = :name WHERE c.id = :customerId")
     void setCustomerName(@Param("customerId") Long id, @Param("name") String name);
     
  }

Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.

Solution 3 - Java

This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.

Solution 4 - Java

If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer instance and map the updated fields from the DTO to that.

Customer entity = //load from DB
//map fields from DTO to entity

Solution 5 - Java

Simple JPA update..

Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);

Solution 6 - Java

> So now assume the Customer wants to change his name in the webui - > then there will be some controller action, where there will be the > updated DTO with the old ID and the new name.

Normally, you have the following workflow:

  1. User requests his data from server and obtains them in UI;
  2. User corrects his data and sends it back to server with already present ID;
  3. On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
  4. Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread);

P.S. This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary @DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.

P.S. If you do not wanna pay for first select statement and prefer to use Spring Data's @Modifying query, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.

Solution 7 - Java

I have encountered this issue!
Luckily, I determine 2 ways and understand some things but the rest is not clear.
Hope someone discuss or support if you know.

  1. Use RepositoryExtendJPA.save(entity).
    Example:
    List<Person> person = this.PersonRepository.findById(0) person.setName("Neo"); This.PersonReository.save(person);
    this block code updated new name for record which has id = 0;
  2. Use @Transactional from javax or spring framework.
    Let put @Transactional upon your class or specified function, both are ok.
    I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.

Solution 8 - Java

There is a method in JpaRepository

getOne

It is deprecated at the moment in favor of

getById

So correct approach would be

Customer customerToUpdate = customerRepository.getById(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukas MakorView Question on Stackoverflow
Solution 1 - JavaRobert NiestrojView Answer on Stackoverflow
Solution 2 - JavagorefestView Answer on Stackoverflow
Solution 3 - JavaAmer QarabsaView Answer on Stackoverflow
Solution 4 - JavaAlan HayView Answer on Stackoverflow
Solution 5 - JavaEstyView Answer on Stackoverflow
Solution 6 - JavaAndreas GeleverView Answer on Stackoverflow
Solution 7 - JavaNeo_View Answer on Stackoverflow
Solution 8 - JavasantikView Answer on Stackoverflow