Переглянути джерело

More eager/lazy fetching optimisations.

Ben Alex 20 роки тому
батько
коміт
32646b5581

+ 14 - 1
domain/src/main/java/org/acegisecurity/domain/dao/Dao.java

@@ -112,7 +112,9 @@ public interface Dao<E extends PersistableEntity> {
     public List<E> findId(Collection<Serializable> 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<E extends PersistableEntity> {
      */
     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 <code>null</code> if not found
+	 */
+	public E readPopulatedId(Serializable id);
+	
     /**
      * Find persistent instances with properties matching those of the passed
      * <code>PersistableEntity</code>.

+ 34 - 0
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 <code>PersistableEntity</code> using the passed
+     * <code>Object</code> (provided that the passed <code>Object</code>
+     * implements <code>EvictionCapable</code>), along with expressly
+     * evicting every <code>PersistableEntity</code> returned by the
+     * <code>PersistableEntity</code>'s getters.
+     *
+     * @param daoOrServices the potential source for
+     *        <code>EvictionCapable</code> services (never <code>null</code>)
+     * @param entity to evict includnig its getter results (can be <code>null</code>)
+     */
+    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 <code>PersistableEntity</code> element of the passed
      * <code>Collection</code> using the passed <code>Object</code> (provided
      * that the passed <code>Object</code> implements

+ 61 - 1
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<E extends PersistableEntity> 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<E extends PersistableEntity> 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<E extends PersistableEntity> extends HibernateDaoSuppo
 
     public void delete(E value) {
         Assert.notNull(value);
+		validate(value);
         getHibernateTemplate().delete(value);
     }
 
@@ -104,6 +131,37 @@ public class DaoHibernate<E extends PersistableEntity> 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 <code>get*()</code> method against the passed entity
+	 * and calls it. This method does not nest its initialization beyond
+	 * the immediately passed entity.
+	 * 
+	 * <p>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<E> scroll(E value, int firstElement,
         int maxElements, String orderByAsc) {
         Assert.notNull(value);
@@ -134,6 +192,7 @@ public class DaoHibernate<E extends PersistableEntity> 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<E extends PersistableEntity> 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();

+ 4 - 2
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);
                         }
                     }

+ 14 - 1
domain/src/main/java/org/acegisecurity/domain/service/Manager.java

@@ -129,7 +129,9 @@ public interface Manager<E extends PersistableEntity> {
     public List<E> findId(Collection<Serializable> 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<E extends PersistableEntity> {
      */
     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 <code>null</code> if not found
+	 */
+	public E readPopulatedId(Serializable id);
+	
     /**
      * Find persistent instances with properties matching those of the passed
      * <code>PersistableEntity</code>.

+ 6 - 1
domain/src/main/java/org/acegisecurity/domain/service/ManagerImpl.java

@@ -123,7 +123,12 @@ public class ManagerImpl<E extends PersistableEntity> extends ApplicationObjectS
         return dao.readId(id);
     }
 
-    public PaginatedList<E> scroll(E value, int firstElement,
+    public E readPopulatedId(Serializable id) {
+		Assert.notNull(id);
+		return dao.readPopulatedId(id);
+	}
+
+	public PaginatedList<E> scroll(E value, int firstElement,
         int maxElements) {
         Assert.notNull(value);
 		Assert.isInstanceOf(this.supportsClass, value, "Can only scroll with values this manager supports");

+ 10 - 14
domain/src/main/java/org/acegisecurity/domain/validation/package.html

@@ -8,12 +8,12 @@ will need to wire a suitable {@link IntrospectionManager} against the
 <code>ValidationManager</code> so that children of a domain object presented
 for validation can be identified and in turn also validated.
 
-<p>The {@link ValidationInterceptor} and {@link ValidationAdvisor} should be
-used against each of your data access object (DAO) mutator methods, such as
-<code>SomeDao.create(Object)</code> and <code>SomeDao.update(Object)</code>.
-The interceptor will cause the <code>Object</code> to be presented to the
-<code>ValidationManager</code>, thus ensuring the domain object instance is in
-a valid state before being persisted.</p>
+<p>The {@link ValidationInterceptor} and {@link ValidationAdvisor} are provided,
+although their use is not recommended against DAOs given many <code>Validator</code>s
+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 <code>ValidationManager</code> prior to persistence. This
+is a non-AOP approach, but represetns a practical solution.</p>
 
 <p>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
 
 <p>Finally, sometimes <code>Validator</code>s might need to perform queries
 against a persistence or services layer. For example, the <code>Validator</code>
-may be checking no other user has this username. If using an ORM tool such as
-Hibernate, it is recommended your <code>Validator</code>s subclass a common
-abstract parent that provides an <code>evict(Object)</code> and 
-<code>evict(Collection)</code> method. That way your <code>Validator</code>s
-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 <code>Validator</code> 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 <code>EvictionUtils</code>
+static methods to remove objects from the session.
 </p>
 </body>
 </html>