Updating property and resource link in single PUT query with Spring Data Rest - spring

OK so let's start self referencing object, something like this:
#Data
#Entity
public class FamilyNode {
#Id
#GeneratedValue
private long id;
private boolean orphan;
#ManyToOne
private FamilyNode parent;
}
And a standard repository rest resource like this:
#RepositoryRestResource(collectionResourceRel = "familynodes", path = "familynodes")
public interface FamilyNodeRepository extends CrudRepository<FamilyNode, Long> {
}
Now, let's assume some parent objects which I want to link with already exist with ID=1 and ID=2, each of which were created with a POST to /api/familynodes which looked like this:
{
"orphan": true,
}
If I attempt to create a new client (ID=3) with something like this using a POST request to /api/familynodes, it will work fine with the linked resource updating fine in the DB:
{
"orphan": false,
"parent": "/api/familynodes/1"
}
However, if I attempt to do a PUT with the following body to /api/familynodes/3, the parent property seems to silently do nothing and the database is not updated to reflect the new association:
{
"orphan": false,
"parent": "/api/familynodes/2"
}
Similarly (and this is the use case that I'm getting at), a PUT like this will only update the orphan property but will leave the parent untouched:
{
"orphan": true,
"parent": null
}
So you now have a record which claims to be an orphan, but still has a parent. Of course you could do subsequent REST requests to the resource URI directly but I'm trying to make rest operations atomic so that it's impossible for any single rest query to create invalid state. So now I'm struggling with how do that with what seems like a simple use case without getting into writing my own controller to handle it - am I missing a mechanism here within the realm of spring data rest?

This is the expected behaviour for PUT requests in Spring Data Rest 2.5.7 and above wherein a PUT request does not update the resource links, only the main attributes.
As detailed here by Oliver Gierke:
If we consider URIs for association fields in the payload to update those associations, the question comes up about what's supposed to happen if no URI is specified. With the current behavior, linked associations are simply not a part of the payload as they only reside in the _links block. We have two options in this scenario: wiping the associations that are not handed, which breaks the "PUT what you GET" approach. Only wiping the ones that are supplied using null would sort of blur the "you PUT the entire state of the resource".
You may use a PATCH instead of PUT to achieve the desired result in your case

Related

Spring Data Rest - Manage child entities when only parent has a repository

I have a Spring backend that is using Spring Data Rest. I have a One-To-Many relation where the parent has its own repository but the children don't. I've chosen to do it this way because the child class is rather small and I would want to avoid having to make a bunch of requests to manage them.
So the classes are like this:
#Entity
class Parent(
#field:OneToMany(mappedBy="parent", cascade = [CascadeType.ALL])
var children: MutableList<Child> = mutableListOf()
)
#Entity
class Child(
#field:ManyToOne
var parent: Parent? = null
)
When I'm creating a parent, I post a JSON like this:
{
children: [
{ ... fields }
]
}
I have noticed that the child entities are indeed created but the field referencing the parent is not populating. To overcome this, I've tried to set the parent field in a #PrePersist and #PreUpdate method. This worked for a while but then I've noticed that the fields are not deleted when I make a patch request and don't include them in the JSON. Google-in a bit a found that the orphanRemoval property of the #OneToMany relation can be the key but when I've added it, the first part of my problem (populating the relationship fields) stoped working.
Is there a way to handle these operations with Spring Data Resp to avoid having to write a custom controler from scratch?
EDIT:
Turns out they are not connected. My #PreUpdate hooks are not called because Spring does not save my entity if there were no changes to its fields. Can this behaviour be altered somehow (again, without creating a custom controller)

PUT VS PATCH for operation set template to default

I have to change one value (template) in my entity A:
class A {
private Template template
}
My endpoint looks like:
/warehouse/company/{companyId}/template/default
with body:
class MyDto {
private Long templateId
}
What is the best solution for this operation? I want to change only one value in my entity: based on MyDto I have to fetch template from DB and set A.setTemplate(newTemplateFromDB).
The best option would be PATCH without RequestBody, but request body is necessary...
You should definitely use PATCH because you are partially updating your resource (A) and not completely updating it.
If you would update the complete resource by replacing your DB data with whatever you receive in the endpoint, then you should use PUT.
I just don't get why you say that the best option would be without the request body.

Is double saving a new entity instance with a Spring data 2 JpaRepository correct?

I have two entities in a bi-directional many to many relationship.
A <-> many to many <-> B
I have an endpoint where a client can create an instance of A, and at the same time add some number of B entities to that A, by passing in an array of B entity id keys. Please keep in mind that these B entities already exist in the database. There is no business or software design case for tightly coupling their creation to the creation of A.
So class A looks like this, and B is the same, but with references to A.
#Entity
class A {
#Id
#GeneratedValue
int id;
#ManyToMany
List<B> bs;
String someValue;
int someValue2;
// With some getters and setters omitted for brevity
}
So at first try my endpoint code looks like this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aToCreate);
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
And the client would submit a JSON request like this to create a new A which would contain links to B with id 3, and B with id 4.
{
"bs": [{id:3}, {id:10}],
"someValue": "not important",
"someValue2": 1
}
Okay so everything's working fine, I see all the fields deserializing okay, and then I go to save my new A instance using.
aRepository.save(aToCreate);
And that works great... except for the fact that I need all the data associated with the b entity instances, but the A object returned by aRepository.save() has only populated the autofill fields on A, and done nothing with the B entities. They're still just hollow entities who only have their ids set.
Wut.
So I go looking around, and apparently SimpleJpaRepository does this.
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
And since the A entity is brand new, it only persists the A entity, but it doesn't merge it so I don't get any of the rich B data. So okay, if I modify my code to take this into account I get this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aRepository.save(aToCreate));
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
Which works just fine. The second pass through the repository service it merges instead of persists, so the B relationships get hydrated.
My question is: Is this correct, or is there something else I can do that doesn't look so ineloquent and awful?
To be clear this ONLY matters when creating a brand new instance of A, and once A is in the database, this isn't an issue anymore because the SimpleJpaRepository will flow into the em.merge() line of code. Also I have tried different CascadingType annotations on the relationship but none of them are what I want. Cascading is about persisting the state of the parent entity's view of its children, to its children, but what I want to do is hydrate the child entities on new instance creation, instead of having to make two trips to the database.
In the case of a new A, aToCreate and savedA are the same instance because that is what the JPA spec madates:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#persist(java.lang.Object)
Make an instance managed and persistent.
Spring Data simply returns the same instance so persist/merge can be abstracted into one method.
If the B instances you wish to associate with A are existing entities then you need to fetch a reference to these existing instances and set them on A. You can do this without a database hit by using the T getOne(ID id) method of Spring Data's JpaRepository:
https://docs.spring.io/spring-data/jpa/docs/2.1.4.RELEASE/api/
You can do this in your controller or possibly via a custom deserializer.
This is what I ended up going with. This gives the caller the ability to save and hydrate the instance in one call, and explains what the heck is going on. All my Repository instances now extend this base instance.
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
/**
* Saves an instance twice so that it's forced to persist AND then merge. This should only be used for new detached entities that need to be saved, and who also have related entities they want data about hydrated into their object.
*/
#Transactional
default T saveAndHydrate(T save) {
return this.save(this.save(save));
}
}

