In the domain model, Category that has a ManyToOne relation with Tag entity, and Tag has ManyToOne with OfferingDetail.
I'm getting these errors in the TagServiceImpl_Roo_Service_Impl.aj file:
The method setTag(null) is undefined for the type OfferingDetail
Similar errors with a couple of other entities.
The setter/getter is .aj source file. Surprisingly, the error shows up only for the first setTag below, not the second! Why does this error occur, and how do I resolve this? I've tried reindex JDT weaving.
#Transactional
public void TagServiceImpl.delete(Tag tag) {
// Clear bidirectional many-to-one child relationship with Category
if (tag.getCategory() != null) {
tag.getCategory().getTags().remove(tag);
}
// Clear bidirectional one-to-many parent relationship with OfferingDetail
for (OfferingDetail item : tag.getOurPlay()) {
item.setTag(null);
}
// Clear bidirectional one-to-many parent relationship with UseCase
for (UseCase item : tag.getUseCases()) {
item.setTag(null);
}
getTagRepository().delete(tag);
}
The error was because I'd changed a property name in the entity, but Roo shell apparently did not pick up the change and modify other files.
Related
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)
I am trying to mapping many to many bidirectional. I have created two entity classes (Book, Author). Tables are created but in the Joint table data is not inserting here you can see my screenshots below.
I think I have written everything right but I am not sure what is wrong in my project. Please correct me if I am wrong.
With a bidirectional association you are supposed to update the association on both sides, usually with an helper method. See: Hibernate user guide - bidirectional many-to-many:
#Entity
class Book {
...
public void addAuthor(Author author) {
authors.add(author);
author.getBooks().add(this);
}
...
}
Now you can change your code to something similar to:
Author a1 = ...
Authort a2 = ..
Book b1 = ...
b1.addAuthor(a1);
b1.addAuthor(a2);
save(b1);
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));
}
}
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
Please can you help me with the problem below:
I have 2 domain classes(Parent, Child) which I do not want to be mapped to a table, so I put mapWith=none. However, when I do parent.validate() I want the validation to be cascaded to the child. How can I enable cascade validation for domain objects which are not mapped to a table?
Many thanks in advance!
Don't know if you can by design.
You could optionally add a customValidation() method on the parent object to initiate a check which is looping over the children and invoking their validate() methods. Any errors on the children could then be added to the errors object of the parent object.
boolean cascadedValidation() {
this.validate();
children.each {
if (!it.validate()) {
it.errors.allErrors.each { err ->
// Bind somewhere on parent object
}
}
}
return this.hasErrors();
}