Is there a way to use UPDATE query with dynamic attributes in Spring Framework? - spring

I'm developing a REST server application using Spring Boot.
Just got a question while constructing an UPDATE query.
Currently my UPDATE query in UserRepository is like this;
#Modifying
#Transactional
#Query(value ="update User u set u.user_dob=:userDOB, u.user_lastname=:userLastName, u.user_firstname=:userFirstname, u.user_streetaddress=:userStreetAddress where d.driver_id=:driverId", nativeQuery = true)
void updateUser(#Param("userDOB") String userDOB, #Param("userLastName") String userLastName, #Param("userFirstName") String userFirstName, #Param("userStreetAddress") String userStreetAddress);
However, I don't like to list all the attributes of User in one UPDATE query.
Is there anyway to construct UPDATE query dynamically?
For example;
Update with
set u.user_dob=:userDOB, u.user_lastname=:userLastName, u.user_firstname=:userFirstname, u.user_streetaddress=:userStreetAddress
or
u.user_lastname=:userLastName, u.user_firstname=:userFirstname
using one update method.

If you are using Spring Data JPA (seems you do), your repository interface is probably extending JpaRepository interface.
In this case, you could simply use save method.
Here are some good examples:
http://www.springbyexample.org/examples/spring-data-jpa-repository.html
http://www.springbyexample.org/examples/spring-data-jpa-code-example.html

Related

Spring data update just one entity field

i’m new to spring when i try to update just one field of an entity I noticed in logs that hibernate perform two queries, before update it does a SELECT of all fields. Is that ok? Why does Hibernate perform that SELECT? How can i update a field with just one UPDATE query? Additionally when I tried to update a single title in an entity that has another nested entity i end up with a bunch of SELECT. I think it’s not good for performance or I’m wrong?
Something s = somethingRepository.findById(id);
s.setField1(someData);
somethingRepository.save(s);
On the internet I found a solution to make custom query with #Modifying and #Query(“UPDATE …”) but in this way I need to make custom query for every single field. Is there a better solution?
As per the source code you have pasted in the question
Something s = somethingRepository.findById(id);
s.setField1(someData);
somethingRepository.save(s);
if the entity Something which you are asking does not exist in the hibernate first level cache, it will make one SELECT call.
and as you are updating field 1 value then it will make another update call.
It does not matter if you are using save or not because Hibernate dirty checks will ensure that all changes are updated.
otherwise you can use custom method with #Modifying with JPQL and named params. It is more readable than ?1,
#Modifying
#Query("UPDATE Something s SET s.field = :fieldValue WHERE s.id = :id")
void updateField(String fieldValue, UUID id);
Regarding that you are seeing multiple calls "when I tried to update a single title in an entity that has another nested entity". It depends on how you have created the relationship among entities. If you can share the entities and their relationship then only it can be answered accurately.
Because internally the repository.save() method does an upsert. If you go to the inner classes and check you will see that first, it will check whether the entity is present in the database then based on that it will perform add or update. If you don't want the SELECT query to run then you can use the native query offered by the JPA repository. You can do something like this:
#Modifying
#Transactional
#Query("UPDATE <tableName> SET <columnName> = ?1 WHERE <condition>" ,nativeQuery=true)
void updateSomething(String value);

How to create a dyncamic update query with JPQL?

I have to update an entity by ensuring it's already there in the database(Primary key is not auto generated).Therefor I cannot use only thesave() method to overcome this issue.There must be a way to ensure that the inserted primary key is already existing.To overcome this issue I know I can follow several approaches like below.But I found creating my own update statement is the most optimal way to solve this problem.
So I need to create a dynamic update query with JPQL . I know you will say why don't you use a method like getOne() or findOne() and set the necessary fields for the entity and save() .But I think using a findOne() or getOne() leads to an additional db hit to fetch the relevant entities .So I can omit the fetch by going with a custom update query.
I guess using #DynamicUpdate also won't resolve the problem ,because as far as I know it's also fetch the entity from the database to compare the changed fields during an updation.
So both of the above mentioned approaches leads to an additional db hit.
So is there a way to write a custom jpql query to update only the fields which are not null.
I have achieved similar kind of behaviour with fetching by writing dynamic where clauses.But didn't find a way to do the same with update.
Ex for dynamic select with JPQL:
SELECT d FROM TAndC d WHERE (:termStatus is null or d.termStatus= :termStatus ) AND (:termType is null or d.termType=:termType) AND (:termVersion is null or d.termVersion=:termVersion)"
#Modifying
#Query("update entity e set e.obj1=:obj1 and e.obj2=:obj2 where e.id=:id")
public void updateCustom(String obj1, String obj2, String id);
I wrote a pseudo code.
It is possible with spring data.
Also you can use jpa entity manager to create update statement and dynamic query programatically

Spring Data JPA + Bytecode Enhancement

Is it possible to load #*ToOne attributes eagerly using JPA interface(Entity Graphs) which are set lazy using #LazyToOne , #LazyGroup in the parent entity class and enabled bytecode enhancement ? I am trying to load such attributes eagerly using entity graph but it is firing another query for such #*ToOne attributes when an parent entity is queried.
Trying to have another way to override static fetch type in entity classes including #LazyToOne which was added with bytecode enhancement.
Using Spring 5.1.3 , Spring JPA 2.2 , Hibernate 5.4.19
Update : Data JPA is working as expected and i could see joins for the attributes which i am trying to fetch eagerly but those lazy attributes are not being initialised with the join query response and hibernate causing each query on referencing attributes which were annotated with #LazyToOneOption.NO_PROXY and was already fetched eagerly using entity graph in my repository.
How can i avoid this second select which is not even required since i got the that data eagerly from entity graph in JPA respository ??
Any help would be highly appreciated.
Entity Graphs just like Hibernate fetch profiles apply regardless of what annotations you have on the association. If it does not, maybe there is a bug in Spring Data or maybe even Hibernate. It's probably best if you create a new JIRA issue with a test case reproducing the problem.
Having said that, I think this is the perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
An example DTO model could look like the following with Blaze-Persistence Entity-Views:
#EntityView(User.class)
public interface UserDto {
#IdMapping
Long getId();
String getName();
Set<RoleDto> getRoles();
#EntityView(Role.class)
interface RoleDto {
#IdMapping
Long getId();
String getName();
}
// Other mappings
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserDto a = entityViewManager.find(entityManager, UserDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Expose custom query in Spring Boot Rest API with multiple joins

I have an Spring REST Api and a MySQL Database, now I would like to expose the result of an custom query with multiple joins.
I have tried multiple suggestions that I found online but none of them were working for me so far.
What I want to do is something like a read only DTO that has all the fields of my custom query so that in the end I have one api page exposing the DTO data as JSON so my client (Angular) can read the data from there.
I already tried to:
create an #RestController with an injected EntityManager that executes a NativeQuery and then populates the DTO with the returned data but since my DTO is no Entity I get an Hibernate Mapping Exception
create a custom Repository and its Impl but with a similar outcome
place the Query inside an existing #Entity that is part of the Query statement
What am I missing here? Do I have to annotate my DTO maybe? Cuttently it's just a POJO, I think the #Entity annotation is not the right thing here since I don't want a Table created from my DTO.
Fixed it by letting the Query return an Array of type Object and afterwards mapping it to the DTO Constructor.

Spring Hibernate : Multiple resultset mapping

I want to understand the limitations of Spring's Data repository.
While querying the database, it seems that Spring repository can only return entities, or a collection of same type, like string/int etc. It makes sense because the Spring Repository is a function and a function can only return one result.
So what if I need to execute a complexe sql by using #Query annotation, and expect more than one result? like a collection of entityies and a number.
I don't think it is possible with Spring Repository, so if i'm wrong, please correct me.
And more importantly, how could I do that by using spring?
No, it's not possible that I know of for the Repository to work with queries, but the Repository is used by a Spring ServiceImpl anyway and you can Inject an EntityManager into the serviceImpl and use that. For example see Getting started with Spring Data JPA:
#PersistenceContext
private EntityManager em;
TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class);
query.setParameter(1, customer);
return query.getResultList();

Resources