Spring Data JPA and Spring Web Updating Entities Avoiding Associations

I have the following entities:
Area
Listing
They are both many-to-many:
An area can have many listings
A listing can have many areas
Both Area and Listing have other fields like name, domain, etc.
I'm using Spring Web RestController as a way to update the entities.
For example:
#PutMapping("/{id}")
public Area update(#PathVariable Long id, #RequestBody Area update) {
return areaRepository.save(update);
}
However, as an Area can have many thousands of Listings, it's not practical to pass them all in the update request when I just want to update the Area name and other basic fields in my web application.
For example, the update json in the http request would be:
{
"id" : 69,
"name" : "San Francisco",
"domain" : "foo",
...
}
When serialised, the area instance above will have a listings field equal to null (obviously) and then when saved, all association are remove from this Area.
I'm thinking that I could do a select-then-update set of operations and only update the values necessary but that is cumbersome - especially when there are many dozens of non-association fields.
The question would be: how can I try to keep to the above code and http request and not remove all of the existing Listing associations when saving the input area? Is this possible? I want to update the name and other basic fields but not the association fields.
You can use the BeanUtilBean(org.apache.commons.beanutils.BeanUtilsBean).
Step 1: Create custom beanutilbean class.
#Component
public class CustomBeanUtilsBean extends BeanUtilsBean {
#Override
public void copyProperty(Object dest, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
if(value==null)return;
super.copyProperty(dest, name, value);
}
}
Step 2: In your controller while updating. first get the Area from database & use copyProperties method as shown in below.
#PutMapping("/{id}")
public Area update(#PathVariable Long id, #RequestBody Area update) {
Area areaDB = areaRepository.findOne(update.getId());
customBeanUtilsBean.copyProperties(areaDB,update);//it will set not null field values of update to areaDB.
return areaRepository.save(areaDB);
}
Hope this will helps you..:)
Since you are using spring-data-jpa repository you can write a new method that takes the changed values of Area object with #Modifying and #Query annotations on it.
In the #Query you specify the HQL query with update statement as in here

Spring Data Rest Mongo - how to create a DBRef using an id instead of a URI?

I have the following entity, that references another entity.
class Foo {
String id;
String name supplierName;
**#DBRef** TemplateSchema templateSchema;
...
}
I want to be able to use the following JSON (or similar) to create a new entity.
{
"supplierName": "Stormkind",
"templateSchema": "572878138b749120341e6cbf"
}
...but it looks like Spring forces you to use a URI like this:
{
"supplierName": "Stormkind",
"templateSchema": "/template-schema/572878138b749120341e6cbf"
}
Is there a way to create the DBRef by posting an ID instead of a URI?
Thanks!
In REST, the only form of ID's that exist are URIs (hence the name Unique Resource Identifier). Something like 572878138b749120341e6cbf does not identify a resource, /template-schema/572878138b749120341e6cbf does.
On the HTTP level, entities do not exist, only resources identified by URIs. That's why Spring Data REST expects you to use URIs as identifiers.

Resources