I want to define my #ManyToMany relationship with JPA Annotations so that relations are not removed when updating entity.
#ManyToMany(targetEntity=Event.class, cascade={CascadeType.ALL}, fetch = FetchType.LAZY)
#JoinTable(
name = "event_user",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "event_id")
)
private Set<Event> events;
and Event class
#ManyToMany(cascade = {CascadeType.ALL}, mappedBy="events", targetEntity=User.class, fetch = FetchType.LAZY)
private Set<User> attending;
I thought setting CascadeType.REMOVE would not trigger deletion when updating but when I call update on a user object, all its related events are removed.
This is from my DAO
#Override
public User update(User entity) {
sessionFactory.getCurrentSession().update(entity);
return entity;
}
When I call update on my entity, Hibernate does:
Hibernate: delete from event_user where user_id=?
The comments on your questions are correct so far. You obviously do not load the entity from the database before updating it. Hence, hibernate updates everything just as it finds it in your entity. So, load the entity (by id?), merge your changes and update it afterwards.
Btw you should also consider using the delete orphans annotation. You would hence make sure that events to a user would also get deleted when setting the event collection to null and not only when removing the entire user.
Related
I am implementing soft delete with Hibernate. Each entity that supports soft-deleting has an attribute for it.
#Column(name = "DELETED")
private boolean deleted;
I have created #FilterDef in package-info.java for package with domain objects.
#FilterDef(name = "deletedFilter",
parameters = #ParamDef(name = "includeDeleted", type = Boolean.class),
defaultCondition = ":includeDeleted = true OR DELETED = false"
)
applied it to all DeleteAware entities
#Filter(name = "deletedFilter")
public class CustomerGroup
and enabled in when using in queries
Session session = em.unwrap(Session.class);
session.enableFilter("deletedFilter")
.setParameter("includeDeleted", fp.isDeleted());
Filter is applied and works correctly for primary entity (for example when I query customers I can see that additional where condition is always applied as needed).
Problem is with filter of association. Let's say Customer entity has collection of CustomerGroup.
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinTable(name = "CUSTOMER_CUSTOMER_GROUP",
joinColumns = #JoinColumn(name = "CUSTOMER_ID"),
inverseJoinColumns = #JoinColumn(name = "CUSTOMER_GROUP_ID"))
private Set<CustomerGroup> groups;
However when I query for Customer, groups collection contains deleted entities. I have turned on sql logging and I can see that condition is not applied for lazy query. However if I change
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
to
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
it works.
Both entities are annotated with #Filter. I have also tried applying #Filter annotation to collection itself success without. For initial testing I have also ensured that filters are not disabled and includeDeleted parameter is always false.
#Where annotation on entities works like a charm, but cannot be disabled (99% percent of queries we want to filter out deleted objects but there is that pesky 1% where we need deleted ones).
We are using Hibernate 6.1.13 provided in WildFly 27 application server. Looks like filters are not applied when relation is lazy loaded. Am I missing something?
I need some advice on how to properly configure a unidirectional many-to-one relationship with with JPA.
I have an entity called ScheduleEntry. Schedule entries need to know their parent schedule entry, but a parent schedule entry doesn't need to know its child entries. For that reason ScheduleEntry looks like this:
#Entity
#Data
public class ScheduleEntry {
[...]
#ManyToOne
#JoinColumn(name = "parent_id", nullable = true)
private ScheduleEntry parent;
#ElementCollection
#CollectionTable(name = "schedule_entry", joinColumns = #JoinColumn(name = "parent_id"))
#Column(name = "recurrenceNumber")
#EqualsAndHashCode.Exclude
private Set<Integer> recurrences;
}
There is no OneToMany side of this relationship.
This works fine when creating entries, setting their parent entry and fetching entries with parent entries.
However, whenever I update a parent ScheduleEntry Hibernate executes a DELETE statement and delete all child entries of the updated ScheduleEntry:
org.hibernate.SQL: update schedule_entry set active=?, cancelled=?, capacity=?, description=?, end_time=?, parent_id=?, recurrence_number=?, recurs_until_time=?, start_time=?, title=? where id=?
org.hibernate.SQL: delete from schedule_entry where parent_id=?
And that's not what I want. I want that child entries keep their reference to the updated parent entry and don't get deleted. I.e. I want to prevent HIbernate from executing the DELETE statement. Any ideas how to achieve that?
PS: The code that executes the update:
public void update(DTO dto) {
jpaRepository.save(mapper.dtoToModel(dto));
}
called from within a REST controller that manages the transaction scope with Spring's #Transactional:
#Transactional
#PutMapping("/{id}")
public ResponseEntity<?> update(#PathVariable("id") Long id, #RequestBody ScheduleEntryDTO dto) {
return super.update(id, dto);
}
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 a Hibernate ManyToMany mapping between data table and user table linked by data_user table. Now I want to update the data table to add one more user to the data. How to update link table(data_user) to add one more entry for the new user?
I, first updated the user collection: data.getUsers().add(user), and then in the DAO layer tried session.saveOrUpdate(data). But it deleted everything in the link table.
Update1: data_user(iddata_user,iddataroom,iduser) was manually created in the db.
Update2 : implemented Hashcode and equals for Data and User.
Update 3: I changed to CascadeType.MERGE. This updates my link table. Also, I am never going to update User table from Data which hibernate was trying when CascadeType was ALL.
org.hibernate.NonUniqueObjectException: a different object with the
same identifier value was already associated with the session.
PS: I am very new to hibernate.
public class Data {
private int dataId;
private Data parentData;
private Set<User> users;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "data_user", joinColumns = { #JoinColumn(name = "iddata") },
inverseJoinColumns = { #JoinColumn(name = "iduser") })
public Set<User> getUsers() {
return users;
}
...
}
You need to override equals() and hashCode() in your entities or you are going to run into issues sooner or latter see this for more details.
If your problem still exists after overriding equals() and hashCode() you may add a mappedBy on the other side of the relation see this for more details.
Let me get my question straight, using the #OnDelete here will delete this and any other InventoryPreference entities if the Inventory entity is deleted? I just can't understand a thing from Hibernate's annotations reference.. so I need your help to confirm that I understood it correctly.
public class InventoryPreference {
...
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "inventory_id", nullable = false)
public Inventory getInventory() {
return inventory;
}
}
Do I then in the Inventory entity need to use CascadeType.ALL too to get all the InventoryPreferences deleted if the Inventory entity is deleted?
public class Inventory {
...
#OneToMany(mappedBy = "inventory", cascade = CascadeType.ALL)
public Set<InventoryPreference> getPreferenceItems() {
return preferenceItems;
}
}
If the first question is true, then I don't see the point of CascadeType.ALL. If it's not then what do each of these do and what annotations and configuration I need to specify to get the InventoryPreferences deleted when Inventory is deleted? Oh and I don't want the Inventory to be deleted if InventoryPreference gets deleted. Sorry if it's too obvious.
They do somewhat different things. #OnDelete is a schema generation instruction. It will add 'on delete cascade' to the end of the DDL generated for the foreign key (or dialect equivalent.) If you're not using hibernate to generate your database, it isn't going to do anything.
The cascade property on the #OneToMany or #ManyToOne is what's used at runtime to generate additional actual SQL statements. That's probably what you actually want, additional delete statements to remove the children, not delete cascades turned on in the database table? If what you want is for InventoryPreferences to get removed when you delete an Inventory, then you want:
#OneToMany(mappedBy = "inventory", cascade = CascadeType.REMOVE, orphanRemoval=true)
public Set<InventoryPreference> getPreferenceItems() {
return preferenceItems;
}
And of course add additional Cascade Types as appropriate to your design.