I have a the two following classes:
#Entity
class A {
#Id
private aId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "AB", joinColumns = #JoinColumn(name = "aId", referencedColumnName = "aId"), inverseJoinColumns = #JoinColumn(name = "bId", referencedColumnName = "bId"))
private Set<B> bSet;
}
#Entity
class B {
#Id
private bId;
}
I load the complete object structure from one database and then enters a new transaction on the second database to persist the structure again. However the "AB" table is left empty. This is very strange as "B" is persisted though I only explicitly persist "A". I have check that A-objects contains non empty sets of B, so that is not a problem.
This leaves me with the conclusion that Hibernate believes that "AB"-table should exist as both "A" and "B" already have their primary keys. Is there a way around this so I can get Hibernate to persist the join-table in the second database?
I guess this is happening because you are using proxy object.That is if you create instances of A and B with new operator and then call persist ,Join table record will be created .But you are using object from obtained from entitymanager(these are proxy objects) so you have to merge object that way entitymanager will create new proxies of this objects.
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 want to delete Recipe (using spring data DAO) but I got SQL exception: org.postgresql.util.PSQLException: ERROR: update or delete on table "recipe" violates foreign key constraint "fkacys689tmdmfggtf4thdoc83k" on table "favourite_recipes"
Detail: Key (id)=(76823) is still referenced from table "favourite_recipes".
My entities:
#Entity
public class Account {
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "favourite_recipes",
joinColumns = #JoinColumn(name = "account_id"),
inverseJoinColumns = #JoinColumn(name = "recipe_id"))
private Set<Recipe> favouriteRecipes = new HashSet<>(0);
...
}
#Entity
public class Recipe {
...
}
How to remove recipe instance?
You need to handle the cascade type, by default is set to ALL.
For example you can work around the contraints like this:
#ManyToMany(cascade = CascadeType.DETACH)
more info : cascade type docs
in you need to delete from the owning entity side which is the Account.
So first remove the recipe from recipe list in Account and save the account, then remove the recipe itself.
As Amer Qarabsa metioned I had to remove recipe from Account.
I added new field in Recipe to get bidirectional mapping
#ManyToMany(cascade = CascadeType.MERGE, mappedBy = "favouriteRecipes")
private Set<Account> recipeLovers = new HashSet<>(0);
Code in service class to remove recipe from all accounts + clear lovers in recipe (recipe and recipeId variables are not initialized here)
Set<Account> recipeLovers = recipe.getRecipeLovers();
recipeLovers.forEach(account ->
account.getFavouriteRecipes()
.removeIf(r -> r.getId() == recipeId));
recipeLovers.clear();
recipeDao.delete(recipe);
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 have a fairly straightforward one-to-many relationship
[SampleAliasMask] has many [SampleAliasMaskPart]
My problem is that when I persist a new instance of SampleAliasMask with collection parts I get an constraint violation that the foreign key link from the tables of SampleAliasMaskPart to SampleAliasMask is being set to NULL.
I am mapping using hibernate annotations as such:
#Entity
#Table(name="SAMPLE_ALIAS_MASK")
public class SampleAliasMask extends ClientEntity {
#OneToMany(mappedBy = "sampleAliasMask", fetch = FetchType.EAGER, cascade = javax.persistence.CascadeType.ALL, orphanRemoval = true)
#Cascade(CascadeType.ALL)
#Length(min = 1, message = "The sample alias mask must have components")
private Set<SampleAliasMaskPart> components;
With the other half of the relationship mapped as so:
#Entity
#Table(name="SAMPLE_ALIAS_MASK_PART")
public class SampleAliasMaskPart extends ClientEntity {
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "SAMPLE_ALIAS_MASK_ID", nullable = false)
private SampleAliasMask sampleAliasMask;
The relevant part of ClientEntity is
#MappedSuperclass
public abstract class ClientEntity {
#Id
#Column(name="ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
I am creating the parts like this:
HashSet<SampleAliasMaskPart> components = new HashSet<>();
for(Map<String, Object> c : this.components) {
SampleAliasMaskPart component = new SampleAliasMaskPart(Integer.parseInt(c.get("value").toString(), 10), c.get("name").toString());
result.validate(component);
components.add(component);
}
mask.setComponents(components);
The exact error I get is:
java.sql.BatchUpdateException: ORA-01400: cannot insert NULL into ("ST"."SAMPLE_ALIAS_MASK_PART"."SAMPLE_ALIAS_MASK_ID")
I suspect the issue has to do with the fact that I never explicitly set SampleAliasMaskPart.sampleAliasMask but why do I need to? That relationship is never exposed nor navigated. That field is only there for mapping purposes which makes me think that I'm mapping this wrong.
Your assumption is correct. Hibernate uses the owning side of an association to know is the association exists or not. And the owning side ai the side where there is no mappedBy attribute.
The general rule is that when you have a bidirectional association, it's your responsibility to make the object graph coherent by initializing/modifying both sides of the association. Hibernate doesn't care much about it, but if you don't initialize the owning side, it won't persist the association.
Note that you're not forced to make this association bidirectional. If you don't, then adding the part to the mask will be sufficient, because this side (which is the unique side) is the owning side.
JB Nizet suggested correctly. There are two ways you can solve it:
Removing the bi-directional relationship:Remove the annotation #ManyToOne(optional = false, fetch = FetchType.LAZY) from the simpleAliasMask in SampleAliasMaskPart
Add the mask to each component by doing something like component.setSimpleAliasMask(mask). This will do the bidirectional relationship.
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