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

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)

Related

Rest call doesn't retrieve subclass entities which exist

I'm retrieving list of objects from database using JPA repository.
If I don't access sub classes in any way, they will be null.
If I just go and show count of them in log file, they will not be null.
I'm not sure why is this happening.
In below code, if I comment out 'for loop', policy.getFields() and policy.getFieldGroups will be null.
Why is this happening?
log.debug("Request to get all Policies");
List<Policy> policies = policyRepository.findAll();
for (Policy policy : policies) {
log.info("Policy fields group size:{}", policy.getFieldGroups().size());
log.info("Policy fields size:{}", policy.getFields().size());
}
return policies;
This is called lazy loading (or lazy initialization) , which means that collection relations will be fetched when they are about to be used. This is perfectly normal behavior. If you need it everytime, you can change loading of that relation to EAGER so those will be fetched everytime.
This can be done by adding #OneToMany(fetch = FetchType.EAGER) on fields you are need to be loaded (here field groups and fields)

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));
}
}

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

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

JPA MERGE failed to update entity field value when this field is a collection(using ElementCollection)

Here we have a Manifest class that includes list of students and teachers, both could be null.
class Manifest{
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_STUDENT")
List<String> students = new ArrayList<String>();
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_TEACHER")
List<String> teachers = new ArrayList<String>();;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_OTHERS")
List<String> others = new ArrayList<String>();;
}
on the UI, there are two multiple select, one for student and one for teacher that let user choose for current manifest.
here is the problem:
When user deselect all students or teachers from the list(meaning remove all students or teachers from current manifest) and click save, unfortunately nothing can be saved, from UI and database it shows that the multiselect chosen looks the SAME as before.
from service layer, the code is simply like this.
manifest.merge();
It seems we must keep at least one student or teacher for the collection field to make the change valid. So what's going on here and what is the solution? BTW, we are on Openjpa.
Kind of resolve the issue, more like a work around:
Before calling merge(), place several condition checkers to make sure the collection fields are not null
public void save(Manifest entity) {
if(entity.getStudents()==null){
entity.setStudents(new ArrayList<String>());
}
if(entity.getTeachers()==null){
entity.setTeachers(new ArrayList<String>());
}
if(entity.getOthers()==null){
entity.setOthers(new ArrayList<String>());
}
entity.merge();
}
Simple as it, it seems the UI returns those collection fields as null even we initiate them as with empty String lists.
cheers.
Initializing a value in a JPA managed class, such as class Manifest, has no bearing on what, or how, JPA will create the class as JPA maps extracted rows to the class. In particular, the result of:
List<String> students = new ArrayList<String>();
is likely to be:
On creation (by JPA) of a new instance, assign an ArrayList<String>() to students.
JPA overwrites students with the data it extracts - the empty ArrayList is dereferenced/lost.
If your code is clearing a list, such as students, use obj.getStudents().clear(). More likely to run into problems if you call obj.setStudents(someEmptyList).
The issue here is how the JPA manager handles empty datasets: as null or as an empty list. The JPA spec (old, not sure about the just released update) doesn't take a position on this point. A relevant article here.
From your comments, it's apparent that OpenJPA may not be respecting a null value for a Collection/List, while it happily manages the necessary changes for when the value is set to an empty list instead. Someone knowing more about OpenJPA than I may be able to help at this stage - meanwhile you've got a workaround.

Setup knockoutJS view model to bind to one-to-many property

I have a controller in grails that I am sending an ajax JSON post to with a knockoutJS view model. The view model (along with a javascript object) looks like this:
var childProperty= function(name, id) {
this.name = name;
this.id = id;
};
//KnockoutJS - Main view model
var viewModel = {
id: ko.observable(1),
childProperty: ko.observable(new childProperty("Chuck",1))
}
The data model on the controller side is trying to automatically use the Spring binding magic and bind the JSON request parameters to a new instance of my data model like so:
def jUpdate = {
def update = new SomeObject(params)
}
The problem comes in when I want the Spring binding to detect that childProperty.id is a one-to-many relationship in the data model and to go fetch the related property in the data model. The Grails documentation says this:
Data binding and Associations
If you have a one-to-one or many-to-one association you can use
Grails' data binding capability to update these relationships too. For
example if you have an incoming request such as:
/book/save?author.id=20
Grails will automatically detect the .id
suffix on the request parameter and look-up the Author instance for
the given id when doing data binding such as:
def b = new Book(params)
I am using the ko.toJS utility function and the simple properties are binding correctly. How can I set the view model child property up so that when it is posted to the grails controller, Spring detects it properly and fetches the associated record and builds the object?
I was never able to get the automagic spring bindings to work, so I just passed over the id for the child objects and manually set them on the server side in the params map. After that, GORM fetches the record appropriately. Something like this:
def update = {
params.put("childObject.id",params.childObjectId)
params.remove("childObjectId")
def parentObject = new ParentObject(params)
}
This fetches the related items and builds the object. If you had a lot of related fields, this would become a painful process.

Resources