How to update only particular fields in an entity using springbooot JPA update API - java

Here is my code.
Entit class:
#Entity
public class Book
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long bookId;
private double bookPrice;
private String bookTitle;
private String bookAuthor;
private String bookLanguage;
private LocalDate publicationDate;
private String publisher;
private byte[] bookImage;
private long isbn;
private int bookQuantity;
Controller class:
#PutMapping("/updatebooks")
public ResponseEntity<ApiResponse> updateBook(#RequestBody BookDto bookDto)
throws DataNotFoundException
{
return ResponseEntity.ok(bookService.updateBook(bookDto));
}
Service class:
#Override
public ApiResponse updateBook(BookDto bookDto) throws DataNotFoundException
{
Book book = bookRepository.findById(bookDto.getBookId())
.orElseThrow(() -> new DataNotFoundException("Book not found"));
book.setBookAuthor(bookDto.getBookAuthor());
book.setBookLanguage(bookDto.getBookLanguage());
book.setBookPrice(bookDto.getBookPrice());
book.setBookTitle(bookDto.getBookTitle());
book.setIsbn(bookDto.getIsbn());
book.setPublicationDate(bookDto.getPublicationDate());
book.setPublisher(bookDto.getPublisher());
bookRepository.save(book);
return new ApiResponse(HttpStatus.OK.value(), "Updation successful");
}
So through postman I just want to update bookAuthor field alone and other fields has to be same as it is in the database. But when I update just one field the others are automatically assigned as null and I just want to update only one field.
Here see that i'm just updating the bookAuthor field but others are simply changing to null. So how can I update only particular fields and display the others as as it is in database.
Pre Updation DB:
Post Updation DB:

This is not really springboot jpa problem. The issue is in the way updateBook method in service has been implemented. The updateBook method is setting all the fields from dto on persisted book entity without checking if the dto.get* returns null or not.
Very simply but crude solution to this problem would be to have if(dto.get* != null) check for each field before setting the value in book entity. i.e.
if (bookDto.getBookAuthor() != null)
book.setBookAuthor(bookDto.getBookAuthor());
if (bookDto.getBookLanguage() != null)
book.setBookLanguage(bookDto.getBookLanguage());
if (bookDto.getBookPrice() != 0)
book.setBookPrice(bookDto.getBookPrice());
...
This leaves the updateBook service method generic to allow updating one or more fields without worrying about others being overwritten. If this makes your service method too noisy the dto to entity conversation part can be extracted into its own method for clarity.
For more advance usecases and if your entity/dto has more than a handful fields or nested objects as fields then this becomes cumbersome. In such scenarios you may want to handcraft a separate implementation which perhaps uses reflection to map fields from dto to entity if the field value is not null. The usage of reflection is however likely to be slow and/or error prone if not done properly.
There are however libraries and frameworks for more regular usecases which makes such conversation easier. At the simplest springframework's BeanUtils.copyProperties method provides a way to map values from one object to another and the last argument of ignored properties can be used to provide fields names to be ignored. This stackoverflow answer shows how to write a generic method that creates a list of property names to ignore if they are null in source object; and they uses this list to pass as parameter in BeanUtils.copyProperties.
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for(PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
return emptyNames.toArray(new String[0]);
}
then in your service method;
Book book = bookRepository.findById(bookDto.getBookId())
.orElseThrow(() -> new DataNotFoundException("Book not found"));
BeanUtils.copyProperties(bookDto, book, getNullPropertyNames(bookDto));
bookRepository.save(book);
For further advanced usecases you can use frameworks such as mapstruct or modelmapper.

Don't get the fields from the DTO. Just Do findByID, set the new bookAuthor to the entity and save.

#Modifying
#Query(nativeQuery = true, value = "update book set book_author = :bookAuthor where id = :bookId")
int updateBookAuthor(#Param("bookAuthor") String bookAuthor, #Param("bookId") Long bookId);
You can do something like this in your BookRepository, then invoke from the service class like...
bookRepository.updateBookAuthor(bookAuthor, bookId)
Or modify your service class method like the following...
#Override
public ApiResponse updateBook(BookDto bookDto) throws DataNotFoundException
{
Book book = bookRepository.findById(bookDto.getBookId())
.orElseThrow(() -> new DataNotFoundException("Book not found"));
book.setBookAuthor(bookDto.getBookAuthor());
bookRepository.save(book);
return new ApiResponse(HttpStatus.OK.value(), "Updation successful");
}

Related

Return empty collection when this collection is lazy intitialized

I have this object:
Entity
#Entity
public class someClass{
private String name;
private String labelKey;
#ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE}, fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<>();
}
DTO
public class someClass{
private String name;
private String labelKey;
private Set<Product> products = new HashSet<>();
}
My problem is that when I get this object but products are lazy initialized, when I mapp entity to DTO using Dozer, I get a LaziInitializedException, then i want to get that when I get products lazy initialized, this products will return a empry Set.
Is this possible?
Thanks for your time and sorry for my english, it's not my native language.
As you can see in this tutorial here you can instruct dozer to exclude some field from the mapping.
If you do so, then the dozer will not invoke the method of getProducts of your entity class and therefore the exception LaziInitializedException will not be thrown.
At the same time because your DTO object is initialized with an empty HashSet for the field products, this is what will remain at the end in the DTO.
So your requirement will work, where your entity is lazily initialized for products and your DTO returns an empty list while at the same time the mapping happens from dozer.
Here is the configuration that you need for the mapper of dozer.
BeanMappingBuilder mappingExclusion = new BeanMappingBuilder() {
#Override
protected void configure() {
mapping(SomeClassEntity.class, SomeClassDto.class).exclude("products");
}
};
mapper = new DozerBeanMapper();
mapper.addMapping(mappingExclusion);
Then you can use it to do the mapping as following
mapper.map(someClassEntityInstance, someClassDtoInstance);
You could create/modify your Getter such that:
public Set<Product> getProducts() {
if (products == null) {
return new HashSet<>();
//or products = new HashSet<>(), but I'm not sure of the side effects as far as database framework is concerned.
}
return products;
}
Try marking your service class or method as #Transactional to let Spring handle session management.
public class ServiceUsingSomeClass {
final SomeClassRepository someClassRepository;
//Constructor ...
#Transactional
showProducts() {
someClassRepository.findAll();
// Do something with Set<Product>
}
}
If you only want to avoid fetching the association in cases where you use Dozer for DTO mapping, you could configure it to ignore products field in source object by extending DozerConverter and using that custom converter.
I also feel that maybe that means your target type doesn't really need to have
a products field to begin with, since you're not going to populate it.
If there's many places like this in your codebase, consider using projections to only fetch the properties necessary for the purpose at hand.
#fella7ena brings up a point about #Transactional, however this is actually unrelated - you can still come across LazyInitializationException within a transaction. This happens because Hibernate loses track of the relation between the java bean's persistence state and the database state. If you actually wanted to fetch products association from the database, you would have to use eager fetchtype (leads to n+1 issue), batching, or entitygraphs.

Update Specific Fields with Spring Data Rest and MongoDB

I'm using Spring Data MongoDB and Spring Data Rest to create a REST API which allows GET, POST, PUT and DELETE operations on my MongoDB database and it's all working fine except for the update operations (PUT). It only works if I send the full object in the request body.
For example I have the following entity:
#Document
public class User {
#Id
private String id;
private String email;
private String lastName;
private String firstName;
private String password;
...
}
To update the lastName field, I have to send all of the user object, including the password ! which is obviously very wrong.
If I only send the field to update, all the others are set to null in my database. I even tried to add a #NotNull constraints on those fields and now the update won't even happens unless I send all of the user object's fields.
I tried searching for a solution here but I only found the following post but with no solution: How to update particular field in mongo db by using MongoRepository Interface?
Is there a way to implement this ?
Spring Data Rest uses Spring Data repositories to automatically retrieve and manipulate persistent data using Rest calls (check out https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference).
When using Spring Data MongoDB, you have the MongoOperations interface which is used as a repository for your Rest endpoints.
However MongoOperations currently does not supports specific fields updates !
PS: It will be awesome if they add this feature like #DynamicUpdate in Spring Data JPA
But this doesn't mean it can be done, here's the workaround I did when I had this issue.
Firstly let me explain what we're going to do:
We will create a controller which will override all the PUT operations so that we can implement our own update method.
Inside that update method, we will use MongoTemplate which do have the ability to update specific fields.
N.B. We don't want to re-do these steps for each model in our application, so we will retrieve which model to update dynamically. In order to do that we will create a utility class. [This is optional]
Let's start by adding the org.reflections api to our project dependency which allows us to get all the classes which have a specific annotation (#Document in our case):
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
Then create a new class, called UpdateUtility and add the following methods and also replace the MODEL_PACKAGE attribute with your own package containing your entities:
public class UpdateUtility {
private static final String MODEL_PACKAGE = "com.mycompany.myproject.models";
private static boolean initialized = false;
private static HashMap<String, Class> classContext = new HashMap<>();
private static void init() {
if(!initialized) {
Reflections reflections = new Reflections(MODEL_PACKAGE);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Document.class); // Get all the classes annotated with #Document in the specified package
for(Class<?> model : classes) {
classContext.put(model.getSimpleName().toLowerCase(), model);
}
initialized = true;
}
}
public static Class getClassFromType(String type) throws Exception{
init();
if(classContext.containsKey(type)) {
return classContext.get(type);
}
else {
throw new Exception("Type " + type + " does not exists !");
}
}
}
Using this utility class we can retreive the model class to update from it's type.
E.g: UpdateUtility.getClassFromType() will returns User.class
Now let's create our controller:
public class UpdateController {
#Autowired
private MongoTemplate mongoTemplate;
#PutMapping("/{type}/{id}")
public Object update(#RequestBody HashMap<String, Object> fields,
#PathVariable(name = "type") String type,
#PathVariable(name = "id") String id) {
try {
Class classType = UpdatorUtility.getClassFromType(type); // Get the domain class from the type in the request
Query query = new Query(Criteria.where("id").is(id)); // Update the document with the given ID
Update update = new Update();
// Iterate over the send fields and add them to the update object
Iterator iterator = fields.entrySet().iterator();
while(iterator.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) iterator.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
update.set(key, value);
}
mongoTemplate.updateFirst(query, update, classType); // Do the update
return mongoTemplate.findById(id, classType); // Return the updated document
} catch (Exception e) {
// Handle your exception
}
}
}
Now we're able to update the specified fields without changing the calls.
So in your case, the call would be:
PUT http://MY-DOMAIN/user/MY-USER-ID { lastName: "My new last name" }
PS: You can improve it by adding the possibility to update specific field in a nested objects...

