Partial update without retrieving the object from db - spring-boot

I am looking for some way to partially update an object without retrieving the object from DB.
Say I have an Employee entity containing the following properties -
firstName
lastName
phone
age
salary
The JSON I get in the update request may not contain all the properties. I need to make sure I update only the properties provided in the request and leave all other data unchanged.
I explored some ways of achieving partial update but all of them involves retrieving the data from db. I don't have this option since the db in my case is too slow and this will increase the response time. Please suggest

You can combine #Modifying and #Query annotation to issue an update query
#Modifying
#Query("update Employee e set e.firstName = :firstName, e.lastName = :lastName where e.id = :id")
void updateEmployeePartially(Long id, String firstName, String lastName);
For more information, you can check this article

Related

Can Spring R2DBC execute batch insert and update at the same time?

Can Spring R2BC save and update a batch at the same time?
I get a list of users (1 million rows from a file for example). Some of these users are new and I need to INSERT them to the table, and some need to be UPDATED due to changed data. It is not clear from the file which are new and which are not.
I'm considering user_id as the primary key
How can I describe this logic in code using Spring R2DBC?
It a little depend how much work you would like to do :)
Select then insert/update
The first way is quite simple to understand, but suboptimal in case of number of queries/database interactions.
#Transactional
Mono<User> save(User user){
return repository.findById(user.getId())
.flatMap(found -> repository.save(found.copyMutableValuesFrom(user)))
.switchIfEmpty(repository.save(user));
}
In first step you try to find user by id, then update (with fields rewrite in copyMutableValuesFrom) or insert.
Upsert
Second way is to use custom query:
interface UserRepository extends ReactiveCrudRepository<User, Long> {
#Modifying
#Query("""
INSERT INTO user (id, firstname, lastname) VALUES(1, :firstname, :lastname)
ON DUPLICATE KEY
UPDATE firstname=:firstname, lastname=:lastname
""")
Mono<Integer> maybeInsertMaybeUpdate(Long id, String firstname, String lastname);
}
This way limit number of queries, but strongly depends on database. Above query is for mySQL, but postgres version looks like:
INSERT INTO user (id, firstname, lastname) VALUES(1, :firstname, :lastname)
ON CONFLICT(id)
DO
UPDATE SET firstname=:firstname, lastname=:lastname
Batch
As in comment. You need to use construction like this:
Flux<Long> insertProductColors(List<User> users){
if (users.isEmpty()) return Flux.empty();
return databaseClient.inConnectionMany { connection ->
val statement = connection.createStatement(insertOrUpdateUserQuery)
users.forEach(user ->
statement.bind(0, user.id).bind(1, user.firstName, user.lastname).add()
);
return statement.execute().toFlux().flatMap( result ->
result.map ( row, meta -> row.get("id", Long.class) )
)
}
}
I'm not sure everything here, because I get some of my code in kotlin and translated it here, but general idea is probably clear.

SpringData JPA: Query with collection of entity as parameter

I have a list of entities on which I want to perform an update, I know I could update the table with list of String/Integer.. etc as the parameter with something like
#Query("update tableName i set i.isUpdated = true where i.id in :ids")
void markAsUpdated(#Param("ids") List<Integer> itemIds);
I'm trying to avoid repeated conversion of list of entities to list of Ids for making the query in DB. I know there are deleteAll and deleteInBatch commands which accept parameter as list of entities.
How do I do this in JPA Query, I tried the following but it didn't work yet.
#Modifying(flushAutomatically = true, clearAutomatically = true)
#Query("update tableName i set i.updated = true where i in :items")
void markAsUpdated(#Param("items") List<Item> items)
The query needs ids, it doesn't know how to deal with entities.
You have multiple options:
Just pass ids to the method, the client is responsible for extracting ids.
Pass entities and use SpEL for extracting ids
As suggested in the comments use a default method to offer both APIs and to delegate from one to the other.
As for the question that came up in the comments: You can move the method for extracting the id into a single method by either have relevant entities implement an interface similar to this one:
interface WithId {
Long getId();
}
Or by passing a lambda to the method, doing the conversion for a single entity:
List<ID> extractIds(List<E> entities, Function<E, ID> extractor) {
// ...
}

How to Return all instances of the type with the given ID in JPA SpringBoot?

I'm trying to return (or fetch) all the records from the database based on an ID provided by me. I'm using JPA and i'm aware of findAll() method but it returns all the records without any criteria, I created a custom query and it is only returning a unique value from the table but i want to return all records based on a criteria.
For example, findAllByUserID(String UserID) method should return all the records based on that UserID not just one.
I'd appreciate any help or suggestion.
Thanks
Have a look at the doc. There you will find the keywords you can use to declare methods in repository interfaces that will generate the according queries:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
In your case: If userID is an attribute of your entity you can add a method
List<YourEntity> findByfindAllByUserID(String userId)
to your repository interface.
First, make sure that you're not using any aggregate function in your select query such as DISTINCT()
Then make sure that the the method which is implementing that query is returning a List of you're desired result.
here's how it should look :
#Query("select t from table t where t.code = ?1")
List<Result> findAllByUserID(String UserID);

Incorrect derived query for byId in Spring Data Neo4j

I have two entities: User and Connection, along with two appropriate repositories. Both entities has #GraphId id field. Connection entity has User user field.
In ConnectionRepository interface I added following method:
List<Connection> findByUserId(long userId)
But it doesn't work. It generates incorrect cypher query. I think it incorrect, because it contains clause like this:
WHERE user.id = 15
which is not working, because id is not a property. It must be:
WHERE id(user) = 15
Is this a bug? In any case, how can I get it to work?
The derived query translates to the property id of the user defined on the Connection. It is quite possible that node entities contain a user managed id property as well and it would be incorrect to assume that id is always the node id.
In this case, you might want to use a #Query instead.
#Query("MATCH (user:label) WHERE ID(user)={0} return user")
List<Connection> findByUserId(long userId)

Spring-Hibernate: How to submit a for when the object has one-to-many relations?

I have a form changeed the properties of my object CUSTOMER. Each customer has related ORDERS. The ORDER's table has a column customer_id which is used for the mapping. All works so far, I can read customers without any problem.
When I now e.g. change the name of the CUSTOMER in the form (which does NOT show the orders), after saving the name is updated, but all relations in the ORDERS table are set to NULL (the customer_id for the items is set to NULL.
How can I keep the relationship working?
THX
UPDATE: Mapping Info
The Orders are mapped on the Customer side
#OneToMany
#JoinColumn(name = "customer_id")
#OrderBy("orderDate")
private Collection<Order> orders = new LinkedList<Order>();
UPDATE
Seems like adding a
#SessionAttributes("customer")
to my model, changing the method to
public String saveTrip(#ModelAttribute("customer") Customer customer, BindingResult result, SessionStatus status) {
if (!result.hasErrors()) {
this.tripManager.saveTrip(trip);
}
else {
logger.debug("Form data included errors, did not save data");
BindingUtils.logBindingErrors(result, logger);
}
status.setComplete();
return "redirect:/customers/";
}
Could solve the issu. But is this a good way of solving it???
One way would be not to submit the CUSTOMER Object from the form.
Instead submit the customer, submit only the customers ID and the new Name. In the controller you have to load the Customer by the submitted ID and then update the Name. And persist the Customer again.
HI,
Make cascade="none" attribute of many-to-one relationship from order side.
Thanks.

Resources