# PreDeleteEventListener: veto deletion of entity with relation

**URL:** <https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649>\
**Category:** Hibernate ORM\
**Created:** [July 15, 2025, 4:50pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649 "2025-07-15T16:50:38Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Felk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/felk/32/2725_2.png) [@Felk](https://discourse.hibernate.org/u/Felk)\
**Post date:** [July 15, 2025, 4:50pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/1 "2025-07-15T16:50:38Z")

</div>

Hello everyone,

My goal is to implement a custom version of soft-deletion.

I am aware that with hibernate-orm 7, timestamp-based soft-deletion is supported out-of-the-box. However, I retrieve the “sys\_valid\_to” timestamp dynamically, so I was unable to fulfill my usecase using that feature.

My current attempt is registering a `PreDeleteEventListener`, in which I `return true` to veto the deletion, and instead manually set my “sys\_valid\_to” field. It looks something like this:

```java
private boolean onPreDelete(PreDeleteEvent event) {
    int validToIndex = -1;
    for (int i = 0; i < event.getPersister().getPropertyNames().length; i++) {
        if (event.getPersister().getPropertyNames()[i].equals("sysValidTo")) {
            validToIndex = i;
            break;
        }
    }

    Instant now = myContext.getTimestamp();

    Object[] deletedState = event.getDeletedState();
    Object[] oldDeletedState = new Object[deletedState.length];
    System.arraycopy(deletedState, 0, oldDeletedState, 0, deletedState.length);

    event.getPersister().setValue(entity, validToIndex, now);
    deletedState[validToIndex] = now;

    // Instead of actually deleting the entity, we instead issue an UPDATE that sets the entity's sys_valid_to to 'now'
    event.getPersister().getUpdateCoordinator().update(
        entity, event.getId(), null, deletedState, event.getPersister().getVersion(entity),
        oldDeletedState,
        new int[]{validToIndex},
        false, event.getSession());

    return true; // veto deletion: we performed our soft-delete above and wand no actual DELETE to be executed
}

```

This works fine for regular entities, but let’s consider an entity that has a relation like this:

```java
@Table(name = "T_PARENT_ENTITY")
@Entity
public class MyParentEntity {

    @Id
    @Column(name = "PARENT_ID")
    private Long id;

    @Column(name = "SYS_VALID_TO")
    private Instant sysValidTo;

    @ManyToMany
    @JoinTable(
        name = "T_PARENT_TO_CHILD",
        joinColumns = @JoinColumn(name = "PARENT_LIST", nullable = false),
        inverseJoinColumns = @JoinColumn(name = "CHILD_ELEMENT", nullable = false)
    )
    private Set<MyChildEntity> children;
}

```

Now, the deletion of the `MyParentEntity` gets vetoed and prevented, but the deletion of any rows in the `T_PARENT_TO_CHILD` proceed. In fact, enabling SQL printing shows that those rows get deleted before the PreDeleteEventListener even runs; if I don’t veto the deletion, this is the chronology of statements:

```auto
[Hibernate] 
    delete 
    from
        T_PARENT_TO_CHILD
    where
        PARENT_LIST=?
...
[Hibernate] 
    delete 
    from
        T_PARENT_ENTITY
    where
        PARENT_ID=?

```

which makes sense of course, constraint-wise.

The `PreCollectionRemoveEventListener` event looked promising, but due to the fact that this one unfortunately does not allow any vetoing (the return type is `void`), I had no luck with it either.  
Regarding the order, I could probably get away with looking at the event’s `getAffectedOwnerOrNull` to determine whether I need a veto there or not, even before my `PreDeleteEventListener` visited the owner.

Is there a way to “fully” veto the deletion of an entity?

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [July 16, 2025, 8:57am UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/2 "2025-07-16T08:57:03Z")

</div>

> [@Felk](#):
>
> However, I retrieve the “sys\_valid\_to” timestamp dynamically, so I was unable to fulfill my usecase using that feature.

What does that mean, can you please elaborate? Do you have a formula for the deleted timestamp? If so, how does that look and work? Do you set that yourself somehow through delete triggers?

Do you know that there is a `SoftDeleteType.TIMESTAMP` which would do exactly what you’re describing here? Also see [Hibernate ORM User Guide](https://docs.jboss.org/hibernate/orm/7.0/userguide/html_single/Hibernate_User_Guide.html#soft-delete-type)

> [@Felk](#):
>
> Is there a way to “fully” veto the deletion of an entity?

There is no supported way to veto the update of a collection. AFAICT, you’d have to provide a custom `CollectionPersister` to achieve that and dive into the guts of Hibernate ORM.

---

<div class="post-metadata">

**Author:** ![Felk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/felk/32/2725_2.png) [@Felk](https://discourse.hibernate.org/u/Felk)\
**Post date:** [July 16, 2025, 2:54pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/3 "2025-07-16T14:54:54Z")

</div>

> Do you know that there is a `SoftDeleteType.TIMESTAMP` which would do exactly what you’re describing here?

I am aware, yes. That feature almost does what I need it to do, but just almost. What I meant by the “sys\_valid\_to” timestamp being retrieved dynamically is basically this:

```sql
-- This is technically what @SoftDelete(strategy = SoftDeleteType.TIMESTAMP) does:
UPDATE mytable SET deleted = CURRENT_TIMESTAMP
-- But this is what I need
UPDATE mytable SET deleted = :deleted_at

```

With “deleted\_at” being a timestamp provided at runtime by a @TransactionScoped CDI bean. In the example code above, that bean was called `myContext`. It’s done this way so that we can create one timestamp at the start of a transaction and use that timestamp for all deletions within that transactions.  
I thought about subclassing the Dialect and override `currentTimestamp`, but mocking away the database’s `CURRENT_TIMESTAMP` feels like it will just introduce other problems.

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [July 17, 2025, 2:10pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/4 "2025-07-17T14:10:35Z")

</div>

> [@Felk](#):
>
> It’s done this way so that we can create one timestamp at the start of a transaction and use that timestamp for all deletions within that transactions.

It seems like you don’t understand how `CURRENT_TIMESTAMP` works within a transaction, because that’s exactly what the semantics are.

---

<div class="post-metadata">

**Author:** ![Felk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/felk/32/2725_2.png) [@Felk](https://discourse.hibernate.org/u/Felk)\
**Post date:** [July 17, 2025, 3:23pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/5 "2025-07-17T15:23:43Z")

</div>

> It seems like you don’t understand how `CURRENT_TIMESTAMP` works within a transaction, because that’s exactly what the semantics are.

I wish they were, but no, at least not in oracle:

```auto
MYDB_OWNER> declare
                t1 timestamp;
                t2 timestamp;
            begin
                select current_timestamp into t1 from dual;
                dbms_session.sleep(1);
                select current_timestamp into t2 from dual;
                if t1 = t2 then
                    dbms_output.put_line('Timestamps are equal: ' || t1);
                else
                    dbms_output.put_line('Timestamps are different: ' || t1 || ' and ' || t2);
                end if;
            end;
[2025-07-17 17:22:59] completed in 1 s 8 ms
Timestamps are different: 17.07.25 15:22:58,158703 and 17.07.25 15:22:59,158860

```

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [July 17, 2025, 4:33pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/6 "2025-07-17T16:33:45Z")

</div>

It looks like you are right. I was working with PostgreSQL for too long it seems: [PostgreSQL: Documentation: 11: 9.9.&nbsp;Date/Time Functions and Operators](https://www.postgresql.org/docs/11/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT)

It seems though, that this SQL fragment could produce a transaction time on Oracle if you have the necessary permissions:

```sql
SELECT tx.START_DATE
FROM v$transaction tx 
JOIN v$session s ON tx.addr=s.taddr 
WHERE s.sid = sys_context('userenv','sid')

```

If you put that into your dialect, you could get around this AFAIU e.g.

```java
public class MyOracleDialect extends OracleDialect {
	@Override
	public String currentTimestamp() {
		return "(SELECT tx.START_DATE FROM v$transaction tx JOIN v$session s ON tx.addr=s.taddr WHERE s.sid = sys_context('userenv','sid'))";
	}
}

```

---

<div class="post-metadata">

**Author:** ![Felk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/felk/32/2725_2.png) [@Felk](https://discourse.hibernate.org/u/Felk)\
**Post date:** [July 17, 2025, 5:21pm UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/7 "2025-07-17T17:21:40Z")

</div>

That looks promising, I’ll give it a shot soon, thanks!

---

<div class="post-metadata">

**Author:** ![Felk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/felk/32/2725_2.png) [@Felk](https://discourse.hibernate.org/u/Felk)\
**Post date:** [July 18, 2025, 6:53am UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/8 "2025-07-18T06:53:01Z")

</div>

No luck so far. Your idea falls flat for me because I checked and we won’t be able to give our database access user those permissions.  
Of course, I could still try to overwrite the dialect and supply an application-provided `currentTimestamp()` (e.g. by having it return a constant `TO_TIMESTAMP(...some constant...)`). Given that other databases like Postgres and apparently also H2 do guaranteee a stable `CURRENT_TIMESTAMP` per transaction, that alleviates my worries of it being a bad idea.

However, I still went ahead and tried to make `@SoftDelete` working, but was stopped by a whole different issue:

> org.hibernate.MappingException: Column ‘SYS\_VALID\_TO’ is duplicated in mapping for entity ‘org.acme.MyEntity’ (use ‘@Column(insertable=false, updatable=false)’ when mapping multiple properties to the same column)

But we do need to have that column mapped in an insertable, updatable way.

If you’re interested in the why: We’re emulating system versioned tables application-side, since oracle’s `AS OF TIMESTAMP` operates on the undo tablespace but we need to be able to go back arbitrarily far into the past. For this we have a `PreUpdateEventListener` that captures the pre-update state and inserts it as an old version, setting the `SYS_VALID_TO` to “now”.

My gut tells me to not go down this particular rabbit hole any further and instead think about alternative solutions, but any suggestions are welcome. So far, thanks for sticking with me.

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [July 18, 2025, 8:57am UTC](https://discourse.hibernate.org/t/predeleteeventlistener-veto-deletion-of-entity-with-relation/11649/9 "2025-07-18T08:57:14Z")

</div>

> [@Felk](#):
>
> But we do need to have that column mapped in an insertable, updatable way.

Are you saying that you want to manually update the value of the column? Because that certainly isn’t supported out of the box. Updating the column through native queries would be the way to go for now, though we’d be curious to hear about your use case. You’re not the first one to ask for this and we might add features to cover more use cases once we know the bigger picture.

> [@Felk](#):
>
> If you’re interested in the why: We’re emulating system versioned tables application-side, since oracle’s `AS OF TIMESTAMP` operates on the undo tablespace but we need to be able to go back arbitrarily far into the past. For this we have a `PreUpdateEventListener` that captures the pre-update state and inserts it as an old version, setting the `SYS_VALID_TO` to “now”.

I’d be curious to hear more about this. In fact, I brought up the idea to rethink Hibernate Envers on top of [system/application versioned tables](https://github.com/hibernate/hibernate-orm/discussions/7072), but so far, we didn’t dig into that further yet.

It sounds to me like you try to re-implement what Hibernate Envers does though. Note that it does take care of inserting data into separate tables and can also be configured to store such timestamps: [Hibernate ORM User Guide](https://docs.jboss.org/hibernate/orm/7.0/userguide/html_single/Hibernate_User_Guide.html#envers-audit-strategy)
