I have CATEGORY, AD and CATEGORY_AD table, typical many to many relationship. Somehow nothing is inserted into CATEGORY_AD table. What am I missing?
In Category.java:
#ManyToMany
#JoinTable(name = "CATEGORY_AD", joinColumns = {
#JoinColumn(name = "CATEGORY_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "AD_ID", referencedColumnName = "ID") })
private List<Ad> ads;
In Ad.java:
#ManyToMany(mappedBy = "ads")
private List<Category> categories;
In my Service class:
Category laCat = new Category();
laCat.setId(categoryId);
laCat.getAds().add(ad);
ad.getCategories().add(laCat);
ad = adRepository.saveAndFlush(ad);
You are saving the 'owned' side instead of the 'owning' side.
Every ManyToMany relationship must have an owner, which in your case is Category. You can see that because in Category you have the definition of the #JoinTable in the ads List, and in Ad you refer to that list by #ManyToMany(mappedBy = "ads").
Whenever you save the owning side of the relationship, this will trigger a save on the join table too, but not the other way around. So saving the owned side (Ad) will do nothing on the CATEGORY_ADtable.
You should do something like this:
Category laCat = new Category();
laCat.setId(categoryId);
laCat.getAds().add(ad);
// Next line will insert on CATEGORY and CATEGORY_AD tables
laCat = categoryRepository.saveAndFlush(category);
// We add the category to the ad object to keep both sides in sync
ad.getCategories().add(laCat);
You can see that even if a save on Category triggers a save on the join table, it's still our responsibility manually add the category to the categories Listin the ad object so both sides are in sync, having the same elements. And there's no need to save the ad object already.
Try to save Category object also (before you invoke ad.getCategories().add(laCat);)
Please use hibernate #Cascade annotations on List<Category> categories;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
#ManyToMany(mappedBy = "ads")
#Cascade(value = CascadeType.SAVE_UPDATE)
private List<Category> categories;
If you prefer using JPA annotations then you can try this:
#ManyToMany(mappedBy = "ads", cascade = CascadeType.PERSIST)
#Cascade(value = CascadeType.SAVE_UPDATE)
private List<Category> categories;
Please check this thread: what is cascading in hibernate
Related
The Problem
I have a 1:n relation, but the n side shouldnt rely on constraints. So i actually wanna insert a EntityPojo via its future id, when its not saved yet ( Lets ignore that its a bad practice ). This looks kinda like this.
var relation = new RelationshipPojo();
.
.
.
relation.targets.add(session.getReference(futureID, EntityPojo.class));
session.save(relation);
// A few frames later
session.save(theEntityPojoWithTheSpecificId);
Cascading is not possible here, i only have its future ID, not a reference to the object i wanna save. Only its id it will have in the future.
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
.
.
.
#ManyToMany(cascade = {}, fetch = FetchType.EAGER)
public Set<EntityPojo> targets = new LinkedHashSet<>();
}
Question
How do we tell hibernate that it should ignore the constraints for this 1:n "target" relation ? It should just insert the given ID into the database, ignoring if that EntityPojo really exists yet.
Glad for any help on this topic, thanks !
For a much simpler solution, see the EDIT below
If the goal is to insert rows into the join table, without affecting the ENTITY_POJO table, you could model the many-to-many association as an entity itself:
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
#OneToMany(cascade = PERSIST, fetch = EAGER, mappedBy = "relationship")
public Set<RelationShipEntityPojo> targets = new LinkedHashSet<>();
}
#Entity
public class RelationShipEntityPojo {
#Column(name = "entity_id")
private Long entityId;
#ManyToOne
private RelationshipPojo relationship;
#ManyToOne
#NotFound(action = IGNORE)
#JoinColumn(insertable = false, updatable = false)
private EntityPojo entity;
}
This way, you'll be able to set a value to the entityId property to a non-existent id, and if an EntityPojo by that id is later inserted, Hibernate will know how to populate relationship properly. The caveat is a more complicated domain model, and the fact that you will need to control the association between RelationshipEntityPojo and EntityPojo using the entityId property, not entity.
EDIT Actually, disregard the above answer, it's overly complicated. Turing85 is right in that you should simply remove the constraint. You can prevent Hibernate from generating it in the first place using:
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinTable(inverseJoinColumns = #JoinColumn(name = "target_id", foreignKey = #ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT)))
public Set<EntityPojo> targets = new LinkedHashSet<>();
The only caveat is that when you try to load RelationshipPojo.targets before inserting the missing EntityPojo, Hibernate will complain about the missing entity, as apparently #NotFound is ignored for #ManyToMany.
I have major performance issues when I try to map an entity into a response.
This is the entity:
#Entity
#Table(name = "MyEntity")
public class MyEntity extends BaseEntity {
#Column(name = "someOtherId", nullable = false)
private String someOtherId;
#ElementCollection
#CollectionTable(name = "Phones", joinColumns = #JoinColumn(name = "myEntityId"))
#Column(name = "phone")
private List<String> phones; // <------- we care about this
#ElementCollection
#CollectionTable(name = "Websites", joinColumns = #JoinColumn(name = "myEntityId"))
#Column(name = "websites")
private List<String> websites; // <------- we care about this
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "myEntity")
private List<ContactEntity> bbb;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "myEntity")
private List<AddressEntity> ccc;
}
This is how I use the DAL to fetch it:
List<MyEntity> findByTenantIdAndIdIn(String someOtherId, Set<String> MyEntityIds);
Now when I iterate over List<MyEntity> to map it, and call myEntity.getPhones(), I see that a DB call is being made, which is what causes the slowdown, a 70 seconds slowdown on 1000 entities.
So what can I do to force it to join in the first query it did when I called findByTenantIdAndIdIn?
Notes:
Phones is a simple table with columns: [myEntityId, phone]
The same problem happens with Websites
This has nothing to do with it being an #ElementCollection. Like you figured out, you can use subselect fetching or could also use a batch size for select fetching(the default strategy). Another possibility is to use a fetch join in the query, but be careful when fetch joining multiple collections as that might create a cartesian product which leads to a performance problem cause by too many rows being transfered. A fetch join example HQL query looks like this:
SELECT e FROM MyEntity e LEFT JOIN FETCH e.phones LEFT JOIN FETCH e.websites
I solved it right after I posted this.
I annotated phones and websites with #Fetch(FetchMode.SUBSELECT) which creates a parallel subquery.
Another way to solve this is simply to not use #ElementCollection because it has bad performance, use an Entity instead as they recommend in the video here: https://thorben-janssen.com/hibernate-tips-query-elementcollection/
I have two entities:
User
Work
they have many-to-many relationship with each-over.
#ManyToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JoinTable(
name = "user_work",
joinColumns = #JoinColumn(name = "work_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> workUsers;
when i add new work using JPA :
List<UserDto> users = new ArrayList<>();
users.add(new User(something something));
WorkDto work1 = new WorkDto(1, users));
workRepository.save(workMapper.fromDto(work1));
problem : when i save my work entity in the database it does not save user, so when i extract it , work says that is has no users.
How can i insert work into database and add users to it as well? I have mutual table with work_id and user_id of course
Are you executing it within a transaction with #Transactional? Have you #Override the equals method?
More detail is needed like the relationship in the User class
I have table Animal with OneToMany mapping to table EventAnimal:
#OneToMany(mappedBy = "animal")
public Set<EventAnimal> getEventAnimals() {
return eventAnimals;
}
Table EventAnimal looks like this
#Entity
#Table(name = "eventAnimal")
public class EventAnimal {
#Id
int id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "userEvent_id")
UserEvent userEvent;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "animal_id", nullable=false)
Animal animal;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "eventAnimalClass_id")
EventAnimalClass eventAnimalClass;
}
When I add Event animal to animal and save animal, database is not being updated:
//Create EventAnimal object, set properties
eventAnimal.setUserEvent(newEvent);
eventAnimal.setAnimal(animal);
animal.getEventAnimals().add(eventAnimal);
animalPersistenceService.saveAnimal(animal);
What am I doing wrong?
When I try inserting Event animal, like eventAnimalDao.insert(eventAnimal);
instead of
animalPersistenceService.saveAnimal(animal);
I get exception that "animal_id" does not have default value even though I set it.
What is your ID generation strategy? Are you generating the ids by yourself or you will leave this to the DB? If it will be database put:
#GeneratedValue(strategy=GenerationType.AUTO)
You may want to update the key to Integer instead of int too but don`t think this is the problem.
Also if you want to add event in animal and expect to persist it update your mapping to:
#OneToMany(mappedBy = "animal", cascade = CascadeType.ALL)
I have table Company with OneToMany mapping to table Customer:
//bi-directional many-to-one association to Customer
#OneToMany(mappedBy="company")
private Set<Customer> customer;
I have mapped from tis format:
//bi-directional many-to-one association to Company
#ManyToOne
#JoinColumn(name="company_id")
private Company company;
I have use this code, Sucessfully add,edit and delete function is working.
Add function :
Customer customer = new Customer();
Company company = new Company();
company.setCompanyId(intCompanyId);
customer.setCompany(company);
I resolved the problem. It was rather silly mistake - script didn't remove one foreign key from table and I didn't care to look into SQL table.
I have two classes, say Group and Person with a ManyToMany-Relation that is mapped in a JoinTable.
If I delete a Person that has a relation to a Group, I want to delete the entry from the join table (not delete the group itself!).
How do I have to define the cascade-Annotations? I didn't found a really helpful documentation but several unsolved board discussions...
public class Group {
#ManyToMany(
cascade = { javax.persistence.CascadeType.? },
fetch = FetchType.EAGER)
#Cascade({CascadeType.?})
#JoinTable(name = "PERSON_GROUP",
joinColumns = { #JoinColumn(name = "GROUP_ID") },
inverseJoinColumns = { #JoinColumn(name = "PERSON_ID") })
private List<Person> persons;
}
public class Person {
#ManyToMany(
cascade = { javax.persistence.CascadeType.? },
fetch = FetchType.EAGER,
mappedBy = "persons",
targetEntity = Group.class)
#Cascade({CascadeType.?})
private List<Group> group;
}
Cascade will not clean up the leftover references to the deleted Person that remain on the Group object in memory. You have to do that manually. It seems like cascade should do this, but sadly that's not the way it works.
Based on the info provided in your question, I don't think you need any cascade options set on your Person or Group entities. It doesn't sound like they share a parent/child relationship where the existence of one depends upon the other. That's the kind of relationship where I would expect to see some cascade options.
I believe what you want is:
cascade = CascadeType.ALL
To remove the DB relation, remove the association from each group. Remove the person from the Group.persons collection and remove the Group from the Person.group collection, then persist your person object.
You can probably do it on a database specifically (depends on your database and it capabilities). By adding "on delete cascade" to the foreign key of the relationship table.