This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 1a48917e03d8c00c7b47239f31e518fdc7cdda8b
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Mon Feb 23 15:16:17 2026 -0600

    Removed deprecated methods in ClosureEventListener
---
 grails-data-hibernate7/REMOVAL_WARNINGS.md         |   1 -
 .../grails/orm/hibernate/HibernateDatastore.java   |   4 +-
 .../hibernate/support/ClosureEventListener.java    |  10 +-
 .../support/ClosureEventListenerSpec.groovy        | 359 +++++++++++++++++++++
 4 files changed, 367 insertions(+), 7 deletions(-)

diff --git a/grails-data-hibernate7/REMOVAL_WARNINGS.md 
b/grails-data-hibernate7/REMOVAL_WARNINGS.md
index 347ade680f..5af6e42ed2 100644
--- a/grails-data-hibernate7/REMOVAL_WARNINGS.md
+++ b/grails-data-hibernate7/REMOVAL_WARNINGS.md
@@ -9,7 +9,6 @@ Generated from: Hibernate `7.1.11.Final`
 
 | Fully Qualified Class | Line | Warning |
 |---|---|---|
-| `org.grails.orm.hibernate.support.ClosureEventListener` | 342 | 
`EntityMetamodel in org.hibernate.tuple.entity` has been deprecated and marked 
for removal |
 | `org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor` | 286 | 
`EntityMetamodel in org.hibernate.tuple.entity` has been deprecated and marked 
for removal |
 | `org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor` | 305 | 
