Jackson bi-directional nested object setting - java

I'm having a few issues with deserializing the following json:
{
children:[{name:"1c"},{name:"2c"},{name:"3c"}]
}
My classes would look like this:
#JsonIdentityInfo(scope=ParentObject.class,generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class ParentObject {
int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy="parent",orphanRemoval=true)
Set<Child> children;
}
#JsonIdentityInfo(scope=Child.class,generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Child {
int id;
String name;
#ManyToOne
ParentObject parent;
}
With this in mind, I'd like Jackson to automatically set the ParentObject.
The structure I'm given is almost perfect with the exception of the ParentObject not being set. This is imperative as Hibernate will then set the ids of the parent and then set the ids of the children based on this which is currenly null.
I originally had it set with a JSONManagedReference and BackReference, but that it uni-directional and when requesting a single object, the parent object would be ignored.
How can I get this to work?
Thanks!
Tom

Try using #jsonignore on the
#ManyToOne
ParentObject parent;
I think while deserializing it will not consider it and give you a Parent Object..
Regards,
Prasad

Related

Parent child issue hibernate jpa

Consider I have 2 entity
#Entity(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#OneToMany(cascade = CascadeType.ALL)
public List<Child> children = new ArrayList<>();
}
and
#Entity
class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#ManyToOne
public Parent parent;
}
how can I create Child instance with parentId
without call findById(Long parentId) i.e.
Child createChild(parentId) {
Child child = new Child();
child.parent = //parent.findById(parentId); I don't wanna go to database
//for nothing if in this spot anyway will be parentId in database
return child;
}
I thought it can be done with quare but hql don't have
INSERT .... VALUE .., so I'm here, appreciate any help.
If it's don't have any sense due to architecture,
please explain, it's be a great help.
No need to create new object in
public List<Child> children = new ArrayList<>();
just write
#OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL)
public List<Child> children;
It is not a big problem that you will call parent.findById(parentId);
Hibernate caches some of the requests especially when used findById. You can see this answer link
The only thing to note is that you should not override findById in the repository, or if you do you should added it to the jpa cache.
EntityManager#getReference(Class<T> entityClass, Object primaryKey) is your friend here.
entityManager.getReference(Parent.class, parentId); returns an entity proxy. It can be used to improve the performance of the write operations since there will be no database call unless you access the fields of the returned entity.

Quarkus/Hibernate merge with OneToMany(orphanRemoval=true) gives HibernateException