Updating with Morphia Optimistic locking

Hi considering the following example:
Resource:
#PUT
#Path("{id}")
public Response update(#PathParam(value = "id") final String id, final Person person) {
final Person person = service.getPerson(id);
final EntityTag etag = new EntityTag(Integer.toString(person.hashCode()));
// If-Match is required
ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder != null) {
throw new DataHasChangedException("Person data has changed: " + id);
}
service.updatePerson(id, person.getName());
....
}
Service:
public void updatePerson(final String id, final String name) {
final Query<Person> findQuery = morphiaDataStore.createQuery(Person.class).filter("id ==", id);
UpdateOperations<Person> operation = morphiaDataStore.createUpdateOperations(Person.class).set("name", name);
morphiaDataStore.findAndModify(findQuery, operation );
}
Person:
#Entity("person")
public class Person {
#Id
private ObjectId id;
#Version
private Long version;
private String name;
...
}
I do check if the etag provided is the same of the person within the database. However this check is been done on the resource itself. I don't think that this is safe since the update happens after the check and another thread could have gone threw the check in the meantime. How can this be solved correctly? Any example or advise is appreciated.
Morphia already implements optimistic-locking via #Version annotation.
http://mongodb.github.io/morphia/1.3/guides/annotations/#version
#Version marks a field in an entity to control optimistic locking. If the versions change in the database while modifying an entity (including deletes) a ConcurrentModificationException will be thrown. This field will be automatically managed for you – there is no need to set a value and you should not do so. If another name beside the Java field name is desired, a name can be passed to this annotation to change the document’s field name.
I see you have already use the annotation in your example. Make sure the clients include the version of the document as part of the request so you can also pass it to morphia.
Not sure if findAndModify will be able to handle it (I would think it does). but at least I'm sure save does handle it.
Assuming the object person contains the new name and version that the client was looking at, you can do directly something like this to update the record:
morphiaDataStore.save(person);
If there was another save before this client could pick it up the versions will no longer match and a ConcurrentModificationException will be issued with this message:
Entity of class %s (id='%s',version='%d') was concurrently updated