`EntityMetamodel in org.hibernate.tuple.entity` has been deprecated and marked 
for removal |
 
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
index 2b784df2be..3a739069c5 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
@@ -717,14 +717,14 @@ public class HibernateDatastore extends 
AbstractHibernateDatastore implements Me
     dataSource =
         new MultiTenantDataSource(dataSource, schemaName) {
           @Override
-          public Connection getConnection()  {
+          public Connection getConnection() throws SQLException {
             Connection connection = super.getConnection();
             schemaHandler.useSchema(connection, schemaName);
             return new MultiTenantConnection(connection, schemaHandler);
           }
 
           @Override
-          public Connection getConnection(String username, String password){
+          public Connection getConnection(String username, String password) 
throws SQLException {
             Connection connection = super.getConnection(username, password);
             schemaHandler.useSchema(connection, schemaName);
             return new MultiTenantConnection(connection, schemaHandler);
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventListener.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventListener.java
index 3eaf7d9949..870fd38fc7 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventListener.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventListener.java
@@ -46,8 +46,9 @@ import org.hibernate.engine.spi.ExecutableList;
 import org.hibernate.event.spi.*;
 import org.hibernate.jpa.event.spi.CallbackRegistry;
 import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
+import org.hibernate.metamodel.mapping.AttributeMapping;
+import org.hibernate.metamodel.mapping.EntityMappingType;
 import org.hibernate.persister.entity.EntityPersister;
-import org.hibernate.tuple.entity.EntityMetamodel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.ReflectionUtils;
@@ -339,11 +340,12 @@ public class ClosureEventListener
     Object entity = event.getEntity();
     EntityReflector reflector = persistentEntity.getReflector();
     HashMap<Integer, Object> changedState = new HashMap<>();
-    EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
+    EntityMappingType entityMappingType = persister.getEntityMappingType();
     for (int i = 0; i < propertyNames.length; i++) {
       String p = propertyNames[i];
-      Integer index = entityMetamodel.getPropertyIndexOrNull(p);
-      if (index == null) continue;
+      AttributeMapping attributeMapping = 
entityMappingType.findAttributeMapping(p);
+      if (attributeMapping == null) continue;
+      int index = attributeMapping.getStateArrayPosition();
 
       PersistentProperty property = persistentEntity.getPropertyByName(p);
       if (property == null) {
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/support/ClosureEventListenerSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/support/ClosureEventListenerSpec.groovy
new file mode 100644
index 0000000000..32c3ca50e0
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/support/ClosureEventListenerSpec.groovy
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.grails.orm.hibernate.support
+
+import grails.gorm.annotation.Entity
+import grails.gorm.hibernate.HibernateEntity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import grails.gorm.transactions.Rollback
+import grails.validation.ValidationException
+import org.springframework.orm.hibernate5.HibernateSystemException
+
+class ClosureEventListenerSpec extends HibernateGormDatastoreSpec {
+
+    @Override
+    void setupSpec() {
+        manager.addAllDomainClasses([
+            EventBook,
+            ValidatedBook,
+            MutatingBook,
+        ])
+    }
+
+    void cleanup() {
+        EventBook.callLog.clear()
+    }
+
+    // 
-------------------------------------------------------------------------
+    // beforeInsert
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "beforeInsert is called before a new entity is persisted"() {
+        when:
+        new EventBook(title: "Groovy in Action").save(flush: true, 
failOnError: true)
+
+        then:
+        'beforeInsert' in EventBook.callLog
+    }
+
+    @Rollback
+    void "beforeInsert returning false vetoes the insert"() {
+        given:
+        EventBook.vetoInsert = true
+
+        when:
+        new EventBook(title: "Vetoed Book").save(flush: true)
+
+        then:
+        thrown(HibernateSystemException)
+
+        cleanup:
+        EventBook.vetoInsert = false
+    }
+
+    // 
-------------------------------------------------------------------------
+    // afterInsert
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "afterInsert is called after a new entity is persisted"() {
+        when:
+        new EventBook(title: "Clean Code").save(flush: true, failOnError: true)
+
+        then:
+        'afterInsert' in EventBook.callLog
+    }
+
+    // 
-------------------------------------------------------------------------
+    // beforeUpdate / afterUpdate
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "beforeUpdate is called before an existing entity is updated"() {
+        given:
+        def book = new EventBook(title: "Original Title").save(flush: true, 
failOnError: true)
+        EventBook.callLog.clear()
+
+        when:
+        book.title = "Updated Title"
+        book.save(flush: true, failOnError: true)
+
+        then:
+        'beforeUpdate' in EventBook.callLog
+    }
+
+    @Rollback
+    void "afterUpdate is called after an existing entity is updated"() {
+        given:
+        def book = new EventBook(title: "First Edition").save(flush: true, 
failOnError: true)
+        EventBook.callLog.clear()
+
+        when:
+        book.title = "Second Edition"
+        book.save(flush: true, failOnError: true)
+
+        then:
+        'afterUpdate' in EventBook.callLog
+    }
+
+    // 
-------------------------------------------------------------------------
+    // beforeDelete / afterDelete
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "beforeDelete is called before an entity is deleted"() {
+        given:
+        def book = new EventBook(title: "Ephemeral Book").save(flush: true, 
failOnError: true)
+        EventBook.callLog.clear()
+
+        when:
+        book.delete(flush: true)
+
+        then:
+        'beforeDelete' in EventBook.callLog
+    }
+
+    @Rollback
+    void "afterDelete is called after an entity is deleted"() {
+        given:
+        def book = new EventBook(title: "Gone Book").save(flush: true, 
failOnError: true)
+        EventBook.callLog.clear()
+
+        when:
+        book.delete(flush: true)
+
+        then:
+        'afterDelete' in EventBook.callLog
+    }
+
+    @Rollback
+    void "beforeDelete returning false vetoes the delete"() {
+        given:
+        def book = new EventBook(title: "Protected Book").save(flush: true, 
failOnError: true)
+        Long id = book.id
+        EventBook.vetoDelete = true
+
+        when:
+        book.delete(flush: true)
+
+        then:
+        EventBook.get(id) != null
+
+        cleanup:
+        EventBook.vetoDelete = false
+    }
+
+    // 
-------------------------------------------------------------------------
+    // onLoad / afterLoad
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "onLoad is called when an entity is loaded from the database"() {
+        given:
+        def book = new EventBook(title: "Loaded Book").save(flush: true, 
failOnError: true)
+        session.clear()
+        EventBook.callLog.clear()
+
+        when:
+        EventBook.get(book.id)
+
+        then:
+        'onLoad' in EventBook.callLog
+    }
+
+    @Rollback
+    void "afterLoad is called after an entity is loaded from the database"() {
+        given:
+        def book = new EventBook(title: "After Load Book").save(flush: true, 
failOnError: true)
+        session.clear()
+        EventBook.callLog.clear()
+
+        when:
+        EventBook.get(book.id)
+
+        then:
+        'afterLoad' in EventBook.callLog
+    }
+
+    // 
-------------------------------------------------------------------------
+    // beforeValidate
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "beforeValidate is called before validation runs"() {
+        when:
+        new EventBook(title: "Validated").save(flush: true, failOnError: true)
+
+        then:
+        'beforeValidate' in EventBook.callLog
+    }
+
+    // 
-------------------------------------------------------------------------
+    // failOnError — validation failure throws ValidationException
+    // 
-------------------------------------------------------------------------
+
+    void "validation failure with failOnError throws ValidationException"() {
+        when:
+        ValidatedBook.withTransaction {
+            new ValidatedBook(title: null).save(flush: true, failOnError: true)
+        }
+
+        then:
+        thrown(ValidationException)
+    }
+
+    void "validation failure without failOnError returns null"() {
+        when:
+        def book = ValidatedBook.withTransaction {
+            new ValidatedBook(title: null).save(flush: true)
+        }
+
+        then:
+        book == null || book.hasErrors()
+    }
+
+    // 
-------------------------------------------------------------------------
+    // beforeInsert can mutate state that gets persisted
+    // 
-------------------------------------------------------------------------
+
+    @Rollback
+    void "property mutation in beforeInsert is reflected in the persisted 
state"() {
+        when:
+        def book = new MutatingBook(title: "raw title").save(flush: true, 
failOnError: true)
+        session.clear()
+        def reloaded = MutatingBook.get(book.id)
+
+        then:
+        reloaded.title == "RAW TITLE"
+    }
+
+    @Rollback
+    void "property mutation in beforeUpdate is reflected in the persisted 
state"() {
+        given:
+        def book = new MutatingBook(title: "first").save(flush: true, 
failOnError: true)
+        session.clear()
+
+        when:
+        def loaded = MutatingBook.get(book.id)
+        loaded.title = "second"
+        loaded.save(flush: true, failOnError: true)
+        session.clear()
+        def reloaded = MutatingBook.get(book.id)
+
+        then:
+        reloaded.title == "SECOND"
+    }
+}
+
+// ---------------------------------------------------------------------------
+// Domain class with all event hooks instrumented
+// ---------------------------------------------------------------------------
+
+@Entity
+class EventBook implements HibernateEntity<EventBook> {
+
+    String title
+
+    static callLog = [].asSynchronized() as List<String>
+    static boolean vetoInsert = false
+    static boolean vetoDelete = false
+
+    static mapping = {
+        id generator: 'identity'
+    }
+
+    def beforeInsert() {
+        callLog << 'beforeInsert'
+        return vetoInsert ? false : null
+    }
+
+    def afterInsert() {
+        callLog << 'afterInsert'
+    }
+
+    def beforeUpdate() {
+        callLog << 'beforeUpdate'
+    }
+
+    def afterUpdate() {
+        callLog << 'afterUpdate'
+    }
+
+    def beforeDelete() {
+        callLog << 'beforeDelete'
+        return vetoDelete ? false : null
+    }
+
+    def afterDelete() {
+        callLog << 'afterDelete'
+    }
+
+    def onLoad() {
+        callLog << 'onLoad'
+    }
+
+    def afterLoad() {
+        callLog << 'afterLoad'
+    }
+
+    def beforeValidate() {
+        callLog << 'beforeValidate'
+    }
+}
+
+// ---------------------------------------------------------------------------
+// Domain class for validation tests
+// ---------------------------------------------------------------------------
+
+@Entity
+class ValidatedBook implements HibernateEntity<ValidatedBook> {
+
+    String title
+
+    static mapping = {
+        id generator: 'identity'
+    }
+
+    static constraints = {
+        title nullable: false, blank: false
+    }
+}
+
+// ---------------------------------------------------------------------------
+// Domain class that mutates state in event hooks
+// ---------------------------------------------------------------------------
+
+@Entity
+class MutatingBook implements HibernateEntity<MutatingBook> {
+
+    String title
+
+    static mapping = {
+        id generator: 'identity'
+    }
+
+    def beforeInsert() {
+        title = title?.toUpperCase()
+    }
+
+    def beforeUpdate() {
+        title = title?.toUpperCase()
+    }
+}

Reply via email to