You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2025/08/29 02:50:05 UTC
(grails-core) 02/04: More fixes and consolidation and testing
This is an automated email from the ASF dual-hosted git repository.
borinquenkid pushed a commit to branch merge-hibernate6
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 8eb19bf37e5c7b2a7bdf30c2a3b8e2ebedfba94d
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Thu Aug 28 21:38:04 2025 -0500
More fixes and consolidation and testing
---
.../orm/hibernate/AbstractHibernateDatastore.java | 2 +-
.../AbstractHibernateGormInstanceApi.groovy | 480 ---------------------
.../orm/hibernate/HibernateGormInstanceApi.groovy | 371 +++++++++++++++-
.../grails/orm/hibernate/cfg/InstanceProxy.groovy | 6 +-
.../support/ClosureEventTriggeringInterceptor.java | 2 +-
.../specs/validation/UniqueWithHasOneSpec.groovy | 13 +-
.../hibernate/HibernateGormInstanceApiSpec.groovy | 172 ++++++++
.../data/testing/tck/tests/InheritanceSpec.groovy | 2 +-
.../data/testing/tck/tests/ValidationSpec.groovy | 4 +-
9 files changed, 550 insertions(+), 502 deletions(-)
diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java
index 6ac2bf79e8..458dd4832b 100644
--- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java
+++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java
@@ -312,7 +312,7 @@ public abstract class AbstractHibernateDatastore extends AbstractDatastore imple
public void destroy() {
if (!this.destroyed) {
super.destroy();
- AbstractHibernateGormInstanceApi.resetInsertActive();
+ HibernateGormInstanceApi.resetInsertActive();
try {
connectionSources.close();
} catch (IOException e) {
diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormInstanceApi.groovy b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormInstanceApi.groovy
deleted file mode 100644
index cd477b9f74..0000000000
--- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormInstanceApi.groovy
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * Copyright 2013 the original author or authors.
- *
- * Licensed 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
- *
- * http://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
-
-import grails.gorm.validation.CascadingValidator
-import groovy.transform.CompileDynamic
-import groovy.transform.CompileStatic
-import jakarta.persistence.FlushModeType
-import org.grails.datastore.gorm.GormValidateable
-import org.grails.datastore.mapping.core.Datastore
-import org.grails.datastore.mapping.dirty.checking.DirtyCheckable
-import org.grails.datastore.mapping.model.config.GormProperties
-import org.grails.datastore.mapping.model.types.Embedded
-import org.grails.datastore.mapping.proxy.ProxyHandler
-import org.grails.datastore.mapping.reflect.ClassUtils
-import org.grails.datastore.mapping.reflect.EntityReflector
-import org.grails.orm.hibernate.support.HibernateRuntimeUtils
-import org.grails.datastore.gorm.GormInstanceApi
-import org.grails.datastore.mapping.engine.event.ValidationEvent
-import org.grails.datastore.mapping.model.PersistentEntity
-import org.grails.datastore.mapping.model.PersistentProperty
-import org.grails.datastore.mapping.model.types.Association
-import org.grails.datastore.mapping.model.types.ToOne
-import org.hibernate.*
-import org.springframework.beans.BeanWrapperImpl
-import org.springframework.beans.InvalidPropertyException
-import org.springframework.dao.DataAccessException
-import org.springframework.validation.Errors
-import org.springframework.validation.Validator
-
-
-/**
- * Abstract extension of the {@link GormInstanceApi} class that provides common logic shared by Hibernate 3 and Hibernate 4
- *
- * @author Graeme Rocher
- * @param <D>
- */
-@CompileStatic
-abstract class AbstractHibernateGormInstanceApi<D> extends GormInstanceApi<D> {
- private static final String ARGUMENT_VALIDATE = "validate"
- private static final String ARGUMENT_DEEP_VALIDATE = "deepValidate"
- private static final String ARGUMENT_FLUSH = "flush"
- private static final String ARGUMENT_INSERT = "insert"
- private static final String ARGUMENT_MERGE = "merge"
- private static final String ARGUMENT_FAIL_ON_ERROR = "failOnError"
- private static final Class DEFERRED_BINDING
-
- static {
- try {
- DEFERRED_BINDING = Class.forName('grails.validation.DeferredBindingActions')
- } catch (Throwable e) {
- DEFERRED_BINDING = null
- }
- }
-
- protected static final Object[] EMPTY_ARRAY = []
- /**
- * When a domain instance is saved without validation, we put it
- * into this thread local variable. Any code that needs to know
- * whether the domain instance should be validated can just check
- * the value. Note that this only works because the session is
- * flushed when a domain instance is saved without validation.
- */
- static final ThreadLocal<Boolean> insertActiveThreadLocal = new ThreadLocal<Boolean>()
-
-
-
- protected SessionFactory sessionFactory
- protected ClassLoader classLoader
- protected IHibernateTemplate hibernateTemplate
-
- boolean autoFlush
-
- protected AbstractHibernateGormInstanceApi(Class<D> persistentClass, AbstractHibernateDatastore datastore, ClassLoader classLoader, IHibernateTemplate hibernateTemplate) {
- super(persistentClass, datastore)
- this.classLoader = classLoader
- sessionFactory = datastore.getSessionFactory()
- this.hibernateTemplate = hibernateTemplate
- this.autoFlush = datastore.autoFlush
- this.failOnError = datastore.failOnError
- this.markDirty = datastore.markDirty
- }
-
- @Override
- D save(D target, Map arguments) {
-
- PersistentEntity domainClass = persistentEntity
- runDeferredBinding()
- boolean shouldFlush = shouldFlush(arguments)
- boolean shouldValidate = shouldValidate(arguments, persistentEntity)
-
- HibernateRuntimeUtils.autoAssociateBidirectionalOneToOnes(domainClass, target)
-
- boolean deepValidate = true
- if (arguments?.containsKey(ARGUMENT_DEEP_VALIDATE)) {
- deepValidate = ClassUtils.getBooleanFromMap(ARGUMENT_DEEP_VALIDATE, arguments)
- }
-
- if (shouldValidate) {
- Validator validator = datastore.mappingContext.getEntityValidator(domainClass)
-
- Errors errors = HibernateRuntimeUtils.setupErrorsProperty(target)
-
- if (validator) {
- datastore.applicationEventPublisher?.publishEvent new ValidationEvent(datastore, target)
-
- if (validator instanceof CascadingValidator) {
- ((CascadingValidator)validator).validate target, errors, deepValidate
- } else if (validator instanceof org.grails.datastore.gorm.validation.CascadingValidator) {
- ((org.grails.datastore.gorm.validation.CascadingValidator) validator).validate target, errors, deepValidate
- } else {
- validator.validate target, errors
- }
-
- if (errors.hasErrors()) {
- handleValidationError(domainClass,target,errors)
- if (shouldFail(arguments)) {
- throw validationException.newInstance("Validation Error(s) occurred during save()", errors)
- }
- return null
- }
-
- setObjectToReadWrite(target)
- }
- }
-
- // this piece of code will retrieve a persistent instant
- // of a domain class property is only the id is set thus
- // relieving this burden off the developer
- autoRetrieveAssociations datastore, domainClass, target
-
- // Once we get here we've either validated this object or skipped validation, either way
- // we don't need to validate again for the rest of this save.
- GormValidateable validateable = (GormValidateable) target
- validateable.skipValidation(true)
-
- try {
- if (shouldInsert(arguments)) {
- return performInsert(target, shouldFlush)
- }
- else if(shouldMerge(arguments)) {
- return performMerge(target, shouldFlush)
- }
- else {
- if (target instanceof DirtyCheckable && markDirty) {
- target.markDirty()
- }
- return performSave(target, shouldFlush)
- }
- } finally {
- // After save, we have to make sure this entity is setup to validate again. It's possible it will
- // be validated again if this save didn't flush, but without checking it's dirty state we can't really
- // know for sure that it hasn't changed and need to err on the side of caution.
- validateable.skipValidation(false)
- }
- }
-
- @CompileDynamic
- private void runDeferredBinding() {
- DEFERRED_BINDING?.runActions()
- }
-
- @Override
- D merge(D instance, Map params) {
- Map args = new HashMap(params)
- args[ARGUMENT_MERGE] = true
- return save(instance, args)
- }
-
- @Override
- D insert(D instance, Map params) {
- Map args = new HashMap(params)
- args[ARGUMENT_INSERT] = true
- return save(instance, args)
- }
-
- @Override
- void discard(D instance) {
- hibernateTemplate.evict instance
- }
-
- @Override
- void delete(D instance, Map params = Collections.emptyMap()) {
- boolean flush = shouldFlush(params)
- try {
- hibernateTemplate.execute { Session session ->
- session.delete instance
- if(flush) {
- session.flush()
- }
- }
- }
- catch (DataAccessException e) {
- try {
- hibernateTemplate.execute { Session session ->
- session.flushMode = FlushModeType.COMMIT
- }
- }
- finally {
- throw e
- }
- }
- }
-
- @Override
- boolean isAttached(D instance) {
- hibernateTemplate.contains instance
- }
-
- @Override
- boolean instanceOf(D instance, Class cls) {
- return super.instanceOf(instance, cls)
- }
-
- @Override
- D lock(D instance) {
- hibernateTemplate.lock(instance, LockMode.PESSIMISTIC_WRITE)
- instance
- }
-
- @Override
- D attach(D instance) {
- hibernateTemplate.lock(instance, LockMode.NONE)
- return instance
- }
-
- @Override
- D refresh(D instance) {
- hibernateTemplate.refresh(instance)
- return instance
- }
-
- protected D performSave(final D target, final boolean flush) {
- hibernateTemplate.execute { Session session ->
- session.saveOrUpdate target
- if (flush) {
- flushSession session
- }
- return target
- }
- }
-
- protected D performMerge(final D target, final boolean flush) {
- hibernateTemplate.execute { Session session ->
- Object merged = session.merge(target)
- session.lock(merged, LockMode.NONE)
- if (flush) {
- flushSession session
- }
- return (D)merged
- }
- }
-
- protected D performInsert(final D target, final boolean shouldFlush) {
- hibernateTemplate.execute { Session session ->
- try {
- markInsertActive()
- session.save target
- if (shouldFlush) {
- flushSession session
- }
- return target
- } finally {
- resetInsertActive()
- }
-
- }
- }
-
- protected void flushSession(Session session) throws HibernateException {
- try {
- session.flush()
- } catch (HibernateException e) {
- // session should not be flushed again after a data access exception!
- session.setFlushMode FlushModeType.COMMIT
- throw e
- }
- }
- /**
- * Performs automatic association retrieval
- * @param entity The domain class to retrieve associations for
- * @param target The target object
- */
- @SuppressWarnings("unchecked")
- private void autoRetrieveAssociations(Datastore datastore, PersistentEntity entity, Object target) {
- EntityReflector reflector = datastore.mappingContext.getEntityReflector(entity)
- IHibernateTemplate t = this.hibernateTemplate
- for (PersistentProperty prop in entity.associations) {
- if(prop instanceof ToOne && !(prop instanceof Embedded)) {
- ToOne toOne = (ToOne)prop
-
- def propertyName = prop.name
- def propValue = reflector.getProperty(target, propertyName)
- if (propValue == null || t.contains(propValue)) {
- continue
- }
-
- PersistentEntity otherSide = toOne.associatedEntity
- if (otherSide == null) {
- continue
- }
-
- def identity = otherSide.identity
- if(identity == null) {
- continue
- }
-
- def otherSideReflector = datastore.mappingContext.getEntityReflector(otherSide)
- try {
- def id = (Serializable)otherSideReflector.getProperty(propValue, identity.name);
- if (id) {
- final Object associatedInstance = t.get(prop.type, id)
- if (associatedInstance) {
- reflector.setProperty(target, propertyName, associatedInstance)
- }
- }
- }
- catch (InvalidPropertyException ipe) {
- // property is not accessable
- }
- }
-
- }
- }
-
- /**
- * Checks whether validation should be performed
- * @return true if the domain class should be validated
- * @param arguments The arguments to the validate method
- * @param domainClass The domain class
- */
- private boolean shouldValidate(Map arguments, PersistentEntity entity) {
- if (!entity) {
- return false
- }
-
- if (arguments?.containsKey(ARGUMENT_VALIDATE)) {
- return ClassUtils.getBooleanFromMap(ARGUMENT_VALIDATE, arguments)
- }
- return true
- }
-
- private boolean shouldInsert(Map arguments) {
- ClassUtils.getBooleanFromMap(ARGUMENT_INSERT, arguments)
- }
-
- private boolean shouldMerge(Map arguments) {
- ClassUtils.getBooleanFromMap(ARGUMENT_MERGE, arguments)
- }
-
-
- protected boolean shouldFlush(Map map) {
- if (map?.containsKey(ARGUMENT_FLUSH)) {
- return ClassUtils.getBooleanFromMap(ARGUMENT_FLUSH, map)
- }
- return autoFlush
- }
-
- protected boolean shouldFail(Map map) {
- if (map?.containsKey(ARGUMENT_FAIL_ON_ERROR)) {
- return ClassUtils.getBooleanFromMap(ARGUMENT_FAIL_ON_ERROR, map)
- }
- return failOnError
- }
-
- /**
- * Sets the flush mode to manual. which ensures that the database changes are not persisted to the database
- * if a validation error occurs. If save() is called again and validation passes the code will check if there
- * is a manual flush mode and flush manually if necessary
- *
- * @param domainClass The domain class
- * @param target The target object that failed validation
- * @param errors The Errors instance @return This method will return null signaling a validation failure
- */
- protected Object handleValidationError(PersistentEntity entity, final Object target, Errors errors) {
- // if a validation error occurs set the object to read-only to prevent a flush
- setObjectToReadOnly target
- if (entity) {
- for (Association association in entity.associations) {
- if (association instanceof ToOne && !association instanceof Embedded) {
- def bean = new BeanWrapperImpl(target)
- def propertyValue = bean.getPropertyValue(association.name)
- if (propertyValue != null) {
- setObjectToReadOnly propertyValue
- }
- }
- }
- }
- setErrorsOnInstance target, errors
- return null
- }
-
- /**
- * Sets the target object to read-only using the given SessionFactory instance. This
- * avoids Hibernate performing any dirty checking on the object
- *
- *
- * @param target The target object
- * @param sessionFactory The SessionFactory instance
- */
- void setObjectToReadOnly(Object target) {
- hibernateTemplate.execute { Session session ->
- if (session.contains(target)) {
- session.setReadOnly target, true
- session.flushMode = FlushModeType.COMMIT
- }
- }
- }
- /**
- * Sets the target object to read-write, allowing Hibernate to dirty check it and auto-flush changes.
- *
- * @see #setObjectToReadOnly(Object)
- *
- * @param target The target object
- * @param sessionFactory The SessionFactory instance
- */
- abstract void setObjectToReadWrite(Object target)
-
- /**
- * Associates the Errors object on the instance
- *
- * @param target The target instance
- * @param errors The Errors object
- */
- @CompileDynamic
- protected void setErrorsOnInstance(Object target, Errors errors) {
- if(target instanceof GormValidateable) {
- ((GormValidateable)target).setErrors(errors)
- }
- else {
- target."$GormProperties.ERRORS" = errors
- }
- }
-
- /**
- * Called by org.grails.orm.hibernate.metaclass.SavePersistentMethod's performInsert
- * to set a ThreadLocal variable that determines the value for getAssumedUnsaved().
- */
- static void markInsertActive() {
- insertActiveThreadLocal.set(Boolean.TRUE);
- }
-
- /**
- * Clears the ThreadLocal variable set by markInsertActive().
- */
- static void resetInsertActive() {
- insertActiveThreadLocal.remove();
- }
-
- /**
- * Increments the entities version number in order to force an update
- * @param target The target entity
- */
- @CompileDynamic
- protected void incrementVersion(Object target) {
- if (target.hasProperty(GormProperties.VERSION)) {
- Object version = target."${GormProperties.VERSION}"
- if (version instanceof Long) {
- target."${GormProperties.VERSION}" = ++((Long)version)
- }
- }
- }
-
- SessionFactory getSessionFactory() {
- return this.sessionFactory
- }
-}
diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy
index 1eb8961fd8..f0d5f96314 100644
--- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy
+++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy
@@ -15,13 +15,40 @@
*/
package org.grails.orm.hibernate
+import grails.gorm.validation.CascadingValidator
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
+import jakarta.persistence.FlushModeType
+import org.grails.datastore.gorm.GormInstanceApi
+import org.grails.datastore.gorm.GormValidateable
+import org.grails.datastore.mapping.core.Datastore
+import org.grails.datastore.mapping.dirty.checking.DirtyCheckable
+import org.grails.datastore.mapping.model.config.GormProperties
+import org.grails.datastore.mapping.model.types.Embedded
+import org.grails.datastore.mapping.proxy.ProxyHandler
+import org.grails.datastore.mapping.reflect.ClassUtils
+import org.grails.datastore.mapping.reflect.EntityReflector
+import org.grails.datastore.mapping.engine.event.ValidationEvent
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.datastore.mapping.model.PersistentProperty
+import org.grails.datastore.mapping.model.types.Association
+import org.grails.datastore.mapping.model.types.ToOne
import org.grails.orm.hibernate.cfg.GrailsHibernateUtil
+import org.grails.orm.hibernate.support.HibernateRuntimeUtils
+import org.hibernate.FlushMode
+import org.hibernate.HibernateException
+import org.hibernate.LockMode
+import org.hibernate.Session
+import org.hibernate.SessionFactory
import org.hibernate.engine.spi.EntityEntry
import org.hibernate.engine.spi.SessionImplementor
import org.hibernate.persister.entity.EntityPersister
import org.hibernate.tuple.NonIdentifierAttribute
+import org.springframework.beans.BeanWrapperImpl
+import org.springframework.beans.InvalidPropertyException
+import org.springframework.dao.DataAccessException
+import org.springframework.validation.Errors
+import org.springframework.validation.Validator
/**
* The implementation of the GORM instance API contract for Hibernate.
@@ -30,14 +57,348 @@ import org.hibernate.tuple.NonIdentifierAttribute
* @since 1.0
*/
@CompileStatic
-class HibernateGormInstanceApi<D> extends AbstractHibernateGormInstanceApi<D> {
+class HibernateGormInstanceApi<D> extends GormInstanceApi<D> {
+
+ private static final String ARGUMENT_VALIDATE = "validate"
+ private static final String ARGUMENT_DEEP_VALIDATE = "deepValidate"
+ private static final String ARGUMENT_FLUSH = "flush"
+ private static final String ARGUMENT_INSERT = "insert"
+ private static final String ARGUMENT_MERGE = "merge"
+ private static final String ARGUMENT_FAIL_ON_ERROR = "failOnError"
+ private static final Class DEFERRED_BINDING
+
+ static {
+ try {
+ DEFERRED_BINDING = Class.forName('grails.validation.DeferredBindingActions')
+ } catch (Throwable e) {
+ DEFERRED_BINDING = null
+ }
+ }
+
+ protected static final Object[] EMPTY_ARRAY = []
+ static final ThreadLocal<Boolean> insertActiveThreadLocal = new ThreadLocal<Boolean>()
+
+ protected SessionFactory sessionFactory
+ protected ClassLoader classLoader
+ protected IHibernateTemplate hibernateTemplate
+
+ boolean autoFlush
protected InstanceApiHelper instanceApiHelper
HibernateGormInstanceApi(Class<D> persistentClass, HibernateDatastore datastore, ClassLoader classLoader) {
- super(persistentClass, datastore, classLoader, null)
- hibernateTemplate = new GrailsHibernateTemplate(sessionFactory, datastore)
- instanceApiHelper = new InstanceApiHelper((GrailsHibernateTemplate)hibernateTemplate)
+ super(persistentClass, datastore as Datastore)
+ this.classLoader = classLoader
+ this.sessionFactory = datastore.getSessionFactory()
+ this.hibernateTemplate = new GrailsHibernateTemplate(sessionFactory, datastore)
+ this.autoFlush = datastore.autoFlush
+ this.failOnError = datastore.failOnError
+ this.markDirty = datastore.markDirty
+ this.instanceApiHelper = new InstanceApiHelper((GrailsHibernateTemplate) this.hibernateTemplate)
+ }
+
+ @Override
+ D save(D target, Map arguments) {
+ PersistentEntity domainClass = persistentEntity
+ runDeferredBinding()
+ boolean shouldFlush = shouldFlush(arguments)
+ boolean shouldValidate = shouldValidate(arguments, persistentEntity)
+
+ HibernateRuntimeUtils.autoAssociateBidirectionalOneToOnes(domainClass, target)
+
+ boolean deepValidate = true
+ if (arguments?.containsKey(ARGUMENT_DEEP_VALIDATE)) {
+ deepValidate = ClassUtils.getBooleanFromMap(ARGUMENT_DEEP_VALIDATE, arguments)
+ }
+
+ if (shouldValidate) {
+ Validator validator = datastore.mappingContext.getEntityValidator(domainClass)
+
+ Errors errors = HibernateRuntimeUtils.setupErrorsProperty(target)
+
+ if (validator) {
+ datastore.applicationEventPublisher?.publishEvent new ValidationEvent(datastore, target)
+
+ if (validator instanceof CascadingValidator) {
+ ((CascadingValidator)validator).validate target, errors, deepValidate
+ } else if (validator instanceof org.grails.datastore.gorm.validation.CascadingValidator) {
+ ((org.grails.datastore.gorm.validation.CascadingValidator) validator).validate target, errors, deepValidate
+ } else {
+ validator.validate target, errors
+ }
+
+ if (errors.hasErrors()) {
+ handleValidationError(domainClass,target,errors)
+ if (shouldFail(arguments)) {
+ throw validationException.newInstance("Validation Error(s) occurred during save()", errors)
+ }
+ return null
+ }
+
+ setObjectToReadWrite(target)
+ }
+ }
+
+ autoRetrieveAssociations datastore, domainClass, target
+
+ GormValidateable validateable = (GormValidateable) target
+ validateable.skipValidation(true)
+
+ try {
+ if (shouldInsert(arguments)) {
+ return performInsert(target, shouldFlush)
+ }
+ else if(shouldMerge(arguments)) {
+ return performMerge(target, shouldFlush)
+ }
+ else {
+ if (target instanceof DirtyCheckable && markDirty) {
+ target.markDirty()
+ }
+ return performSave(target, shouldFlush)
+ }
+ } finally {
+ validateable.skipValidation(false)
+ }
+ }
+
+ @CompileDynamic
+ private void runDeferredBinding() {
+ DEFERRED_BINDING?.runActions()
+ }
+
+ @Override
+ D merge(D instance, Map params) {
+ Map args = new HashMap(params)
+ args[ARGUMENT_MERGE] = true
+ return save(instance, args)
+ }
+
+ @Override
+ D insert(D instance, Map params) {
+ Map args = new HashMap(params)
+ args[ARGUMENT_INSERT] = true
+ return save(instance, args)
+ }
+
+ @Override
+ void discard(D instance) {
+ hibernateTemplate.evict instance
+ }
+
+ @Override
+ void delete(D instance, Map params = Collections.emptyMap()) {
+ boolean flush = shouldFlush(params)
+ try {
+ hibernateTemplate.execute { Session session ->
+ session.delete instance
+ if(flush) {
+ session.flush()
+ }
+ }
+ }
+ catch (DataAccessException e) {
+ try {
+ hibernateTemplate.execute { Session session ->
+ session.flushMode = FlushModeType.COMMIT
+ }
+ }
+ finally {
+ throw e
+ }
+ }
+ }
+
+ @Override
+ boolean isAttached(D instance) {
+ hibernateTemplate.contains instance
+ }
+
+ @Override
+ D lock(D instance) {
+ hibernateTemplate.lock(instance, LockMode.PESSIMISTIC_WRITE)
+ instance
+ }
+
+ @Override
+ D attach(D instance) {
+ hibernateTemplate.lock(instance, LockMode.NONE)
+ return instance
+ }
+
+ @Override
+ D refresh(D instance) {
+ hibernateTemplate.refresh(instance)
+ return instance
+ }
+
+ protected D performSave(final D target, final boolean flush) {
+ hibernateTemplate.execute { Session session ->
+ session.saveOrUpdate target
+ if (flush) {
+ flushSession session
+ }
+ return target
+ }
+ }
+
+ protected D performMerge(final D target, final boolean flush) {
+ hibernateTemplate.execute { Session session ->
+ Object merged = session.merge(target)
+ session.lock(merged, LockMode.NONE)
+ if (flush) {
+ flushSession session
+ }
+ return (D)merged
+ }
+ }
+
+ protected D performInsert(final D target, final boolean shouldFlush) {
+ hibernateTemplate.execute { Session session ->
+ try {
+ markInsertActive()
+ session.save target
+ if (shouldFlush) {
+ flushSession session
+ }
+ return target
+ } finally {
+ resetInsertActive()
+ }
+
+ }
+ }
+
+ protected void flushSession(Session session) throws HibernateException {
+ try {
+ session.flush()
+ } catch (HibernateException e) {
+ session.setFlushMode FlushModeType.COMMIT
+ throw e
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void autoRetrieveAssociations(Datastore datastore, PersistentEntity entity, Object target) {
+ EntityReflector reflector = datastore.mappingContext.getEntityReflector(entity)
+ IHibernateTemplate t = this.hibernateTemplate
+ for (PersistentProperty prop in entity.associations) {
+ if(prop instanceof ToOne && !(prop instanceof Embedded)) {
+ ToOne toOne = (ToOne)prop
+
+ def propertyName = prop.name
+ def propValue = reflector.getProperty(target, propertyName)
+ if (propValue == null || t.contains(propValue)) {
+ continue
+ }
+
+ PersistentEntity otherSide = toOne.associatedEntity
+ if (otherSide == null) {
+ continue
+ }
+
+ def identity = otherSide.identity
+ if(identity == null) {
+ continue
+ }
+
+ def otherSideReflector = datastore.mappingContext.getEntityReflector(otherSide)
+ try {
+ def id = (Serializable)otherSideReflector.getProperty(propValue, identity.name);
+ if (id) {
+ final Object associatedInstance = t.get(prop.type, id)
+ if (associatedInstance) {
+ reflector.setProperty(target, propertyName, associatedInstance)
+ }
+ }
+ }
+ catch (InvalidPropertyException ipe) {
+ // property is not accessable
+ }
+ }
+
+ }
+ }
+
+ private boolean shouldValidate(Map arguments, PersistentEntity entity) {
+ if (!entity) {
+ return false
+ }
+ if (arguments?.containsKey(ARGUMENT_VALIDATE)) {
+ return ClassUtils.getBooleanFromMap(ARGUMENT_VALIDATE, arguments)
+ }
+ return true
+ }
+
+ private boolean shouldInsert(Map arguments) {
+ ClassUtils.getBooleanFromMap(ARGUMENT_INSERT, arguments)
+ }
+
+ private boolean shouldMerge(Map arguments) {
+ ClassUtils.getBooleanFromMap(ARGUMENT_MERGE, arguments)
+ }
+
+ protected boolean shouldFlush(Map map) {
+ if (map?.containsKey(ARGUMENT_FLUSH)) {
+ return ClassUtils.getBooleanFromMap(ARGUMENT_FLUSH, map)
+ }
+ return autoFlush
+ }
+
+ protected boolean shouldFail(Map map) {
+ if (map?.containsKey(ARGUMENT_FAIL_ON_ERROR)) {
+ return ClassUtils.getBooleanFromMap(ARGUMENT_FAIL_ON_ERROR, map)
+ }
+ return failOnError
+ }
+
+ protected Object handleValidationError(PersistentEntity entity, final Object target, Errors errors) {
+ setObjectToReadOnly target
+ if (entity) {
+ for (Association association in entity.associations) {
+ if (association instanceof ToOne && !association instanceof Embedded) {
+ def bean = new BeanWrapperImpl(target)
+ def propertyValue = bean.getPropertyValue(association.name)
+ if (propertyValue != null) {
+ setObjectToReadOnly propertyValue
+ }
+ }
+ }
+ }
+ setErrorsOnInstance target, errors
+ return null
+ }
+
+ @CompileDynamic
+ protected void setErrorsOnInstance(Object target, Errors errors) {
+ if(target instanceof GormValidateable) {
+ ((GormValidateable)target).setErrors(errors)
+ }
+ else {
+ target."$GormProperties.ERRORS" = errors
+ }
+ }
+
+ static void markInsertActive() {
+ insertActiveThreadLocal.set(Boolean.TRUE);
+ }
+
+ static void resetInsertActive() {
+ insertActiveThreadLocal.remove();
+ }
+
+ @CompileDynamic
+ protected void incrementVersion(Object target) {
+ if (target.hasProperty(GormProperties.VERSION)) {
+ Object version = target."${GormProperties.VERSION}"
+ if (version instanceof Long) {
+ target."${GormProperties.VERSION}" = ++((Long)version)
+ }
+ }
+ }
+
+ SessionFactory getSessionFactory() {
+ return this.sessionFactory
}
/**
@@ -153,12 +514,10 @@ class HibernateGormInstanceApi<D> extends AbstractHibernateGormInstanceApi<D> {
return entry
}
- @Override
void setObjectToReadWrite(Object target) {
GrailsHibernateUtil.setObjectToReadWrite(target, sessionFactory)
}
- @Override
void setObjectToReadOnly(Object target) {
GrailsHibernateUtil.setObjectToReadyOnly(target, sessionFactory)
}
diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/InstanceProxy.groovy b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/InstanceProxy.groovy
index 5ec0edd541..3a74b36d12 100644
--- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/InstanceProxy.groovy
+++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/InstanceProxy.groovy
@@ -17,18 +17,18 @@ package org.grails.orm.hibernate.cfg
import groovy.transform.CompileStatic
-import org.grails.orm.hibernate.AbstractHibernateGormInstanceApi
+import org.grails.orm.hibernate.HibernateGormInstanceApi
import org.grails.orm.hibernate.AbstractHibernateGormValidationApi
@CompileStatic
class InstanceProxy {
protected instance
protected AbstractHibernateGormValidationApi validateApi
- protected AbstractHibernateGormInstanceApi instanceApi
+ protected HibernateGormInstanceApi instanceApi
protected final Set<String> validateMethods
- InstanceProxy(instance, AbstractHibernateGormInstanceApi instanceApi, AbstractHibernateGormValidationApi validateApi) {
+ InstanceProxy(instance, HibernateGormInstanceApi instanceApi, AbstractHibernateGormValidationApi validateApi) {
this.instance = instance
this.instanceApi = instanceApi
this.validateApi = validateApi
diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventTriggeringInterceptor.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventTriggeringInterceptor.java
index 7905ac9c31..fb660c3dd1 100644
--- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventTriggeringInterceptor.java
+++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/ClosureEventTriggeringInterceptor.java
@@ -27,7 +27,7 @@ import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.types.Embedded;
import org.grails.datastore.mapping.proxy.ProxyHandler;
import org.grails.orm.hibernate.AbstractHibernateDatastore;
-import org.grails.orm.hibernate.AbstractHibernateGormInstanceApi;
+import org.grails.orm.hibernate.HibernateGormInstanceApi;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.event.spi.AbstractEvent;
diff --git a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/validation/UniqueWithHasOneSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/validation/UniqueWithHasOneSpec.groovy
index 46cdd36900..c5dd171c1f 100644
--- a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/validation/UniqueWithHasOneSpec.groovy
+++ b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/validation/UniqueWithHasOneSpec.groovy
@@ -52,26 +52,23 @@ class UniqueWithHasOneSpec extends HibernateGormDatastoreSpec {
@Entity
class Foo {
- static hasOne = [bar: Bar]
-
String name
+ Bar bar
+
+ static hasOne = [bar: Bar]
static constraints = {
bar nullable: true
- name unique: "bar"
+ name unique: ["bar"]
}
-
}
@Entity
class Bar {
- static belongsTo = [foo: Foo]
-
String name
+ static belongsTo = [ foo: Foo]
static constraints = {
- foo nullable: true
}
-
}
diff --git a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy
new file mode 100644
index 0000000000..48b4a5e897
--- /dev/null
+++ b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy
@@ -0,0 +1,172 @@
+// HibernateGormInstanceApiSpec.groovy
+package org.grails.orm.hibernate
+
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import grails.gorm.specs.entities.Club
+import grails.persistence.Entity
+import org.springframework.validation.Errors
+
+class HibernateGormInstanceApiSpec extends HibernateGormDatastoreSpec {
+
+ void setupSpec() {
+ manager.addAllDomainClasses([HibernateGormInstanceApiSpecPerson])
+ }
+
+
+ def setup() {
+ new HibernateGormInstanceApiSpecPerson(firstName: "Fred", lastName: "Flintstone", age: 40).save(flush: true)
+ new HibernateGormInstanceApiSpecPerson(firstName: "Wilma", lastName: "Flintstone", age: 35).save(flush: true)
+ }
+
+ void "test save with validation success"() {
+ given:
+ def person = new HibernateGormInstanceApiSpecPerson(firstName: "Barney", lastName: "Rubble", age: 38)
+ when:
+ def result = person.save()
+ then:
+ result != null
+ result.id != null
+ }
+
+ void "test save with validation failure"() {
+ given:
+ def person = new HibernateGormInstanceApiSpecPerson(firstName: null, lastName: "Rubble", age: 38)
+ when:
+ def result = person.save()
+ then:
+ result == null
+ person.errors.hasErrors()
+ }
+
+ void "test save with failOnError"() {
+ given:
+ def person = new HibernateGormInstanceApiSpecPerson(firstName: null, lastName: "Rubble", age: 38)
+ when:
+ person.save(failOnError: true)
+ then:
+ thrown(Exception)
+ }
+
+ void "test merge"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Fred")
+ person.lastName = "Smith"
+ when:
+ def merged = person.merge()
+ then:
+ merged.lastName == "Smith"
+ }
+
+ void "test insert"() {
+ given:
+ def person = new HibernateGormInstanceApiSpecPerson(firstName: "Betty", lastName: "Rubble", age: 36)
+ when:
+ def inserted = person.insert()
+ then:
+ inserted.id != null
+ }
+
+ void "test discard"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ when:
+ person.discard()
+ then:
+ !person.isAttached()
+ }
+
+ void "test delete"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Fred")
+ when:
+ person.delete(flush: true)
+ then:
+ HibernateGormInstanceApiSpecPerson.findByFirstName("Fred") == null
+ }
+
+ void "test isAttached"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ expect:
+ person.isAttached()
+ }
+
+ void "test lock and attach"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ when:
+ def locked = person.lock()
+ def attached = person.attach()
+ then:
+ locked == person
+ attached == person
+ }
+
+ void "test refresh"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ person.lastName = "Changed"
+ when:
+ person.refresh()
+ then:
+ person.lastName == "Flintstone"
+ }
+
+ void "test isDirty for field"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ person.lastName = "Changed"
+ expect:
+ person.isDirty("lastName")
+ }
+
+ void "test isDirty for instance"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ person.lastName = "Changed"
+ expect:
+ person.isDirty()
+ }
+
+ void "test getDirtyPropertyNames"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ person.lastName = "Changed"
+ when:
+ def dirtyNames = person.getDirtyPropertyNames()
+ then:
+ dirtyNames.contains("lastName")
+ }
+
+ void "test getPersistentValue"() {
+ given:
+ def person = HibernateGormInstanceApiSpecPerson.findByFirstName("Wilma")
+ def originalLastName = person.lastName
+ person.lastName = "Changed"
+ when:
+ def persistedValue = person.getPersistentValue("lastName")
+ then:
+ persistedValue == originalLastName
+ }
+
+ void "test handleValidationError sets errors"() {
+ given:
+ def person = new HibernateGormInstanceApiSpecPerson(firstName: null, lastName: "Rubble", age: 38)
+ person.save()
+ expect:
+ person.errors instanceof Errors
+ person.errors.hasErrors()
+ }
+}
+
+@Entity
+class HibernateGormInstanceApiSpecPerson {
+ String firstName
+ String lastName
+ Integer age
+
+ static constraints = {
+ firstName nullable: false
+ age min: 0
+ }
+}
\ No newline at end of file
diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/InheritanceSpec.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/InheritanceSpec.groovy
index 21666bbbc5..1e7cc9a01d 100644
--- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/InheritanceSpec.groovy
+++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/InheritanceSpec.groovy
@@ -30,7 +30,7 @@ import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec
class InheritanceSpec extends GrailsDataTckSpec {
void setupSpec() {
- manager.domainClasses += [Practice]
+ manager.addAllDomainClasses([Practice,City,Country,Location])
}
void "Test inheritance with dynamic finder"() {
diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy
index 01874dc5a6..59babe6346 100644
--- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy
+++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy
@@ -37,8 +37,8 @@ import spock.lang.Unroll
*/
class ValidationSpec extends GrailsDataTckSpec {
void setupSpec() {
- manager.domainClasses = [ClassWithListArgBeforeValidate, ClassWithNoArgBeforeValidate,
- ClassWithOverloadedBeforeValidate, TestEntity, ChildEntity, Task]
+ manager.addAllDomainClasses([ClassWithListArgBeforeValidate, ClassWithNoArgBeforeValidate,
+ ClassWithOverloadedBeforeValidate, TestEntity, ChildEntity, Task])
}
// Hibernate did not originally have this test and it fails for it