I am migrating from weblogic+eclipselink to quarkus+hibernate and get an error when trying to update a class through an endpoint.
Error is javax.persistence.PersistenceException: org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:.
Imagine classes
#Data
#Builder
#Entity
class Parent {
...
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
#Builder.Default
private List<Child> children= new ArrayList<>();
}
#Data
#Builder
#Entity
class Child {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Parent parent;
}
#Transactional
#Applicationscoped
class ParentService {
public void updateParent(Parent updatedParent) {
Optional<Parent> optionalExistingParent =
parentRepository.getParentByID(updatedParent.getId());
if (optionalExistingParent.isEmpty()) {
throw new IllegalArgumentException("Tried updating non-existing parent " + updatedParent.getGuid().toString());
}
// set back references for possible new children
setBackReferences(updatedParent);
parentRepository.merge(updatedParent);
}
}
Situation: I retrieve a Parent object through a rest endpoint. Then I update a value from the parent (e.g. name) and send the updated parent object to a PUT endpoint. It then gets to the service layer, where I check if the parent is known, and if so, I call repository.merge().
It is at the repository.merge() call where the exception is thrown.
A lot of other stackoverflow questions regarding this error say that I need to clear() and addAll() the List, however (ignoring that I didn't even change anything in the list) all that code is hibernate code, there is no code written by me adding/removing to that list.
I fixed this by doing parent.getChildren.size() and thus retrieving the data from the database before merging. It's silly, but fetch=EAGER does not work because there are multiple lists of children (including grandchildren) and then you get a HibernateException that you cannot fetch multiple bags.

mappedBy reference an unknown target entity property with inheritance

I am trying to store a entity inside a Database with hibernate. I have got the following classes:
#Entity
public class UsableRemoteExperiment extends RemoteExperiment {
private List<ExperimentNodeGroup> nodeGroups = new ArrayList<>();
#OneToMany(mappedBy = "experiment", cascade = CascadeType.ALL, orphanRemoval = true)
public List<ExperimentNodeGroup> getNodeGroups() {
return nodeGroups;
}
public void setNodeGroups(final List<ExperimentNodeGroup> nodeGroups) {
this.nodeGroups = nodeGroups;
}
/* More getters and setters for other attributes */
The Experiment Node Group looks like this:
#Entity
public class ExperimentNodeGroup extends NodeGroup {
private List<Node> nodes = new ArrayList<>();
/* More getters and setters for other attributes */
And the NodeGroup Class looks like this:
#Entity
public abstract class NodeGroup extends GeneratedIdEntity {
protected Experiment experiment;
#ManyToOne(optional = false)
#JsonIgnore
public Experiment getExperiment() {
return experiment;
}
/* More getters and setters for other attributes */
Now when i try to compile the Code, I get this error:
Caused by: org.hibernate.AnnotationException: mappedBy reference an
unknown target entity property:
[...].ExperimentNodeGroup.experiment
in
[...].UsableRemoteExperiment.nodeGroups
It's one of the quirks of the hibernate where it does not work as expected with mappedBy and inheritance. Could you try specifying targetEntity as well? Here's the documentation and this is what it says:
The entity class that is the target of the association. Optional only
if the collection property is defined using Java generics. Must be
specified otherwise.
You can try specifying targetEntity = ExperimentNodeGroup.class or targetEntity = Transaction.class and see if that makes any difference.
I think the problem here is that you need to also put on a setter, hibernate assumes that if you have a getter to get from a database you will need a setter to read from it, you can either put a setter, or use
#Entity(access = AccessType.FIELD)
and put the annotations on your attributes.

saving mapped collection in new entity

I've read the documentation and thought I'd be able to do the following....
map my classes as so (which does work)
#Entity
public class ParentEntity
{
...
#OneToMany(mappedBy = "parent")
private List<ChildEntity> children;
...
}
#Entity
public class ChildEntity
{
...
#Id
#Column
private Long id;
...
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
.. but i want to be able to insert into both tables in one go and thought this would work:
parent = new ParentEntity();
parent.setChildren(new ArrayList<ChildEntity>());
ChildEntity child = new ChildEntity();
child.setParent(parent);
parent.getChildren().add(child);
session.persist(parent);
Can anyone tell me what i'm missing?
Do i need to save the parent first, then add the child and save it again?
thanks.
You have to add #OneToMany(cascade=CascadeType.PERSIST). You can also have CascadeType.ALL which includes persist, merge, delete...
Cascading is the setting that tells hibernate what to do with collection elements when the owning entity is persisted/merged/deleted.
By default it does nothing with them. If the respective cascade type is set, it invokes the same operation for the collection elements that were invoked for the parent.

Going "behind Hibernate's back" to update foreign key values without an associated entity

Updated: I wound up "solving" the problem by doing the opposite! I now have the entity reference field set as read-only (insertable=false updatable=false), and the foreign key field read-write. This means I need to take special care when saving new entities, but on querying, the entity properties get resolved for me.
I have a bidirectional one-to-many association in my domain model, where I'm using JPA annotations and Hibernate as the persistence provider. It's pretty much your bog-standard parent/child configuration, with one difference being that I want to expose the parent's foreign key as a separate property of the child alongside the reference to a parent instance, like so:
#Entity
public class Child {
#Id #GeneratedValue
Long id;
#Column(name="parent_id", insertable=false, updatable=false)
private Long parentId;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="parent_id")
private Parent parent;
private long timestamp;
}
#Entity
public class Parent {
#Id #GeneratedValue
Long id;
#OrderBy("timestamp")
#OneToMany(mappedBy="parent", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Child> children;
}
This works just fine most of the time, but there are many (legacy) cases when I'd like to put an invalid value in the parent_id column without having to create a bogus Parent first.
Unfortunately, Hibernate won't save values assigned to the parentId field due to insertable=false, updatable=false, which it requires when the same column is mapped to multiple properties. Is there any nice way to "go behind Hibernate's back" and sneak values into that field without having to drop down to JDBC or implement an interceptor?
Thanks!
Whats wrong about a bogus Parent? There is a neat way to do it in one place:
public void setParent(Parent parent) {
this.parent = parent;
this.parentId = parent == null ? null : parent.getId();
}
public void setParentId(Long parentId) {
final Parent parent;
if (parentId == null) {
parent = null;
} else {
parent = new Parent();
parent.setId(parentId);
}
setParent(parent);
}
Have you looked into setting the ManyToOne on the child inverse=true, instead of telling the property value to be un-insertable/updatable? If the inverse=true does what it used to, it'll make the Child entity "not the source of truth" for the relationship.. It'll still read the column, but not write it.. I think. It's been a while since I've been in this situation.

Categories

Resources