I have a class, which can be referenced by many different classes in a Many-To-One or One-To-One relationship or have no referencing object. When an element of this class is deleted, the objects pointing to it should be removed too. What is the most beautiful way to achieve this behavior?
class A {
public remove() {
// remove the element which is pointing to me
}
}
class B {
#ManyToOne
private as
}
class C {
#ManyToOne
private as
}
...
First Of All, I don't think it is a 'beautiful' solution to put business methods in your entity class.
I would recommend creating DAO object for your A class and make your relationship bi-directional with CascadeType set to REMOVE:
#Entity
class A {
#OneToMany(mappedBy = "parentB", cascade = CascadeType.REMOVE)
private Set<Child> childrenB;
#OneToMany(mappedBy = "parentC", cascade = CascadeType.REMOVE)
private Set<Child> childrenC;
}
#Stateless
class daoA {
#PersistenceContext
EntityManager em;
public void remove(A a){
em.delete(a);
}
}
Related
I have two tables in database orders and offers. Earlier there was #OneToOne mapping between two i.e. for a single order, there was a single offer. Corresponding domains are:
#Entity
#Table(name = "orders")
#DiscriminatorFormula("0")
#DiscriminatorValue("0")
class Order {
#OneToOne(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Offer offer;
public Offer getOffer() {
return this.offer;
}
public void setOffer(Offer offer) {
this.offer = offer;
}
}
#Entity
#Table(name = "offers")
class Offer {
}
Now, i want OneToMany mapping between two i.e. for a single order, there can be multiple offers now. But for that, i want to build new version of Domain so as not to effect existing functionality. As it is OneToMany mapping so i will have to use Set or List. So, effectively, i want did:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offer;
public Set<Offer> getOffer() {
return this.offer;
}
public void setOffer(Set<Offer> offer) {
this.offer = offer;
}
}
How can i achieve this as currently it is giving me error in getter method as overridden method cannot have different return type.
Actually your problem is that you are using a field with the same name offer as the field in the super class while both have different types, so it will be confusing because you will have the child getter for Set<Offer> overriding the parent getter for Offer, that's why you get the Exception:
error in getter method as overridden method cannot have different return type
What you will have to do here is to use a different name for the field in your child class, for example offers, so the Model will be correct and Hibernate will correctly map the objects:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order1", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offers;
public Set<Offer> getOffers() {
return this.offers;
}
public void setOffers(Set<Offer> offers) {
this.offers = offers;
}
}
Note:
You need to have two objects of type Order in your Offer class, one for the mapping of offer and the second for the offers mapping, notice the mappedBy = "order1" in the mapping.
I have the following entities:
#Entity
public static class Parent {
#Id
#GeneratedValue
private Long id;
String st;
#OneToMany(mappedBy = "parent")
Set<Child> children = new HashSet<>();
// get,set
}
#Entity
public static class Child {
#Id
#GeneratedValue
private Long id;
String st;
#ManyToOne()
private Parent parent;
//get,set
}
Note, that there is no Cascade on #OneToMany side.
And I want the following:
I have one Parent with one Child in Detached state.
Now I want to remove child by some condition, so I'm accesing all children, find necessary and remove it directly via em.remove(child). + I remove it from Parent's collection.
After that I want to change some property of Parent and save it also.
And I'm getting EntityNotFound exception.
I performed some debug, and found that children collection is PersistentSet which remembered it's state in storedSnapshot. So, when I'm merging Parent to context - Hibernate do something with that stored snapshot and tries to load child it from DB. Of course, there is no such entity and exception is thrown.
So, there are couple of things I could do:
Map collection with #NotFound(action = NotFoundAction.IGNORE)
During removing from children collection - cast to PersistentSet and clear it also.
But it seems like a hack.
So,
1. What I'm doing wrong? It seems, that it's correct to remove child entity directly
2. Is there more elegant way to handle this?
Reproducible example:
#Autowired
PrentCrud parentDao;
#Autowired
ChiildCrud childDao;
#PostConstruct
public void doSomething() {
LogManager.getLogger("org.hibernate.SQL").setLevel(Level.DEBUG);
Parent p = new Parent();
p.setSt("1");
Child e = new Child();
e.setParent(p);
e.setSt("c");
p.getChildren().add(e);
Parent save = parentDao.save(p);
e.setParent(save);
childDao.save(e);
Parent next = parentDao.findAll().iterator().next();
next.setSt("2");
next.getChildren().size();
childDao.deleteAll();
next.getChildren().clear();
if (next.getChildren() instanceof PersistentSet) { // this is hack, not working without
((Map)((PersistentSet) next.getChildren()).getStoredSnapshot()).clear();
}
parentDao.save(next); // exception is thrwn here without hack
System.out.println("Success");
}
have you tried changing fetch type to eager? defaults for relations
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
maybe it gets cached because of fetch method
You can use next.setChildren(new HashSet<>()); instead of next.getChildren().clear(); to get rid of the getStoredSnapshot()).clear()
But it would be more elegant to use cascade and orphanRemoval.
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
Set<Child> children = new HashSet<>();
public void doSomething() {
...
next.setSt("2");
next.setChildren(new HashSet<>());
parentDao.save(next);
System.out.println("Success");
}
I'm about to design my rest API. I wonder how can I handle objects such as one below:
#Entity
public class Foo {
#ManyToMany
private Set<Bar> barSet;
#OneToMany
private Set<Zzz> zzzSet;
}
As you see object I want to expose to my rest API consists of other entity collections. I'm using Spring 4 and Jackson. Is it possible to return objects like one above - or do I have to create classes with primitive values only?
Yes, it is possible but you have to handle 2 problems :
1) at serialization, Jackson will call the getter Foo.getBarSet(). This will crash because by default, Hibernate returns lazy collections for #OneToMany and #ManyToMany relationships.
If you don't need them, annotate them with #JsonIgnore :
#Entity
public class Foo {
#JsonIgnore
#ManyToMany
private Set<Bar> barSet;
#JsonIgnore
#OneToMany
private Set<Zzz> zzzSet;
}
If you need them, you must tell hibernate to load them. For example, you can annotate #ManyToMany and #OneToMany with fetch = FetchType.EAGER (It is not the only solution btw) :
#Entity
public class Foo {
#ManyToMany(fetch = FetchType.EAGER)
private Set<Bar> barSet;
#OneToMany(fetch = FetchType.EAGER)
private Set<Zzz> zzzSet;
}
2) It can also cause some infinite loops :
Serialization of Foo calls Foo.getBarSet()
Serialization of Bar calls Bar.getFoo()
Serialization of Foo calls Foo.getBarSet()
[...]
This can be handled with #JsonManagedReference and #JsonBackReference :
#Entity
public class Foo {
#JsonManagedReference
#OneToMany
private Set<Zzz> zzzSet;
And on the other side :
#Entity
public class Zzz {
#JsonBackReference
private Foo parent;
}
I am trying to configure this #OneToMany and #ManyToOne relationship but it's simply not working, not sure why. I have done this before on other projects but somehow it's not working with my current configuration, here's the code:
public class Parent {
#OneToMany(mappedBy = "ex", fetch= FetchType.LAZY, cascade=CascadeType.ALL)
private List<Child> myChilds;
public List<Child> getMyChilds() {
return myChilds;
}
}
public class Child {
#Id
#ManyToOne(fetch=FetchType.LAZY)
private Parent ex;
#Id
private String a;
#Id
private String b;
public Parent getParent(){
return ex;
}
}
At first, I thought it could be the triple #Id annotation that was causing the malfunction, but after removing the annotations it still doesn't work. So, if anyone have any idea, I am using EclipseLink 2.0.
I just try to execute the code with some records and it returns s==0 always:
Parent p = new Parent();
Integer s = p.getMyChilds().size();
Why?
The problem most probably is in your saving because you must not be setting the parent object reference in the child you want to save, and not with your retrieval or entity mappings per se.
That could be confirmed from the database row which must be having null in the foreign key column of your child's table. e.g. to save it properly
Parent p = new Parent();
Child child = new Child();
p.setChild(child);
child.setParent(p);
save(p);
PS. It is good practice to use #JoinColumn(name = "fk_parent_id", nullable = false) with #ManyToOne annotation. This would have stopped the error while setting the value which resulted in their miss while you are trying to retrieve.
All entities need to have an #Id field and a empty constructor.
If you use custom sql scripts for initialize your database you need to add the annotation #JoinColumn on each fields who match a foreign key :
example :
class Parent {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
public Parent() {}
/* Getters & Setters */
}
class Child {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
/* name="<tablename>_<column>" */
#JoinColumn(name="Parent_id", referencedColumnName="id")
private int foreignParentKey;
public Child () {}
}
fetch= FetchType.LAZY
Your collection is not loaded and the transaction has ended.
Suppose I have self linked Category entity defined as follows:
public class Category {
#Id
#Access(AccessType.FIELD)
public String Url;
#ManyToOne(optional=true)
#Access(AccessType.FIELD)
public Category Parent;
#OneToMany
private Set<Category> subs;
public void addSub(Category sub) {
subs.add(sub);
}
public void removeSub(Category sub) {
subs.remove(sub);
}
#Access(AccessType.FIELD)
public String Title;
#Access(AccessType.FIELD)
public boolean Done;
I wonder, will it work correctly if I create new Category and add it with my addSub method? Will Category be persisted correctly? Will subcategories be persisted automatically and in correct order?
In the current state of your code - no. To make it work as you want you need to do the followng:
Connect sides of bidirectional relationship with mappedBy on #OneToMany, otherwise Hibernate would think that you have two different relationships:
#OneToMany(mappedBy = "Parent")
It's your responsibility to keep both sides of your relationship in consistent state:
public void addSub(Category sub) {
sub.setParent(this);
subs.add(sub);
}
Hibernate looks at #ManyToOne side when it stores the foreign key.
If you want subcategories of persistent Category to be persisted automatically, you need to configure cascading:
#OneToMany(..., cascade = CascadeType.ALL)