JPA: update only specific fields

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"?
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:
Read the existing object
Use BeanUtils to copy values
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:
How to ignore null values using springframework BeanUtils copyProperties?
So putting it all together you would end up with something like this
#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);
}
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();
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"})
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]
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 for details.
You can inject the HolidayDTOMapper the same way you do it with other beans (#Autowired, Lombok,...) .
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:
http://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/
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());

Spring JPA - Best way to update multiple fields

I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?
EDIT:
How would I transform this code from my DAO to something equivalent in JPA?
public void updateFields(String userId, Map<String, String> fields) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append("='");
sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
sb.append("', ");
}
String str = sb.toString();
if (str.length() > 2) {
str = str.substring(0, str.length() - 2); // remove ", "
String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
jdbcTemplate.update(sql, new Object[] { userId },
new int[] { Types.VARCHAR });
}
}
You have to read more about JPA for sure :)
Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.
If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.
In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).
update
After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())
Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);
Root e = update.from(User.class);
for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
if (map.containsKey(attr.getName())) {
update.set(attr, map.get(attr));
}
}
update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();
Please note that Update Criteria Queries are available in JPA since 2.1 version.
Here you can find more informations about metamodel generation.
Alternatively to metamodel you can just use java reflection mechanisms.
JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.
In case you are using Hibernate(as JPA provider), here's an example
Entity
#Entity
#Table(name="PERSON")
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="NAME", nullable=false)
private String name;
other fields....
}
DAO
public interface PersonDao {
Person findById(int id);
void persist(Person person);
...
}
DaoImpl
#Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {
public Person findById(int id) {
return (Person) getSession().get(Person.class, id);
}
public void persist(Person person){
getSession().persist(person);
}
}
Service
#Service("personService")
#Transactional
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Override
public void createAndPersist(SomeSourceObject object) {
//create Person object and populates with the source object
Person person = new Person();
person.name = object.name;
...
personDao.persist(person);
}
#Override
public Person findById(int id) {
return personDao.findById(id);
}
public void doSomethingWithPerson(Person person) {
person.setName(person.getName()+" HELLO ");
//here since we are in transaction, no need to explicitly call update/merge
//it will be updated in db as soon as the methods completed successfully
//OR
//changes will be undone if transaction failed/rolledback
}
}
JPA documentation are indeed good resource for details.
From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.

Categories