I have a many to many relationship. Task2Group.
I'm trying to update some base info of a task using the save() method of spring-boot-jpa.
But when I do not set the groupSet of the task I try to update, and save it, the old task2group relationship will be removed.
I know reason but I just wanna konw how to update the task entity without geting the GroupSet and then set it.
Here are the Task entity related to group
#ManyToMany(cascade = {CascadeType.REFRESH})
#JoinTable(name = "task_2_group", joinColumns = #JoinColumn(name = "task_id"),
inverseJoinColumns = #JoinColumn(name = "group_id"))
private Set<Group> groupSet;
and here are the group
#ManyToMany(mappedBy = "groupSet")
private Set<Task> taskSet;
Related
I am working on a Spring Boot Project in which I have used a Many TO Many relationship between two entities- User and Category.
For User-
//Set of categories a user is following
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinTable(name = "user_category",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "category_id",
referencedColumnName = "categoryId"))
private Set<Category> categories=new HashSet<>();
For Category-
// set of users who follow the category
#ManyToMany(mappedBy = "categories",cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
Set<User> users = new HashSet<>();
What I want-
When I delete a User only user should be deleted and not the Category.
When I delete a Category only category should be deleted and not the User.
Problem-
When I delete a User, it gets deleted successfully without affecting category. But, when I delete a Category, I get an error-
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`blog_application_database`.`user_category`, CONSTRAINT `FK4x6ipr43cfvhqc3aqda6j8c2l` FOREIGN KEY (`category_id`) REFERENCES `categories` (`category_id`))
Now, to remove the associated users, in the category I use-
#PreRemove
public void removeUsers() {
System.out.println("Removing users for " + this.categoryName + " before deleting");
this.users=null;
this.users=new HashSet<>();
}
But, the problem is not resolved.
One approach I came across from several other posts was to manually remove each and every mapping from each user (from user side) but it would be highly inefficient.
I am adding a screenshot of the db tables-
Please help me understand what is going wrong here and how to resolve it.
Changing the Category#users collections has no effect on the relationship, because that is not the owning side of it. The owning side is the one which has no use of mappedBy.
So if you want to remove a category, you will first have to remove the category from every User#categories collection.
Maybe it's easier if you don't map that relationship as a #ManyToMany association and instead introduce an entity for the join table. Then you can use #OneToMany on both sides with DELETE cascading.
In my springboot application I have these 3 entities :
#Entity
public class Process {
#Id
private Long Id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "input_id")
private Input input;
...
}
#Entity
public class Input{
#Id
private Long Id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "template_id")
private Template template;
...
}
#Entity
public class Template{
#Id
private Long Id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "template_id")
private Template template;
private String name;
...
}
In summary, Process has an FK to Input and Input has an FK to Template.
I would like to filter the processes whose template have a certain name. Here is the SQL I would to perform something like that:
select
*
from
process p
left outer join
input i
on p.input_id=i.id
left outer join
template t
on i.template_id=t.id
where
t.name='templateName'
Here is what I currently have in my Process entity to access the template :
#ManyToOne
#JoinTable(name = "Input",
joinColumns = {#JoinColumn(table = "Input", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "template_id", referencedColumnName = "id", table = "Template")})
private Template template;
Here is my ProcessRepository class, where I now have access to the desired find method :
#Repository
public interface ProcessRepository extends PagingAndSortingRepository<Process, Long> {
...
List<Process> findByTemplateNameEquals(String templateName);
...
}
When I execute the findByTemplateNameEquals method, I retrieve the process and one template. But the result I got was not the one expected.
I enabled the sql logging and here is the query really performed (I hide the columns, it is not important here):
select
...
from
process process0_
left outer join
input process0_1_
on process0_.id=process0_1_.id
left outer join
template template1_
on process0_1_.template_id=template1_.id
where
template1_.name=?
There is one problem with the join between Process and Input. It executes
from
process process0_
left outer join
input process0_1_
on process0_.id=process0_1_.id
instead of
from
process process0_
left outer join
input process0_1_
on process0_.input_id=process0_1_.id
I don't understand why it use the PK of Process instead of the FK to Input.
I tried several things to solve this :
Adding name="input_id" in the joinColumns = {#JoinColumn(... but instead of replacing the FK, it replaces the PK of input => failure during execution
replacing the referencedColumnName by "input_id" in the joinColumns = {#JoinColumn(... but it failed at launching.
Configuring a #ForeignKey(name = "input_id") at several places (directly in the #JoinTable, in the #JoinColumn and even in the #JoinColumn of the Input input attribute ) but there was no change.
I also remarked that the joinColumns = {#JoinColumn(table = "Input", referencedColumnName = "id")} was not necessary, because I have the same behaviour if I remove it.
Could someone help me on this ?
Many thanks in advance
I think the declaration of the template field at the Process level is probably unnecessary because you already have the relationship with Input, and certainly error prone.
In any case, if necessary, I would define the field with something like that instead:
#ManyToOne
#JoinTable(
name = "Input",
joinColumns = { #JoinColumn(name = "input_id")},
inverseJoinColumns = {#JoinColumn(name = "template_id")}
)
private Template template;
Pease, verify the code.
Having said that, if you only need to obtain the repositories associated with a certain template by name you can try to navigate through the object hierarchy in your find method. Please, try:
#Repository
public interface ProcessRepository extends PagingAndSortingRepository<Process, Long> {
...
List<Process> findByInput_Template_Name(final String templateName);
...
}
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);
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
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.