jpa map owned by the "one" side - java

I have a tree like data structure with some sort of composite pattern. With an abstract class Element, there is a CompositeElement and a SingleElement. It looks like this:
#Entity
#DiscriminatorValue("Composite")
public class CompositeElement extends Element {
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name="Sub_Elements")
#MapKeyColumn(name="xxx")
protected Map<Integer, Element> subs;
...
}
Until now, the relation was unidirectional. It worked well. But now a use case popped up where I need to navigate from a sub element to the parent element. So what I'd love to do is this:
#Entity
#Inheritance
#DiscriminatorColumn("s_discriminator")
public class Element {
#ManyToOne(mappedBy="subs", fetch=FetchType.LAZY)
protected CompositeElement parent;
...
}
But the #ManyToOne Annotation doesn't allow a "mappedBy" attribute.
From a domain view, the parent owns the children objects in the data structure. Not the other way around. This is also emphasised by the eager fetch and the cascade rule.
If the ownership of the relationship was on the child side, then child.setParent(p) wouldn't really work because here I'm missing the key for the map.
Is there any way to keep the ownership of the relation on the side of the parent but still make it bidirectional?

Have a look at adding a #JoinColumn annotation to your #ManyToOne and the
javadocs for the #ManyToOne annotation.
Normally the mappedby would work the other way where the #OneToMany would be mappedby the #ManyToOne

it looks like it's not possible the way I want it.
I changed it so the relationship is owned by the child. And I added a property "childIndex" in the Element class. This property is referenced by #MapKey.
public class CompositeElement extends Element {
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="parent")
#MapKey(name="childIndex")
protected Map<Integer, Element> subs;
and
public abstract class Element {
protected Integer childIndex;
#ManyToOne(fetch=FetchType.LAZY)
protected CompositeElement parent;
The solution works, but I don't like it. I don't like the child element knowing its "childIndex". When I re-order the children in the parent object, I need to modify each of the children. I would have loved to keep a separate database table representing the relationship and the index column being there.
I guess another option would have been to model the relation as an entity itself.

Related

Fetching a list of parent entities with a filtered collection of children in Spring Data JPA

So I've been stuck on this problem for about half a day so I am wondering if I am just over-complicating things.
My application has three different Java object classes: Grandparent, Parent, and Child. Each Grandparent contains a List of Parents, and each Parent contains a List of Children. Child has an "isWellBehaved" property, which is a boolean.
We are using Spring Data JPA and Hibernate in order to map the data to a database. Our application contains a lot of nested entities and circular relationships and we are relying on projections to keep our request size down.
The Problem: given a grandparent id, I want to return a list of all Parents (as projections). I want each of the Parents to contain a list of Child projections, but only if the Child is well behaved. The rest of the children in the collection should be filtered out from the collection.
What would be the simplest way to achieve this? We are not using Hibernate filters at the moment and I am not keen on introducing them as we are not likely to need them anywhere else (either way, would it be suited for this purpose?). I have used JPA Criteria API predicates (very little) but find it difficult to adapt that to this particular scenario. Is a native query the way to go? I've started going in that direction but am having some issues mapping all the fields to our Spring entity due to all the nested dependencies so just want to make sure I am even headed in the right direction before I continue.
My (simplified) parent entity class looks like this:
#Entity
#Table(name="parent"
public class Parent {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="parent_id")
Integer id;
Integer grandparentId;
#OneToMany(mappedBy = "parent")
List<Child> children;
}
Child class:
#Entity
#Table(name="child"
public class Child {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="child_id")
Integer id;
#ManyToOne
#JoinColumn(name="parent_id")
Parent parent;
boolean isWellBehaved;
}
Parent repository interface:
#RepositoryRestResource(excerptProjection = ParentProjection.class)
public interface ParentRepository extends JpaProjectionRepository<Parent, Integer, ParentProjection> {
List<ParentProjection> findAllByGrandparent_Id(Integer grandpaId);
}
You can use #Where annotation of hibernate on collection. It will be something like
#Entity
#Table(name="parent"
public class Parent {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="parent_id")
Integer id;
Integer grandparentId;
#Where(clause = "isWellBehaved=true")
#OneToMany(mappedBy = "parent")
List<Children> children;
}
As you have said:
I want each of the Parents to contain a list of Child projections, but only if the Child is well behaved.
List<childerns>allChilderns=parentsList.stream().map(parent>dao.findchildernByParentId()).collect(Collectors.List());
allChilderns.stream().filter(childern->childern.isWellBehaved()==true).collect(Collectors.toList());
Get all the parents by GrandParentId--the one which you are doing.
Once you got all the parents,for each parent findchildernByParentId.
and then filter out the childern on the basis of condition.
Let me know:)

#OnDelete with #OneToMany only

