I'm still looking for a update method in Spring's Data JPA to update a given Object persited in a relational database. I only found solutions in which I'm forced to specify some kind of UPDATE queries via #Query annotation (in comparison with #Modifying), for example:
#Modifying
#Query("UPDATE User u SET u.firstname = ?1, u.lastname = ?2 WHERE u.id = ?3")
public void update(String firstname, String lastname, int id);
For building the Query, I also have to pass single parameters instead of whole Objects. But that's exactly what I want to do (passing whole Objects).
So, what I'm trying to find is a method like this:
public void update(Object obj);
Is it possible to build such a update method based on Spring Data JPA? How must it be annotated?
Thank you!
If the goal is to modify an entity, you don't need any update method. You get the object from the database, modify it, and JPA saves it automatically:
User u = repository.findOne(id);
u.setFirstName("new first name");
u.setLastName("new last name");
If you have a detached entity and want to merge it, then use the save() method of CrudRepository:
User attachedUser = repository.save(detachedUser);
If you want to update an Entity you do not need JPQL query(Optional). You can directly use (old version)findOne or (new version) findById to access data and make required modifications
Ex- here approve (entity ) is updated.
Optional<User> user= Repository.findById(id);
Registration reg = reg.get();
reg.setApproved("yes");
userRepo.save(reg);
And That's It.
These answers aren't addressing the question, which is how to avoid all of the messy...
if (var1 != null) u.setVar1( var1 );
if (var2 != null) u.setVar2( var2 );
....
if (varN != null) u.setVarN( varN );
shenanigans.
So, in essence, the question involves how to merge an object. Unfortunately, JPA "merge" is a misnomer. It's re-attaching a detached object.
Have you tried using this scheme:
add jackson data-bind dependency
convert update object (source) to Map
Map<String, Object> sourceMap = objMapper.convertValue(source, Map.class);
remove null values using Java 8 streams
convert to-be-updated object (target to Map) as above
use new Java 8 Map merge method to update target with source, i.e.
targetMap.merge( ... );
then use objMapper to convert targetMap back to target type.
One caveat, now you have an updated entity that's not JPA-attached so you'd have to do a target.merge();
Related
I'm working on a project in which I'm using MongoDB with the Spring boot framework, and I'm trying to make a single API endpoint to create and update the Entity/Documents (if id is provided do update, else insert), the endpoint is working as expected but the problem I'm facing is when updating, i have to send all the fields in the payload otherwise save method changes the fields which are not provided to null, I understand that this is caused when we are not providing fields in the payload,
Entity class sets the value of the missing fields to null by default (unless a default value is not provided),
i saw in the internet that we can use mongoTemplate and provide Update definition as follows,
Document document = new Document();
mongoTemplate.getConverter().write(myEntity, document);
Update update = new Update();
update.set("field", "value");
Query query = new Query(Criteria.where("fieldTwo").is(myEntity.getFieldTwo()));
UpdateResult id = mongoTemplate.upsert(query, update, MyEntity.class);
but here, since the fields to be updated are fully dynamic, i cannot set the update definition.
so what i have done so far is, if id is provided, query for the existing record, and loop though the payload, and check whether any fields have null values if there are copy the corresponding value from the existing record as follows
if (newEntity.get_id() != null) {
// update
MyEntity curEntity = myEntityService.findById(newEntity.get_id());
Field[] fields = newEntity.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // ===> here
Object o = field.get(newEntity);
if (o == null) {
field.set(newEntity, field.get(curEntity));
}
}
}
and now it works and fulfills what I was trying to achieve, my first question is, is there any other proper way to achieve this? I have worked on 50+ MERN and MEAN projects but in nodejs world with mongoose, these kinds of checks/implementations are unnecessary.
And my second question is, all the fields in the entity class are private, so I had to add field.setAccessible(true); in order to read the values, is it a safe thing to do?
I have a requirement where I need to replace mysql with Redis, so I have method for mysql in JPA as findByIdAndName.
And in Redis I am storing key and Object like <Integer,Employee> (Employee is a class and Integer is Id ) so If I want to get employee object from redis based on Integer-Id I can easily get those with findById mentioned below but what if I want to get data based on Employee Name and age so any Idea how to use hashOperation in that way or how to store data in redis in way so that I can get the desired result.
For Example in RedisImplementation:
public Employee findById(Integer id) {
Employee emp = redisTemplate.opsForHash().get("EMPLOYEE_HASH",Id);
}
I want to add a method which can get data based on ID and Name like findByIdAndName
You should use #Cacheable as it enables you to define key with EL expressions.
An example is the following :
#Cacheable(value = "items", key = "#id")
public Item getItem(Integer id) {
Item item = itemRepository.findById(id).orElseThrow(RuntimeException::new);
logger.info("Loading data from DB {}", item);
return item;
}
I am pretty sure #Cacheable supports composite keys like POJOS instead of primitives as keys. Also if you do not specify key explicitly it will take the arguments of the method that has the annotation as a key.
A really good documentation / tutorial is the following one https://springhow.com/spring-boot-redis-cache/
Hope I helped :)
I'm trying to implement a method for updating a database record. So far, I created this one:
public Optional<User> update(final Integer id,final UpdateUserDto dto) {
userRepository.findById(id).ifPresent((user -> {
user.setShop((dto.getShopId() == null) ? null : shopRepository.findById(dto.getShopId())
.orElseThrow(ShopNotFoundException::new));
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setRoles(Arrays.asList(
roleRepository.findByRoleName(dto.getRole()).orElseThrow(RoleNotFoundException::new)
));
}));
return userRepository.findById(id);
}
But now I added two more fields to my user entity (activated, productAllowed) and I must enhance my update method to make them updatable. I can do that, but I have other entities also and if I change them it will be a lot of maybe boilerplate code.
Is there any kind of best practice to do this in a better way, or I just need to keep setting all the fields manually?
I was also thinking about reflection, but in that case I have a few fields that cannot be copied exactly from the DTO to the entity (e.g. the shop field, which is queried from database, or role field).
And I also don't think that another query for returning the new object is effective, but although I set the properties in a service layer, the original findById()'s returned user is wrapped inside an Optional, so I don't think it will be updated.
Thank you in advance.
I have a field in my SQL table which needs to be updated by one and return a unique ID. But looks like it is not being updated on the latest data, especially when I give a lot of requests.
#Transactional
public interface CompanyRepository extends CrudRepository<Company, Integer> {
#Modifying(clearAutomatically = true, flushAutomatically = true)
#Query(value = "update Company c set c.accessId = :accessId WHERE c.id = :companyId AND c.accessId = :oldAccessId", nativeQuery = true)
int updateAccessId(#Param("companyId") Integer companyId, #Param("accessId") Integer accessId, #Param("oldAccessId") Integer oldAccessId);
}
Even with both clearAutomatically and flushAutomatically set to true, it is not working on the latest data.
I could see two update query being successful both with oldAccessId as the same.
Should the table design be changed?
PS : I have tried without nativeQuery = true as well.
What you have here is a classical race condition.
Two threads read the same entity, with identical accessId, increment it by one and then writing the result using the method you show in your question. Resulting in effectively only one update.
There are various ways how to fix this.
Use JPA and optimistic locking.
Assuming you have an attribute with #Version annotated you can do the
following in a single transactional method:
Load the entity.
increment the accessId.
persist the entity.
If another transaction tries to do the same on the same entity one of the two will get an exception. In that case retry until the update goes through.
Use the database.
Make reading and updating atomic in the database. Instead of passing the new value as parameter use a query like this one:
update Company c
set c.accessId = c.accessId + 1
WHERE c.id = :companyId
Make it a version attribute.
As mentioned above JPA already has #Version attributes which get updated on every change. Depending on the exact requirements you might be able to make accessId simply that attribute and get it updated automatically.
Check if any rows got updated.
Based on your comment your intention was to basically reimplement what JPA does with version attributes. If you do this you are missing a crucial part: checking that the update actually updated anything, by comparing the return value against 1.
How can I create update methods in spring CurdRepository?
Something like:
interface PersonRepository extends CrudRepository<PersonEntity> {
//update ... set age =: age where name =: name
boolean setAgeByName(age, name);
}
If you ask me: do not use such queries. That's because your second level cache gets eradicated and you get some performance problems (your application will be slightly slower).
And by the way the process you want to do is in reality the following:
you load your entity from the database
change the attribute on the loaded object (via a setter method)
you save your entity to the database (this is done automagically when your transaction ends so you do not need to call PartnerRepository.save(entity) explicitly)
If you want to use a query then I suggest you write your query as you mentioned in your question:
#Modifying
#Query("update Person p set p.age = :age where p.name = :name")
int setAgeByName(#Param("age") int age, #Param("name") String name);
And you get back how many entries have been modified.