Announcement Announcement Module
Collapse
No announcement yet.
Design of DAOs with Persistence Layer Differences Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Design of DAOs with Persistence Layer Differences

    This is a question that I always have a hard time answering. Therefore, I am turning to the Spring community and their wealth of knowledge for some opinions. I hope this question is clear enough to answer.

    When designing an application, I am a proponent of the DAO concept since I have worked on applications that have needed to change the underlying persistence layer. However, I am always stuck on how to deal with the different features of persistence layers. Let's examine my 2 favorite - persistence of children objects and custom interceptors.

    Parent-Children DAOs -
    Some persistence systems, like Hibernate, have the ability to automatically persist children objects when a parent object is saved. While this is amazingly convenient, it seems to tie the architecture into systems that use this (see my next point regarding persistence layer custom interceptors). Do you take advantage of this feature or do you have the parentDao.save() call the respective childrenDao.save() method for each child object?

    Custom Interceptors -
    Once again, some persistence systems like Hibernate have the ability to apply interceptors for certain events, i.e. when saving an object. Is it better to apply these interceptor in the persistence layer OR around the DAO method?

    Back to my real world example, the two things I need to do when saving an object are set the creation and modification dates and to index the object in Lucene. If I use Hibernate's parent-child automatic persistence, the best way to have this done when saving the object is to create a Hibernate interceptor. However, this adds more responsibility to the persistence layer than generic persistance and does not make it portable to another implementation of the DAO.

    In the end, I start wondering if this is even worth caring about since it is rare that the underlying persistence system changes. (Although, it has happened to me before - see above.)

    Any thoughts? Please...


    Cheers,
    Matthew

  • #2
    The persistence chapter in J2EE without EJB has an excellent discussion of DAO interface design, written by Juergen.

    I would probably do your interception requirement using Spring AOP, rather than an Hibernate interceptor, as it's more portable. However, the Hibernate interceptor approach is also perfectly fine: it's not going to be a lot of code to rewrite if you ever port to another persistence framework, as presumably your Interceptor will only serve as an entry point to invoke another object that actually drives the indexing.

    Comment


    • #3
      A further advantage of a Spring MethodInterceptor vs a Hibernate Interceptor is your MethodInterceptor can access the Hibernate Session (to perhaps validate the Object, as we do) and also change the properties of the Object being persisted.

      Comment


      • #4
        Since I own the great book mentioned, I went back and re-read the Persistence chapter today. Yet, I still have the same questions as before. At this point, it looks like I have 2 options since I want to perform operations on any object that is saved/updated.

        Very fine-grained DAOs -
        With this option, I would completely abandon Hibernate's parent-child mapping and persistence. For example, OrderDAO.save(order) would call lineItemDao.save(lineItem). This would allow my Spring interceptors to occur whenever the save method on a DAO is called. Otherwise, my Spring interceptor would need to know how to walk the object graph.

        Hibernate Interceptor -
        Since Hibernate interceptors work on save and updates regardless of where it was called. However, once again, this ties this functionality to a specific persistence mechanism.

        Did I miss something in the chapter? Thanks in advance.

        Ben, how are you dealing with these issues?


        Cheers,
        Matthew

        Comment


        • #5
          A fairly detailed explanation of our domain object modelling approach is at http://forum.springframework.org/showthread.php?t=10994. It works pretty well. The Validator and bindSupport() methods are responsible for delegating to any superclass-related Validator/bindSupport() or child object Validator/bindSupport().

          Just re-reading your original post, we use a stored procedure to perform indexing of content when it is inserted or updated in the database. However, that's a database-based full text indexing solution so the same approach cannot be used with Lucene.

          When building your DAO implementations, consider using a domain object independent HibernateDao which implements Dao. By being domain object independent your services layer will perform the necessary casting to and from the target object type and you will have 80% of your DAO functionality in one class. Here's the interface contract for Dao so you can see what I mean:

          Code:
          public interface Dao {
              //~ Methods ================================================================
          
              /**
               * Create a new object, the id value will be ignored.
               *
               * @param value (without the id property initialized)
               *
               * @return the value created (with the id property initialised)
               */
              public PersistableEntity create(PersistableEntity value);
          
              /**
               * Delete an object.
               *
               * @param value the value to delete
               */
              public void delete(PersistableEntity value);
          
              /**
               * Return all persistent instances.
               *
               * @return List of PersistableEntity objects
               */
              public List findAll();
          
              /**
               * Find a List of PersistableEntitys, by their identifiers.
               *
               * @param ids collection of identifiers
               *
               * @return the values with those ids
               */
              public List findId(Collection ids);
          
              /**
               * Load an object by its identifier.
               *
               * @param id identifier
               * @return the item or null if not found
               */
              public PersistableEntity readId(Serializable id);
          
              /**
               * Loads entity by id, or null if not found, then evict the loaded
               * entity from any ORM session. Intended for use by Validators executing
               * within the same transaction as an ORM operation involving the entity,
               * which would cause session problems.
               * 
               * @param id identifier
               * @return the item or null if not found
               */
              public PersistableEntity readIdThenEvict(Serializable id);
          
              /**
               * Find objects with properties matching those of value.
               * 
               * <P>
               * Objects are matched on the basis of query by example. Properties whose
               * value is null, and Collections, are ignored in the query by example
               * comparison.
               * </p>
               *
               * @param value parameters to filter on
               * @param firstElement the first result, numbered from 0
               * @param maxElements the maximum number of results
               *
               * @return the request page of the result list
               */
              public PaginatedList scroll(PersistableEntity value, int firstElement,
                  int maxElements, String orderByAsc);
          
              /**
               * Indicates whether this DAO instance provides persistence services for
               * the specified class.
               *
               * @param clazz which should be a subclass of PersistableEntity
               *
               * @return true or false, indicating whether or not the passed class is
               *         supported by this DAO instance
               */
              public boolean supports(Class clazz);
          
              /**
               * Update an object.
               *
               * @param value value to update
               *
               * @return the updated value
               */
              public PersistableEntity update(PersistableEntity value);
          }
          Last edited by robyn; May 14th, 2006, 12:20 PM.

          Comment


          • #6
            matthew,

            I ask myself this question on a daily basis, and come to a different conclusion daily Let me rephrase your question, which sounds actually like two questions:
            1. Do I make my DAO's fine (1 dao per class) or course (a dao that handles persistence for parent and child relationships).
            It doesn't really matter. What matters is that you are explicit about your choice, and make it clear in code. The best way to do this imho is to use to use dao interfaces, and have spring wire up the concrete classes.
            You might hear a little voice in your head saying 'what if I use hibernate cascade, then switch impls and something doesn't get saved'. If you are explicit about the interface contract, then you will not have to worry as much. So how do you decide, course or fine? Well, look at the two extremes: a dao for every object or one dao for all objects. Then look at how the dao will be used by the client. It would be convenient to have one really course grained class, but harder to maintain and implement. Managing dozens of dao's might become tedious or lose persistence relationships (everytime you save x, you must save y). I would try to identify those persistence relationships, and usage patterns and shoot for something in between. If your class gets too big, refactor, if meaning is blurred by lack of cohesion, refactor.

            2. Question two sounds like "should I add non-persistence functionality to my dao because the additional functionality is db-related?"
            I'd recommend not to do this. If you are using the database to implement something other than plain persistence (calculations in an update statement or the searching functionality mentioned), it's usually better to create an interface, and create a db implementation. The fact that the db does the work is an implementation detail.

            That's my two cents. I have struggled with these questions, and I have found these approaches to work for me. ymmv.

            Comment


            • #7
              Ben, I have read both of yours posts a few times. Yet, I still don't see how I would be able to update the creation & modifications dates and update the Lucene index when saving/updating a persistent entity. I am probably not mentally connecting something.


              Cheers,
              Matthew

              Comment


              • #8
                Maybe these code snippets will help:

                Code:
                public class BindAndValidateInterceptor extends BaseObject implements MethodInterceptor &#123;
                	public Object invoke&#40;MethodInvocation mi&#41; throws Throwable &#123;
                		Object&#91;&#93; args = mi.getArguments&#40;&#41;;
                		for &#40;int i = 0; i < args.length; i++&#41; &#123;
                			if &#40;args&#91;i&#93; instanceof PersistableEntity&#41; &#123;
                				PersistableEntity validatable = &#40;PersistableEntity&#41; args&#91;i&#93;;
                				if &#40;logger.isDebugEnabled&#40;&#41;&#41;
                					logger.debug&#40;"bindAndValidate calling for&#58; " + validatable.toString&#40;&#41;&#41;;
                				validatable.bindAndValidate&#40;&#41;;
                			&#125;
                		&#125;
                		return mi.proceed&#40;&#41;;
                	&#125;
                &#125;
                Code:
                /**
                 * Advisor for the &#123;@link BindAndValidateInterceptor&#125;.
                 * 
                 * <p>
                 * Intended to be used with Spring's  <code>DefaultAdvisorAutoProxyCreator</code>.
                 * </p>
                 * 
                 * <p>
                 * Registers <code>BindAndValidateInterceptor</code> for every <code>Method</code>
                 * against a class that directly or through its superclasses implements 
                 * &#123;@link Dao&#125; and has a signature of&#58;
                 * <ul>
                 * <li>create&#40;*, PersistableEntity, *&#41;</li>
                 * <li>update&#40;*, PersistableEntity, *&#41;</li>
                 * </ul>
                 * 
                 * <P>As shown, any additional method arguments aside from <code>PersistableEntity</code>
                 * will still cause the advisor to match them.
                 * 
                 * <p>The above list may be refactored in the future to a property but at this time
                 * it is considered unnecessary.
                 *
                 * @author Ben Alex
                 * @version $Id&#58; BindAndValidateAdvisor.java,v 1.1 2004/11/02 23&#58;45&#58;11 administrator Exp $
                 */
                public class BindAndValidateAdvisor extends StaticMethodMatcherPointcutAdvisor &#123;
                    //~ Instance fields ========================================================
                
                    //~ Constructors ===========================================================
                
                    public BindAndValidateAdvisor&#40;BindAndValidateInterceptor advice&#41; &#123;
                        super&#40;advice&#41;;
                        if &#40;advice == null&#41; &#123;
                            throw new AopConfigException&#40;
                                "Cannot construct a BindAndValidateAdvisor using a "
                                + "null BindAndValidateInterceptor"&#41;;
                        &#125;
                    &#125;
                
                    //~ Methods ================================================================
                
                    public boolean matches&#40;Method m, Class targetClass&#41; &#123;
                        
                    	if &#40;!m.getName&#40;&#41;.equals&#40;"create"&#41; && !m.getName&#40;&#41;.equals&#40;"update"&#41;&#41; &#123;
                    		return false;
                    	&#125;
                    	
                    	// Iterate methods to see if any are assignable from PersistableEntity
                    	Class&#91;&#93; params = m.getParameterTypes&#40;&#41;;
                    	boolean found = false;
                    	for &#40;int i = 0; i < params.length; i++&#41; &#123;
                    		if &#40;params&#91;i&#93;.isAssignableFrom&#40;PersistableEntityLong.class&#41;&#41; &#123;
                    			found = true;
                    		&#125;
                    	&#125;
                    	
                    	if &#40;!found&#41; &#123;
                    		return false;
                    	&#125;
                    	
                    	// A suitable Method was found, so ensure this is a DAO
                    	Class currentClass = targetClass;
                        while &#40;currentClass != null&#41; &#123;
                        	if &#40;currentClass.isAssignableFrom&#40;Dao.class&#41;&#41; &#123;
                        		return true;
                        	&#125;
                        	currentClass = currentClass.getSuperclass&#40;&#41;;
                        &#125;
                        return false;
                    &#125;
                
                &#125;
                Code:
                public abstract class BusinessObject implements Serializable &#123;
                
                	/**
                	 * Ensures the object-wide state is correct.
                	 * 
                	 * Will NEVER change the object state. Will test object-wide parameters,
                	 * and as such SHOULD be called after the &#123;@link #bindSupport&#40;&#41;&#125; method.
                	 * 
                	 * @throws BindException if there is a problem
                	 */
                	public final void validate&#40;&#41; throws BindException &#123;
                        Class clazz = this.getClass&#40;&#41;;
                        Validator v = this.getInternalValidator&#40;&#41;;
                        
                        if &#40;v == null&#41;
                        	throw new IllegalArgumentException&#40;"Cannot validate as Validator not wired against&#58; " + this&#41;;
                        
                        if &#40;v.supports&#40;clazz&#41;&#41; &#123;
                            Errors errors = new BindException&#40;this, clazz.getName&#40;&#41;&#41;;
                            v.validate&#40;this, errors&#41;;
                
                            if &#40;errors.getErrorCount&#40;&#41; > 0&#41; &#123;
                                throw &#40;BindException&#41; errors;
                            &#125;
                        &#125; else &#123;
                        	throw new IllegalArgumentException&#40;"Validator " + v.getClass&#40;&#41;.getName&#40;&#41; + " does not support this class&#58; " + clazz.getName&#40;&#41;&#41;;
                        &#125;
                	&#125;
                	
                	public abstract Validator getInternalValidator&#40;&#41;;
                	
                	/**
                	 * Performs any object-wide tidying of properties etc. Should be called
                	 * by business methods prior to presenting the object for saving.
                	 * 
                	 * <P>Default implementation does nothing. Subclasses should override.
                	 */
                	public void bindSupport&#40;&#41; &#123;&#125;
                
                	/**
                	 * Delegates to the &#123;@link #bindSupport&#40;&#41;&#125; and &#123;@link #validate&#40;&#41;&#125; methods.
                	 * Should be called by business methods needing to ensure they are in a
                	 * valid state. This method
                	 * SHOULD NOT be called from a MVC controller, as the MVC controller will
                	 * separately invoke the <code>Validator</code> and &#123;@link #bindSupport&#40;&#41;&#125;
                	 * methods. 
                	 */
                	public final void bindAndValidate&#40;&#41; throws BindException &#123;
                		this.bindSupport&#40;&#41;;
                		this.validate&#40;&#41;;
                	&#125;
                &#125;
                PersistableEntity extends BusinessObject.

                Comment


                • #9
                  Thanks

                  Ben, I just had to say thank you for posting this code.

                  I've been struggling with these same issues for a while now. Searching the boards turns up a lot of similar questions but no concrete answers.

                  Looking at your code, it's definitely a whole other level of "getting it". I keep reading the theory (J2EE w/o EJB, etc) but just haven't found anything putting those practices into place. The Spring sample apps are good, but are too simple to answer issues I'm running in to. Seeing the actual interfaces you're using helps an enourmous amount.

                  - Ryan

                  Comment


                  • #10
                    Originally posted by Ben Alex
                    A fairly detailed explanation of our domain object modelling approach is at http://forum.springframework.org/showthread.php?t=10994. It works pretty well. The Validator and bindSupport() methods are responsible for delegating to any superclass-related Validator/bindSupport() or child object Validator/bindSupport().
                    Hi Ben - just wondering how your Validators delegate to child object Validators. The ideal would seem to walk through collections and call:
                    Code:
                    childObject.validate();
                    However, validate throws a BindException, which the Validator.validate(Object obj, Errors errors) needs to handle.
                    If we call:
                    Code:
                    childObject.getInternalValidator().validate(childObject, errors)
                    we're
                    - missing out on all of the logic included in your BusinessObject.validate() method.
                    - the errors collection now contains a mix of field errors from the parent object and child objects.

                    Any ideas here?
                    Best regards,
                    Last edited by robyn; May 19th, 2006, 06:54 AM.

                    Comment


                    • #11
                      Hi Ben, something struck me about the snippets you posted and so I would like to ask a question (unrelated to the topic),

                      you wrote (in a snippet):

                      // A suitable Method was found, so ensure this is a DAO
                      Class currentClass = targetClass;
                      while (currentClass != null) {
                      if (currentClass.isAssignableFrom(Dao.class)) {
                      return true;
                      }
                      currentClass = currentClass.getSuperclass();
                      }
                      return false;
                      I am curious why you chose to implement the while loop rather then write the statement as:

                      if( Dao.class.isAssignableFrom(currentClass) )
                      return true;
                      else
                      return false;
                      Is there a performance advantage or some other reason of which I need to be aware?

                      Cheers
                      Steve

                      Comment

                      Working...
                      X