From 32646b5581541f8fda6e16dc2a0331392723c975 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 13 May 2005 07:14:32 +0000 Subject: [PATCH] More eager/lazy fetching optimisations. --- .../org/acegisecurity/domain/dao/Dao.java | 15 ++++- .../domain/dao/EvictionUtils.java | 34 ++++++++++ .../domain/hibernate/DaoHibernate.java | 62 ++++++++++++++++++- .../IntrospectionManagerHibernate.java | 6 +- .../acegisecurity/domain/service/Manager.java | 15 ++++- .../domain/service/ManagerImpl.java | 7 ++- .../domain/validation/package.html | 24 +++---- 7 files changed, 143 insertions(+), 20 deletions(-) diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/Dao.java b/domain/src/main/java/org/acegisecurity/domain/dao/Dao.java index c1322329b6..fd524624ab 100644 --- a/domain/src/main/java/org/acegisecurity/domain/dao/Dao.java +++ b/domain/src/main/java/org/acegisecurity/domain/dao/Dao.java @@ -112,7 +112,9 @@ public interface Dao { public List findId(Collection ids); /** - * Load a persistent instance by its identifier. + * Load a persistent instance by its identifier, although some properties + * may be lazy loaded depending on the underlying DAO implementation and/or + * persistence engine mapping document. * * @param id the identifier of the persistent instance desired to be * retrieved @@ -121,6 +123,17 @@ public interface Dao { */ public E readId(Serializable id); + /** + * Loads a persistent instance by its identifier, along with any + * lazy loaded properties associated with that instance. + * + * @param id the identifier of the persistent instance desired to be + * retrieved + * + * @return the request item, or null if not found + */ + public E readPopulatedId(Serializable id); + /** * Find persistent instances with properties matching those of the passed * PersistableEntity. diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java b/domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java index dc2bd67df1..7724bdbca3 100644 --- a/domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java +++ b/domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java @@ -19,6 +19,7 @@ import net.sf.acegisecurity.domain.PersistableEntity; import org.springframework.util.Assert; +import java.lang.reflect.Method; import java.util.Collection; import java.util.Iterator; @@ -52,6 +53,39 @@ public class EvictionUtils { } /** + * Evicts the PersistableEntity using the passed + * Object (provided that the passed Object + * implements EvictionCapable), along with expressly + * evicting every PersistableEntity returned by the + * PersistableEntity's getters. + * + * @param daoOrServices the potential source for + * EvictionCapable services (never null) + * @param entity to evict includnig its getter results (can be null) + */ + public static void evictPopulatedIfRequired(Object daoOrServices, + PersistableEntity entity) { + EvictionCapable evictor = getEvictionCapable(daoOrServices); + + if (evictor != null && entity != null) { + evictor.evict(entity); + + Method[] methods = entity.getClass().getMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().startsWith("get") && methods[i].getParameterTypes().length == 0) { + try { + Object result = methods[i].invoke(entity, new Object[] {}); + if (result instanceof PersistableEntity) { + evictor.evict((PersistableEntity) result); + } + } catch (Exception ignored) {} + } + } + + } + } + + /** * Evicts each PersistableEntity element of the passed * Collection using the passed Object (provided * that the passed Object implements diff --git a/domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java b/domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java index e39e6db104..752fdc2897 100644 --- a/domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java +++ b/domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java @@ -16,6 +16,7 @@ package net.sf.acegisecurity.domain.hibernate; import java.io.Serializable; +import java.lang.reflect.Method; import java.util.Collection; import java.util.List; @@ -24,6 +25,7 @@ import net.sf.acegisecurity.domain.dao.Dao; import net.sf.acegisecurity.domain.dao.EvictionCapable; import net.sf.acegisecurity.domain.dao.InitializationCapable; import net.sf.acegisecurity.domain.dao.PaginatedList; +import net.sf.acegisecurity.domain.validation.ValidationManager; import org.hibernate.Criteria; import org.hibernate.EntityMode; @@ -35,9 +37,12 @@ import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Order; import org.hibernate.metadata.ClassMetadata; import org.hibernate.type.Type; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.util.Assert; +import org.springframework.validation.BindException; + /** * Generics supporting {@link Dao} implementation that uses Hibernate 3 for persistence. @@ -52,6 +57,9 @@ public class DaoHibernate extends HibernateDaoSuppo /** The class that this instance provides services for */ private Class supportsClass; + + /** Enables mutator methods to validate an object prior to persistence */ + private ValidationManager validationManager; //~ Methods ================================================================ @@ -63,15 +71,33 @@ public class DaoHibernate extends HibernateDaoSuppo return supportsClass; } - public E create(E value) { + public ValidationManager getValidationManager() { + return validationManager; + } + + public void setValidationManager(ValidationManager validationManager) { + this.validationManager = validationManager; + } + + public E create(E value) { Assert.notNull(value); + validate(value); getHibernateTemplate().save(value); return readId(value.getInternalId()); } + + protected void validate(E value) throws DataIntegrityViolationException { + try { + validationManager.validate(value); + } catch (BindException bindException) { + throw new DataIntegrityViolationException("Entity state is invalid", bindException); + } + } public E createOrUpdate(E value) { Assert.notNull(value); + validate(value); getHibernateTemplate().saveOrUpdate(value); return readId(value.getInternalId()); @@ -79,6 +105,7 @@ public class DaoHibernate extends HibernateDaoSuppo public void delete(E value) { Assert.notNull(value); + validate(value); getHibernateTemplate().delete(value); } @@ -104,6 +131,37 @@ public class DaoHibernate extends HibernateDaoSuppo return (E) getHibernateTemplate().get(supportsClass, id); } + public E readPopulatedId(Serializable id) { + Assert.notNull(id); + E result = readId(id); + initializeAllZeroArgumentGetters(result); + return result; + } + + /** + * Locates every get*() method against the passed entity + * and calls it. This method does not nest its initialization beyond + * the immediately passed entity. + * + *