I have problem with annotation #OnDelete with #OneToMany relation.
public class Patent {
#OneToMany
#JoinCollumn(name = "parent_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Child> children;
}
public class Child {
}
When I run it I get this error: "only inverse one-to-many associations may use on-delete="cascade"". How I need to change code to get it functional, without bidirectional relation? I know that, it can be solved with adding #ManyToOne relation, with appropriate annotations, to Child class, but I do not want to use this solution.
Edit: Purpose for this is that i need to generate "on delete cascade" to foreign key constraint in exported ddl schema.
All you need is to use orphanRemoval parameter for your OneToMany relation. See https://docs.oracle.com/cd/E19798-01/821-1841/giqxy/ for reference.
Example:
#OneToMany(mappedBy="customer", orphanRemoval="true")
public List<Order> getOrders() { ... }
However I think that your mapping is quite wrong, for such relation you should add Patent field to your Child class, mark relation as ManyToOne, then use JoinCollumn and set the reference as parent_id. With mappedBy and orphanRemoval options inside Patent - usability will be the same as you want.

Cannot delete detached child from collection in parent in Hibernate

I have the following entities with a parent-child relationship:
public class Parent {
#Id #GeneratedValue String id;
#Version Long version;
#OneToMany(mappedBy = "parent", orphanRemoval = true)
#Cascade({CascadeType.ALL})
Set<Child> children;
// getters and setters
}
public class Child {
#Id #GeneratedValue String id;
#ManyToOne
#JoinColumn("parent_id")
Parent parent;
// getters and setters
}
I retrieve a Parent for edit on the web UI by copy properties to a ParentDto, which has a list of ChildDtos.
Once I'm done editing, I send the ParentDto object back and copy all properties into a new Parent object (parent) with a new HashSet to store the Children created from the list of ChildDtos.
Then I call getCurrentSession().update(parent);
The problem
I can add children, update children, but I can't delete children. What is the issue here and how do I resolve it?
Thanks in advance.
You have a bidirectional association, you need to remove from Child class the link to the parent class, try to make Parent reference to null, and also set the Set<Child> to a new HashSet<Child> or whatever your implementation is.
Then save the changes that will remove the children form the table.
This action can only be used in the context of an active transaction.
public void remove(Object entity);
Transitions managed instances to removed. The instances will be deleted from the datastore on the next flush or commit. Accessing a removed entity has undefined results.
For a given entity A, the remove method behaves as follows:
If A is a new entity, it is ignored. However, the remove operation cascades as defined below.
If A is an existing managed entity, it becomes removed.
If A is a removed entity, it is ignored.
If A is a detached entity, an IllegalArgumentException is thrown.
The remove operation recurses on all relation fields of A whose cascades include CascadeType.REMOVE. Read more about entity lifecycle

JPA + Hibernate - How to get FK of child entity without fetching that entity?

A possible answer to my question is located here:
How can I retrieve the foreign key from a JPA ManyToOne mapping without hitting the target table?
However, the preferable solution (property access) does not work in my case (I got missing column exception - why?)
The model looks like this: Entities Parent and Child. Table parent has column child_id which is PK of child table so it is typical #ManyToOne relation.
Now the point is, if I fetch Parent entities, I need to have access to FK value (aka. PK of Child entity) without fetching Child entities. How can I do that?
I use Annotations and my mapping looks as follows:
#Entity
#Table(name = "parent")
public class Parent extends AbstractEntity {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "patent_id", nullable = true)
private Child child;
#Column(name="child_id",insertable=false,updatable=false)
private Integer childId;
public Child getChild() {
return patent;
}
public void setChild(Child child) {
this.child = child;
}
public Integer getChildId(){
return childId;
}
}
And what I want to do is call parent.getChild().getId() without extra fetches of Child entity from DB.
According to the answer I have mentioned above, If I moved annotations from field to getter (in Parent entity am I right?), requested behavior would be out of the box. However, when I move annotations to getter, I get a validation exception that child column is missing (curious, why child not child_id as declared?)
PS: Shown workaround to declare a FK column as separate field works fine, but I don't think that this is the way it should be done.
OK, after reading following article http://256stuff.com/gray/docs/misc/hibernate_lazy_field_access_annotations.shtml
I have realized, that property access should be to the property I want to fetch, not the actual child object. So changing id access of AbstractEntityfrom field to property, makes the trick.

Hibernate/JPA: ManyToMany and OneToMany relationship on same attribute

I have a problem on combining a ManyToMany with an OneToMany relationship.
I have entries and categories. Every entry has one main category and 0..* subcategories.
This is my implementation:
public class Entry extends AbstractEntity {
[...]
private Category mainCategory;
#ManyToMany(targetEntity = hello.Category.class)
private Set<Category> subCategories;
[...]
}
public class Category extends AbstractEntity {
[...]
#ManyToMany(targetEntity = hello.Entry.class, mappedBy = "subCategories")
private Set<Entry> entries;
[...]
}
The ManyToMany relationship is functional but i don't know how to implement the OneToMany relationship.
You cannot define two separate mapping on a single attribute. The data it should contain is not well-defined. Should it contain the Entries mapped by the subCategories field or by the mainCategory or both? Since there is not a singe sensible answer for all use cases, JPA disallows such multiple annotations.
You can however just add a field corresponding to the inverse (non-owning) side of the one-to-many relationship.
Define it like this:
public class Category ...
#ManyToOne(mappedBy="mainCategory")
private Set<Entry> entriesHavingThisCategoryAsMain;
I could not come up with a better name for the inverse side, so use your context :)
EDIT: you do not need to define the targetEntity attribute for typed Collections except you have multiple Category and Entry entities in different packages.

Categories