Unable to understand JPA cascade behaviour on delete using hibernate - java

I have three tables/entities which are Event, Participant, and ParticipantEvent. ParticipantEvent is kind of like join table of many to many relationship but I have made it as an entity. And the mapping goes like this.
public class Event {
#OneToMany(mappedBy = "event", cascade=CascadeType.REMOVE)
private List<ParticipantEvent> participantEvents;
}
public class Participant {
#OneToMany(mappedBy = "participant", cascade=CascadeType.ALL)
private List<ParticipantEvent> participantEvents;
}
public class ParticipantEvent {
#ManyToOne
private Event event;
#ManyToOne
private Participant participant;
}
When I delete an event, hibernate does not trigger deletion of ParticipantEvent. It give foreign key constraint violation error until I give ParticipantEvent -> Participant cascade to ALL. This will triggers delete on ParticipantEvent fine, but also deletes data from Participant table as well yet I don't want to delete any data from Participant table.
I am lost here, I don't think ParticipantEvent DML should depend on Participant or Event.

Related

Hibernate delete referencing records after update of the owning record in a #ManyToOne relationship

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);
}

Spring data save updates ID to null

I am simply trying to perform an update of an entity. However hibernate attempts 2 SQL statements, one to perform the correct update and an unwanted second to update the ID alone to null, which causes my application to fail.
I am using Spring Data alongside Hibernate and when performing an update of an Entity, I see the expected update SQL is performed, however when running the application with SQL Server, a subsequent update is attempted which does the following:
update my_table set id=null where id=?
This fails obviously.
Cannot update identity column 'ID'.
Running the same code with H2 I do not see this second update triggered.
Any idea what might be the cause of this behaviour?
I am extending JpaRepository and using the default save().
Here is a snippet of my entity:
#Table(name = "MY_TABLE")
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String anotherValue;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id")
private List<ChildEntity> children = new ArrayList<>();
// getters, builder, private default constructor ...
Snippet building my entity:
MyEntity.newBuilder()
.withId(id)
.withAnotherValue(valueUpdate)
.build();
Repository:
public interface MyRepository extends JpaRepository<MyEntity, Long>
Saving:
myRepository.save(myUpdatedEntity);
As i think of probable cause for this is if you associate two entities with their IDs as foreign keys then hibernate may try to update ID of parent as foreign key of other entity. Its not correct way to associate.
In a one-to-many relation add a foreign key in the many side entity, that have to reference the primary key of the one side entity class.
#Entity
public class MyEntity {
..
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "MYENTITY_ID")
private List<ChildEntity> children = new ArrayList<>();
}

Can Hibernate orphanRemoval work with unique constraints?

I have 2 entities: Role and Privilege. One role has many privileges. The entities look like this:
#Entity
public class Role {
private Integer id;
private String code;
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Privilege> privileges;
}
#Entity
public class Privilege {
private Integer id;
private String code;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
The privilege table has the unique constraint U__ROLE_ID__CODE__PRIVILEGE on the role_id and code columns.
I have a REST endpoint which updates roles. The update includes also changing privileges assigned to a role:
private static void setPrivileges(Set<Privilege> existing, Set<Privilege> privileges) {
existing.clear();
existing.addAll(privileges);
}
By some reason, when I do the update of a role, Hibernate first inserts new privileges into the privilege table, and only then it removes the orphaned privileges. As a result, the update fails with the U__ROLE_ID__CODE__PRIVILEGE constraint violation in case when the new list of privileges contains at least one privilege from the old list.
Without the constraint everything works fine. However, removing the constraint does not look like a perfect solution.
Is it possible to change the order the Hibernate handles the role-privilege relationship update so that first the orphaned privileges are removed and only then the new ones are inserted?
The reproduction project is available here.
You may want to revisit the CascadeType. If you are looking to propagate deletes upon removal of the Role you will want to use CasadeType.REMOVE
Because you have it set to CascadeType.ALL you'll notice that each update/persistence attempt on Role will propagate to the Privilege. Thus conflicting with the the unique constraint on these tables.
// You can flush:
private static void setPrivileges(Set<Privilege> existing, Set<Privilege> privileges) {
existing.clear();
repository.flush(); // queues delete statements before subsequent operations
existing.addAll(privileges);
}

JPA CascadeType.ALL does not work by query?

I have OneToMany bidirectional relationship enity classes (WorkOrder and Task). WorkOrder have one or more Task.
when I delete a WorkOrder enity by query, I get foreign key constraint exception. EntityManager can't delete related Task automatically.
My question : Does Is CascadeType.REMOVE used by em.removed(..) method? Is it not used by query?(delete query).
WorkOrder.java
.....
public class WorkOrder {
....
#Temporal(TemporalType.TIMESTAMP)
private Date expiryDate;
#OneToMany(cascade=CascadeType.ALL, mappedBy="workOrder", orphanRemoval=true)
private List<Task> taskList;
......
}
Task.java
......
public class Task {
.....
#ManyToOne
#JoinColumn(name = "WORK_ORDER_ID", referencedColumnName = "ID")
private WorkOrder workOrder;
.....
}
I need to delete WorkOrder based on expiry date. I need to delete related Task firstly, after that I have to delete WorkOrder. Is it correct?
I think, it will be better CascadeType could used by Query.
As per the spec, DELETE by query (query starting "DELETE FROM ...") does not trigger such callbacks (since it is a way of removing data from the datastore quickly); callbacks are only used by the normal persistence operations (persist, merge, refresh, remove) so if you want their behaviour to be followed then use remove()

Trying to understand difference in CascadeType.ALL vs. #OnDelete!

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.

Categories

Resources