Using Hibernate, I have the following classes :
public class Person {
#ManyToMany(fetch=FetchType.LAZY)
#Cascade(CascadeType.ALL)
#JoinTable(name = "person_address", joinColumns = { #JoinColumn(name = "person_id") },
inverseJoinColumns = { #JoinColumn(name = "address_id") })
public List<Address> getAddresses() {
return addresses;
}
}
public class Address {
...
#ManyToMany(mappedBy="addresses", fetch=FetchType.LAZY)
#Cascade(CascadeType.ALL)
public List<Person> getPersons() {
return persons;
}
}
My question is :
Is it possible that deleting an element of the relationship between Address and Person, "orphans" elements of Address are also deleted. In other words I don't want to have addresses that are not linked to a person.
Thanks,
Marc.
No, it's not possible. Hibernate doesn't provide orphan removal functionality for many-to-many relationships.
Why would you like to do that? You can delete any of the entities (a Person or an Address) and Hibernate will ensure the consistency based on the annotation you have defined.
Manually deleting links between the different tables is an unnecessary risk in this case.
org.hibernate.annotations.CascadeType.DELETE_ORPHAN can be used to deleted orphans.
Related
Those are my classes. I want to delete items from likedCourses. So I expect JPA to delete item from course_like table. I looked and try to understand from other examples but couldn't. It is like deletion doesn't exist in JPA realm when there is relation. It is good for selecting though. I'd want to share what I tried but I couldn't find anything about it.
Note : I see that in ManyToMany relationship there is not orphanRemoval option.
#Entity
class Student {
#Id
Long id;
#ManyToMany
#JoinTable(
name = "course_like",
joinColumns = #JoinColumn(name = "student_id"),
inverseJoinColumns = #JoinColumn(name = "course_id"))
Set<Course> likedCourses
}
#Entity
class Course {
#Id
Long id;
#ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;
}
If you want to remove an entry from the course_like table, you will have to load the Student and remove the element from the likedCourses set that should be removed. If you do that in a #Transactional method, you will see that Hibernate will emit delete statements to delete the rows that represent the removed objects from the likedCourses set. That's the magic of an ORM, it synchronizes the object graph with the database, without you telling it to emit statement A, B, ...
I have three Model. BookingDetail have One To Many relation to both LookupFoodItem and LookupFacility model.In my database there is 4 LookupFacility record and 4 LookupFoodItem record. When I'm fetching BookingDetail one record there should be 4 LookupFoodItem record but I found 16 record which is redundant.How can I solve this problem to get only real record not redundant data?
public class BookingDetail {
#OneToMany(mappedBy = "bookingDetail", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<LookupFacility> lookupFacilities;
#OneToMany(mappedBy = "bookingDetail", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<LookupFoodItem> lookupFoodItems;
}
public class LookupFacility {
#ManyToOne
#JoinColumn(name="booking_details_id")
BookingDetail bookingDetail;
}
public class LookupFoodItem{
#ManyToOne
#JoinColumn(name="booking_details_id")
BookingDetail bookingDetail;
}
When I'm fetching BookingDetail information from database using JPA it's giving me redundant data like this
LookupFoodItem{id=40, name='Beef ', price=120.0}
LookupFoodItem{id=41, name='Polao', price=300.0}
LookupFoodItem{id=42, name='Crab Fry', price=299.0}
LookupFoodItem{id=43, name='Chicken Soup', price=100.0}
LookupFoodItem{id=40, name='Beef ', price=120.0}
LookupFoodItem{id=41, name='Polao', price=300.0}
LookupFoodItem{id=42, name='Crab Fry', price=299.0}
LookupFoodItem{id=43, name='Chicken Soup', price=100.0}
LookupFoodItem{id=40, name='Beef ', price=120.0}
LookupFoodItem{id=41, name='Polao', price=300.0}
LookupFoodItem{id=42, name='Crab Fry', price=299.0}
LookupFoodItem{id=43, name='Chicken Soup', price=100.0}
LookupFoodItem{id=40, name='Beef ', price=120.0}
LookupFoodItem{id=41, name='Polao', price=300.0}
LookupFoodItem{id=42, name='Crab Fry', price=299.0}
LookupFoodItem{id=43, name='Chicken Soup', price=100.0}
There is no relation between LookupFoodItem and LookupFacilities.
Not sure if you are using LOMBOK or actually missing the getters and setter so I added it here.
Used Set instead of List and changed the fetching to Lazy. This is from one of my working solutions. Hope this helps.
Feel free to change the annotations as needed For more details this has some info
public class BookingDetail {
private Set<LookupFacility> lookupFacilities = new HashSet<LookupFacility>();
private Set<LookupFoodItem> lookupFoodItems = new HashSet<LookupFoodItem>();
#OneToMany(fetch = FetchType.LAZY, mappedBy = "bookingDetail", cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.SUBSELECT)
public Set<LookupFacility> getLookupFacilities() {
return lookupFacilities;
}
public void setLookupFacilities(final Set<LookupFacility> lookupFacilities) {
this.lookupFacilities = lookupFacilities;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "bookingDetail", cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.SUBSELECT)
public Set<LookupFoodItem> getLookupFoodItems() {
return lookupFoodItems;
}
public void setLookupFoodItems(final Set<LookupFoodItem> lookupFoodItems) {
this.lookupFoodItems = lookupFoodItems;
}
}
And for class LookupFacility and LookupFoodItem us this
private BookingDetail bookingDetail;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "booking_details_id")
public BookingDetail getBookingDetail() {
return bookingDetail;
}
public void setBookingDetail(BookingDetail bookingDetail) {
this.bookingDetail = bookingDetail;
}
If you intend not to fetch records without loading all other relations, i all advice you add fetchType.LAZY to your relationship mapping and also include this json ignore #JsonIgnore so as to solve data loading initialization error at runtime.
See example below
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "booking_details_id")
private BookingDetail bookingDetail;
With this approach, you should be good to go. I hope this help.
Currently we have an issue (a well known one) with Spring Data JPA + Spring Data REST (Hibernate as JPA implementation) when trying to update the collection (relation) which is a not the owning side.
The mapping is the following:
#Entity(name = Product.NAME)
public class Product {
...
#OneToMany(mappedBy = "baseProduct", fetch = FetchType.LAZY, targetEntity = Variant.class)
List<Variant> getVariants() {
...
and on the other variant side:
#Entity(name = Variant.NAME)
public class Variant extends Product {
...
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Product.class)
#JoinColumn(name = "baseproduct_id", referencedColumnName = "id")
Product getBaseProduct() {
...
}
all is good on the Java side if you use Spring Data JPA only, however if you want to update the "product" by updating its collection of variants and send PATCH request to https://localhost:8112/storefront/rest/product/21394435410197232 containing the payload of the new collection only (having 2 out of the 3 items):
{"variants":["22801810293768080","22801810293768096"]}
I get no exceptions or anything but since the owning side is the other side nothing is persisted and I got the old 3 items again.
I know that I can fix this by setting
#JoinColumn(name = "baseproduct_id", referencedColumnName = "id")
on both sides and not use mappedBy anywhere, however I have heard there is a performance implication which I am not sure how big it is (we got 100+ entities having #OneToMany) and I wonder is there better workaround via #PreUpdate listener or something ?
You have to synchronize both sides of the bidirectional association, and also add on orphanRemoval and Cascade.
So, your mapping becomes:
#OneToMany(
mappedBy = "baseProduct",
fetch = FetchType.LAZY,
targetEntity = Variant.class
cascade = CascadeType.ALL,
orphanRemoval = true)
List<Variant> getVariants() {
And the two add/remove methods:
public void addVariant(Variant variant) {
getVariants().add(variant);
variant.setBaseProuct(this);
}
public void removeVariant(Variant variant) {
variant.setBaseProuct(null);
this.getVariants().remove(variant);
}
You need to implement equals and hashCode methods in the Variant child entity for the add and remove methods to work effectively.
i am developing an sample application using hibernate. Its going quite smooth, but i have one small query regarding one to many relation.
I have seen there are 2 different ways of specifying the relation
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_PHONE", joinColumns = { #JoinColumn(name = "STUDENT_ID") }, inverseJoinColumns = { #JoinColumn(name = "PHONE_ID") })
public Set<Phone> getStudentPhoneNumbers() {
return this.studentPhoneNumbers;
}
the other way is
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="PERSON_ID", nullable=false)
public Set<Address> getAddresses() {
return addresses;
}
which is more efficient and when to use which method.
The second one is probably a bit more efficient, because it needs one join less than the first one.
But it couples the many side (address) to the one side (person) by requiring a foreign key in the address table. That is in contradiction with the fact that the association is unidirectional (address doesn't know about its person in the object model).
This is why the second one is the default for unidirectional one to many associations.
Simplifying, in my database I have tables:
Car (pk="id_car")
CarAddon (pk="id_car_fk,id_addon_fk",
`FK_car_addon_addon` FOREIGN KEY (`id_addon_fk`) REFERENCES `addon` (`id_addon`)
`FK_car_addon_car` FOREIGN KEY (`id_car_fk`) REFERENCES `car` (`id_car`)
Addon (pk="id_addon")
Shortly: I have cars, many cars can has many addons (like ABS etc).
There are tables with cars, addons, and one table which is logical connection.
Overall, entities work fine. I have no problems with persist data, when I want persist single object. I don't have problems, when I want FETCH data, ie. Car->getAddon();
But, when I'm going to persisting a collection, nothing happens. No exceptions were thrown, there were no new data in database.
//DBManager is a singleton to create an EntityManager
EntityManager em = DBManager.getManager().createEntityManager();
em.getTransaction().begin();
Addon addon1 = new Addon();
addon1.setName("czesc1");
em.persist(addon1);
Addon addon2 = new Addon();
addon2.setName("czesc2");
em.persist(addon2);
car.setAddonCollection(new ArrayList<Addon>());
car.getAddonCollection().add(addon1);
car.getAddonCollection().add(addon2);
em.persist(car);
em.getTransaction().commit();
In this case, addons were stored in Addon table, car in Car table. There are no new data in CarAddon table though object car has good data (there is addon collection in debbuger).
When I changed em.persist(car) to em.merge(car) I got an exception:
"SEVERE: Persistence error in /admin/AddAuction : java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: model.entity.Car[ idCar=0 ]."
Simple version of my classess:
#Entity
#Table(name = "addon")
#XmlRootElement
#NamedQueries({...})
public class Addon implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_addon")
private Integer idAddon;
#Size(max = 100)
#Column(name = "name")
private String name;
#JoinTable(name = "car_addon",
joinColumns = {
#JoinColumn(name = "id_addon_fk", referencedColumnName = "id_addon")},
inverseJoinColumns = {
#JoinColumn(name = "id_car_fk", referencedColumnName = "id_car")})
#ManyToMany
private List<Car> carCollection;
#XmlTransient
public List<Car> getCarCollection() {
return carCollection;
}
public void setCarCollection(List<Car> carCollection) {
this.carCollection = carCollection;
}
}
#Entity
#Table(name = "car")
#XmlRootElement
#NamedQueries({...)
public class Car implements Serializable {
#ManyToMany(mappedBy = "carCollection", fetch= FetchType.EAGER, cascade=CascadeType.ALL)
private List<Addon> addonCollection;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_car")
private Integer idCar;
#XmlTransient
public List<Addon> getAddonCollection() {
return addonCollection;
}
public void setAddonCollection(List<Addon> addonCollection) {
this.addonCollection = addonCollection;
}
}
How can I fix it?
ps1. I have:
cascade=CascadeType.ALL przy #ManyToMany private List<Car> carCollection
but this dos not solve my problem.
ps2. I am using Netbeans 7, EclipseLink and MySQL (not Hibernate - I have problem with it)
I have one theory that always seems to trip people up with many-to-many collections. The problem is that in memory, the associations are made in two places. Both in the car's addons list and in the addon's cars list. In the database, there isn't such a duplication.
The way JPA providers get around this is through the mappedBy attribute. Since you have mappedBy on the car's addons list this means that the relationship is actually controlled by the addon's cars list (confusing I know).
Try adding the following:
addon1.setCarCollection(new ArrayList<Car>());
addon1.getCarCollection().add(car);
addon2.setCarCollection(new ArrayList<Car>());
addon2.getCarCollection().add(car);
before you persist the car.
Generally speaking, I would avoid many-to-many associations. What you really have is an intermediate link table, with a one-to-many and a many-to-one. As soon as you add anything of interest to that link table (e.g. datestamp for when the association was made), poof, you are no longer working with a pure many-to-many. Add in the confusion around the "owner" of the association, and you're just making things a lot harder than they should be.
could you try add
#JoinTable(name = "car_addon",
joinColumns = {
#JoinColumn(name = "id_addon_fk", referencedColumnName = "id_addon")},
inverseJoinColumns = {
#JoinColumn(name = "id_car_fk", referencedColumnName = "id_car")})
to both side
just reverse the joinColumns and inverseJoinColumns
Try adding (fetch = FetchType.EAGER) to your ManyToMany annotation