Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 584 Vote(s) - 3.53 Average
  • 1
  • 2
  • 3
  • 4
  • 5
JPA: update only specific fields

#1
Is there a way for updating **only some fields** of an entity object using the method `save` from **Spring Data JPA**?

For example I have a JPA entity like this:

@Entity
public class User {

@Id
private Long id;

@NotNull
private String login;

@Id
private String name;

// getter / setter
// ...
}

With its CRUD repo:

public interface UserRepository extends CrudRepository<User, Long> { }

In **Spring MVC** I have a controller that get an `User` object for update it:

@RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> updateUser(@RequestBody User user) {

// Assuming that user have its id and it is already stored in the database,
// and user.login is null since I don't want to change it,
// while user.name have the new value

// I would update only its name while the login value should keep the value
// in the database
userRepository.save(user);

// ...
}

I know that I could load the user using `findOne`, then change its name and update it using `save`... But if I have 100 fields and I want to update 50 of them it could be very annoying change each value..

Is there no way to tell something like "**skip all null values when save the object**"?

Reply

#2
the problem is not spring data jpa related but the jpa implementation lib that you are using related.
In case of hibernate you may have a look at:

[To see links please register here]


Reply

#3
Using JPA you can do it this way.

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaUpdate<User> criteria = builder.createCriteriaUpdate(User.class);
Root<User> root = criteria.from(User.class);
criteria.set(root.get("lastSeen"), date);
criteria.where(builder.equal(root.get("id"), user.getId()));
session.createQuery(criteria).executeUpdate();
Reply

#4
I had the same question and as M. Deinum points out, the answer is no, you can't use save. The main problem being that Spring Data wouldn't know what to do with nulls. Is the null value not set or is it set because it needs to be deleted?

Now judging from you question, I assume you also had the same thought that I had, which was that save would allow me to avoid manually setting all the changed values.

So is it possible to avoid all the manuel mapping then? Well, if you choose to adhere to the convention that nulls always means 'not set' and you have the original model id, then yes.
You can avoid any mapping yourself by using Springs BeanUtils.

You could do the following:

1. Read the existing object
2. Use BeanUtils to copy values
3. Save the object

Now, Spring's BeanUtils actual doesn't support not copying null values, so it will overwrite any values not set with null on the exiting model object. Luckily, there is a solution here:

[To see links please register here]


So putting it all together you would end up with something like this

<!-- language: Java -->

@RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> updateUser(@RequestBody User user) {

User existing = userRepository.read(user.getId());
copyNonNullProperties(user, existing);
userRepository.save(existing);

// ...
}

public static void copyNonNullProperties(Object src, Object target) {
BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}

public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

Set<String> emptyNames = new HashSet<String>();
for(java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
Reply

#5
If you are reading request as JSON String, this could be done using Jackson API. Here is code below. Code compares an existing POJO Elements and create new one with updated fields. Use the new POJO to persist.

public class TestJacksonUpdate {

class Person implements Serializable {
private static final long serialVersionUID = -7207591780123645266L;
public String code = "1000";
public String firstNm = "John";
public String lastNm;
public Integer age;
public String comments = "Old Comments";

@Override
public String toString() {
return "Person [code=" + code + ", firstNm=" + firstNm + ", lastNm=" + lastNm + ", age=" + age
+ ", comments=" + comments + "]";
}
}

public static void main(String[] args) throws JsonProcessingException, IOException {
TestJacksonUpdate o = new TestJacksonUpdate();

String input = "{\"code\":\"1000\",\"lastNm\":\"Smith\",\"comments\":\"Jackson Update WOW\"}";
Person persist = o.new Person();

System.out.println("persist: " + persist);

ObjectMapper mapper = new ObjectMapper();
Person finalPerson = mapper.readerForUpdating(persist).readValue(input);

System.out.println("Final: " + finalPerson);
}}

Final output would be, Notice only lastNm and Comments are reflecting changes.

persist: Person [code=1000, firstNm=John, lastNm=null, age=null, comments=Old Comments]
Final: Person [code=1000, firstNm=John, lastNm=Smith, age=null, comments=Jackson Update WOW]
Reply

#6
You are able to write something like



@Modifying
@Query("update StudentXGroup iSxG set iSxG.deleteStatute = 1 where iSxG.groupId = ?1")
Integer deleteStudnetsFromDeltedGroup(Integer groupId);

Or If you want to update only the fields that were modified you can use annotation

@DynamicUpdate

Code example:

@Entity
@Table(name = "lesson", schema = "oma")
@Where(clause = "delete_statute = 0")
@DynamicUpdate
@SQLDelete(sql = "update oma.lesson set delete_statute = 1, "
+ "delete_date = CURRENT_TIMESTAMP, "
+ "delete_user = '@currentUser' "
+ "where lesson_id = ?")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
Reply

#7
For long,int and other types;
you can use the following code;

if (srcValue == null|(src.getPropertyTypeDescriptor(pd.getName()).getType().equals(long.class) && srcValue.toString().equals("0")))
emptyNames.add(pd.getName());
Reply

#8
> skip all null values when save the object

As others have pointed out, there is not straight forward solution in JPA.

But thinking out of the box you can use MapStruct for that.

This means you use the right `find()` method to get the object to update from the DB, overwrite only the non-null properties and then save the object.

You can use JPA as you know and just use MapStruct like this in Spring to update only the non-null properties of the object from the DB:
```
@Mapper(componentModel = "spring")
public interface HolidayDTOMapper {

/**
* Null values in the fields of the DTO will not be set as null in the target. They will be ignored instead.
*
* @return The target Holiday object
*/
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
Holiday updateWithNullAsNoChange(HolidayDTO holidayDTO, @MappingTarget Holiday holiday);

}
```

See [the MapStruct docu on that][1] for details.

You can inject the `HolidayDTOMapper` the same way you do it with other beans (`@Autowired`, Lombok,...) .

[1]:

[To see links please register here]

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through