For example, a Foo object might provide a getBar() method. + * Passing the Foo instance to this method will guarantee getBar() is + * available to the services layer. However, it getBar() returned a Bar + * which in turn provided a getCar() method, there is NO GUARANTEE + * the getCar() method will be initialized. + * + * @param entity for which its immediate getters should be initialized + */ + protected void initializeAllZeroArgumentGetters(E entity) { + Method[] methods = entity.getClass().getMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().startsWith("get") && methods[i].getParameterTypes().length == 0) { + try { + Hibernate.initialize(methods[i].invoke(entity, new Object[] {})); + } catch (Exception ignored) {} + } + } + } + public PaginatedList scroll(E value, int firstElement, int maxElements, String orderByAsc) { Assert.notNull(value); @@ -134,6 +192,7 @@ public class DaoHibernate extends HibernateDaoSuppo public E update(E value) { Assert.notNull(value); + validate(value); getHibernateTemplate().update(value); return readId(value.getInternalId()); @@ -146,6 +205,7 @@ public class DaoHibernate extends HibernateDaoSuppo */ protected final void initDao() throws Exception { Assert.notNull(supportsClass, "supportClass is required"); + Assert.notNull(validationManager, "validationManager is required"); Assert.isTrue(PersistableEntity.class.isAssignableFrom(supportsClass), "supportClass is not an implementation of PersistableEntity"); initHibernateDao(); diff --git a/domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java b/domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java index 04b4e05ba3..f13aa1ceb5 100644 --- a/domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java +++ b/domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java @@ -19,6 +19,7 @@ import net.sf.acegisecurity.domain.validation.IntrospectionManager; import net.sf.acegisecurity.domain.validation.ValidationRegistryManager; import org.hibernate.EntityMode; +import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.SessionFactory; @@ -120,11 +121,12 @@ public class IntrospectionManagerHibernate implements IntrospectionManager, Type propertyType = classMetadata.getPropertyType(propertyNames[i]); // Add this property to the List of Objects to validate - // only if a Validator is registered for that Object + // only if a Validator is registered for that Object AND + // the object is initialized (ie not lazy loaded) if (this.validationRegistryManager.findValidator( propertyType.getReturnedClass()) != null) { Object childObject = classMetadata.getPropertyValue(parentObject, propertyNames[i], EntityMode.POJO); - if (childObject != null) { + if (childObject != null && Hibernate.isInitialized(childObject)) { allObjects.add(childObject); } } diff --git a/domain/src/main/java/org/acegisecurity/domain/service/Manager.java b/domain/src/main/java/org/acegisecurity/domain/service/Manager.java index 79e507bbea..9e6992c396 100644 --- a/domain/src/main/java/org/acegisecurity/domain/service/Manager.java +++ b/domain/src/main/java/org/acegisecurity/domain/service/Manager.java @@ -129,7 +129,9 @@ public interface Manager { public List findId(Collection ids); /** - * Load a persistent instance by its identifier. + * Load a persistent instance by its identifier, although some properties + * may be lazy loaded depending on the underlying DAO implementation and/or + * persistence engine mapping document. * * @param id the identifier of the persistent instance desired to be * retrieved @@ -138,6 +140,17 @@ public interface Manager { */ public E readId(Serializable id); + /** + * Loads a persistent instance by its identifier, along with any + * lazy loaded properties associated with that instance. + * + * @param id the identifier of the persistent instance desired to be + * retrieved + * + * @return the request item, or null if not found + */ + public E readPopulatedId(Serializable id); + /** * Find persistent instances with properties matching those of the passed * PersistableEntity. diff --git a/domain/src/main/java/org/acegisecurity/domain/service/ManagerImpl.java b/domain/src/main/java/org/acegisecurity/domain/service/ManagerImpl.java index a7264ea0a9..7ca6c82979 100644 --- a/domain/src/main/java/org/acegisecurity/domain/service/ManagerImpl.java +++ b/domain/src/main/java/org/acegisecurity/domain/service/ManagerImpl.java @@ -123,7 +123,12 @@ public class ManagerImpl extends ApplicationObjectS return dao.readId(id); } - public PaginatedList scroll(E value, int firstElement, + public E readPopulatedId(Serializable id) { + Assert.notNull(id); + return dao.readPopulatedId(id); + } + + public PaginatedList scroll(E value, int firstElement, int maxElements) { Assert.notNull(value); Assert.isInstanceOf(this.supportsClass, value, "Can only scroll with values this manager supports"); diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/package.html b/domain/src/main/java/org/acegisecurity/domain/validation/package.html index f961521c89..e3cef3d390 100644 --- a/domain/src/main/java/org/acegisecurity/domain/validation/package.html +++ b/domain/src/main/java/org/acegisecurity/domain/validation/package.html @@ -8,12 +8,12 @@ will need to wire a suitable {@link IntrospectionManager} against the ValidationManager so that children of a domain object presented for validation can be identified and in turn also validated. -

The {@link ValidationInterceptor} and {@link ValidationAdvisor} should be -used against each of your data access object (DAO) mutator methods, such as -SomeDao.create(Object) and SomeDao.update(Object). -The interceptor will cause the Object to be presented to the -ValidationManager, thus ensuring the domain object instance is in -a valid state before being persisted.

+

The {@link ValidationInterceptor} and {@link ValidationAdvisor} are provided, +although their use is not recommended against DAOs given many Validators +require a DAO and this will cause a loop that results in the DAO not being +advised. Instead your DAO implementations should have their mutator methods +pass the object to the ValidationManager prior to persistence. This +is a non-AOP approach, but represetns a practical solution.

If you domain objects themselves wish to ensure they are in a valid state prior to internal business methods being invoked, it is suggested they provide @@ -32,14 +32,10 @@ directly, as it ignores classes that do not implement

Finally, sometimes Validators might need to perform queries against a persistence or services layer. For example, the Validator -may be checking no other user has this username. If using an ORM tool such as -Hibernate, it is recommended your Validators subclass a common -abstract parent that provides an evict(Object) and -evict(Collection) method. That way your Validators -can utilise standard services layer or DAO methods as required to retrieve -other domain objects, and flush them from the session cache afterwards. Whilst -this is more a Validator design choice than something mandated by -this package, we have found it a worthwhile pattern in real-world applications. +may be checking no other user has this username, or comparing the object's old +state to detect modifications that violate business rules. If using an ORM tool +such as Hibernate, it is recommended you use the EvictionUtils +static methods to remove objects from the session.