Announcement Announcement Module
Collapse
No announcement yet.
How Common to Spring Manage Beans Created By Hibernate? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How Common to Spring Manage Beans Created By Hibernate?

    Hello,

    We've heard time and time again to "put business logic inside your business objects". Most of the time, full business logic requires dependencies to be injected in my business object. The business objects themselves (Business, Account, etc) are managed by Hibernate. Their dependencies are/should be managed by Spring.

    How common is this? If my business object is something that comes from the database, but requires external dependencies, then Spring must create and wire up my object.

    This seems fair and good, but I haven't seen many examples of this usage. It makes me wonder if my business domain objects (those that come from the database) should ever rely on external dependencies.

    My gut says yes: keep business logic in business object. Note: this does not mean DAO type actions.

    Of course, I could keep the business objects free from external dependencies, but then their ability to do work is greatly lessened.

    Thoughts? Comments? War stories?

    Thanks very much!
    Seth

  • #2
    Don't forget about good ol' message passing! I find it helpful to pass in parameters to my business objects via messages telling them to "do some work" associated with the data they encapsulate. This is often better than asking them for the data so I can do the work for them.

    For example, in my most recent application, I developed a fairly sophisticated data loader that could parse custom file formats and translate them into a normalized database schema, performing type conversion and data transformations. In this system I had a central "loader service" (a workflow object) which encapsulated the general parse / load algorithm. I also had individual parses that encapsulated logic to parse a particular file format into fields, and separate metadata objects that would then perform any necessary type conversions/data transformations on parsed field values. The result was a bunch of business objects working together, with the workflow object brokering a good deal of the message passing, telling the other objects to "do work" as part of a larger algorithm. Why was this good? Data and behaivior were kept together, the workflows algorithm was kept completely generic (meaning it was coded once and hasn't changed since even though our system has grown to support a large number of custom formats and conversion rules.)

    Just food for thought!

    Keith

    Comment


    • #3
      Don't forget about good ol' message passing! I find it helpful to pass in parameters to my business objects via messages telling them to "do some work" associated with the data they encapsulate. This is often better than asking them for the data so I can do the work for them.
      Exactly my point!

      This is exactly what I want to do with my business objects:

      BusinessObject bo = facade.load(pk);
      bo.myBusinessMethod();
      facade.save(bo);

      where bo.myBusinessMethod() might do something with velocity, for instance. So I'll need Spring to wire up my BusinessObject, which is loaded from DB via Hibernate.

      Just wondering how common that is, to wire Hibernate objects using Spring. Seems to be pretty important if we want to keep business logic inside business objects.

      Seth

      Comment


      • #4
        If I understand what you're saying correctly, the thing you will need to worry about is performance when you're loading a large set of Spring-managed domain objects from the db. Since each Hibernate-loaded domain object obviously must be a prototype (non-singleton) in the Spring context, the performance hit will come when you need to wire up 10,000 new instances of Foo. If this is not a problem, the concept sounds viable.

        The only other downside I can think of is the added complexity in your business objects (where did that object come from). However, this is a weak argument because the Spring application context provides a very valuable kind of "documentation" about how objects within the application interact. A Spring context provides a means to distinguish the forrest from the trees when you need to learn what an application does in an overall sense vs. how it handles the details.

        Comment


        • #5
          The only other downside I can think of is the added complexity in your business objects
          That's just it... if we're supposed to keep business logic in business objects, then a little complexity (in the form of depedencies) is unavoidable.

          There seems to be two different definitions of the term "business object" out there.

          1) The domain objects that come from the database. The objects from the business model.

          2) Where the work is done.

          It seems that we should be combining definitions 1 and 2. Is that a safe assumption?

          Here are the rule breakers:

          1) Business Object is not a transaction demarcation. That is the role of the Facade.
          2) Business Object is not concerned with persistence. That is the role of the DAO.

          For a Business Object to contain Business Logic, it will inevitably need dependencies. This is something I haven't seen too much in examples. All the examples only show CRUD operations as the business logic.

          Just trying to find out how practical it is to keep business logic inside business objects. And if my definition of Business Object is even correct.

          I'll post a Use Case that I think will clearly outline this problem if that would help. The more best practices we can catalogue, the better.

          Seth

          Comment


          • #6
            Martin Fowler has some valuable insite on this topic in his article on the Anemic Domain Model.

            This quote sums it up fairly well:

            In general, the more behavior you find in the services, the more likely you are to be robbing yourself of the benefits of a domain model. If all your logic is in services, you've robbed yourself blind.
            After reading that, my second argument about complexity seems null and void. The domain model is where the logic belongs, which means a bit of complexity is, as Seth said, unavoidable.

            Comment


            • #7
              That's a great quote! Thanks for posting.

              So, back to my original question/observation: If we're supposed to put business logic into business objects, then business objects need dependency injection. And if that's true, then objects loaded by persistence should be managed by Spring.

              That seems like a logical conclusion. So where are the examples?

              Of course, it's easy to figure out, and some examples have been posted to the mailing list in the past. It would be really nice to see this type of practice in some of the sample apps included with Spring. Granted, to warrant this, a more complex business operation other than CRUD would be needed.

              Leading off of this thread (good thread, and thanks for all the insight!) I've got another observation of Best Practice vs Real World. I'll start another thread to see what others think.

              Comment


              • #8
                I would tend to agree with you, though this brings up other questions. Two other thoughts off the top of my head:
                1. What about the client? Do business objects cross the service (facade) boundary, or do you copy everything over into another graph (DTOs?) before sending to the client? If you send the business objects themselves, you would have to rewire all their dependencies on the client side... if you don't send the business objects, then the client is forced to use the facade to access all business logic (good or bad, you decide).
                2. It sure would be nice if Spring had a built in facility for managing Hibernate objects. Maybe there is one and I'm not aware of it? I think right now you would have to implement a custom Hibernate interceptor, no? Anyway, Spring is geared for one time context initialization from a performance perspective... I wonder how well it would perform when injecting into thousands of instances on the fly?

                Comment


                • #9
                  1. What about the client? Do business objects cross the service (facade) boundary, or do you copy everything over into another graph (DTOs?) before sending to the client? If you send the business objects themselves, you would have to rewire all their dependencies on the client side... if you don't send the business objects, then the client is forced to use the facade to access all business logic (good or bad, you decide).
                  You bring up a good point. While your Business Objects still contain all the logic, there still is a need (sometimes) for a Facade. A good use case is remote clients, as you have pointed out. However, even with a Facade, all the logic still remains in the Business Object. The only logic in the Facade ever is to break down its course grained interface into one or more calls into the business logic world.

                  2. It sure would be nice if Spring had a built in facility for managing Hibernate objects. Maybe there is one and I'm not aware of it? I think right now you would have to implement a custom Hibernate interceptor, no? Anyway, Spring is geared for one time context initialization from a performance perspective... I wonder how well it would perform when injecting into thousands of instances on the fly?
                  There is, and I forget what it is at the moment. I don't know if it's builtin, but the facility is there and its pretty straight forward.

                  Sounds like a good example candidate for the Wiki, no?

                  Comment


                  • #10
                    It sure would be nice if Spring had a built in facility for managing Hibernate objects. Maybe there is one and I'm not aware of it? I think right now you would have to implement a custom Hibernate interceptor, no? Anyway, Spring is geared for one time context initialization from a performance perspective... I wonder how well it would perform when injecting into thousands of instances on the fly?
                    Check out the following:
                    http://thread.gmane.org/gmane.comp.j...work.user/3402
                    http://thread.gmane.org/gmane.comp.j...work.user/3513

                    And here is the code for the interceptor
                    Code:
                    /*
                     * SpringInterceptor.java
                     *
                     */
                    
                    import java.io.Serializable;
                    import java.util.HashMap;
                    import java.util.Iterator;
                    import java.util.Map;
                    import java.util.Properties;
                    
                    import net.sf.hibernate.CallbackException;
                    import net.sf.hibernate.HibernateException;
                    import net.sf.hibernate.Interceptor;
                    import net.sf.hibernate.SessionFactory;
                    import net.sf.hibernate.type.Type;
                    
                    import org.springframework.beans.BeanWrapper;
                    import org.springframework.beans.BeanWrapperImpl;
                    import org.springframework.beans.factory.BeanFactory;
                    import org.springframework.beans.factory.BeanFactoryAware;
                    import org.springframework.beans.factory.InitializingBean;
                    import org.springframework.beans.propertyeditors.ClassEditor;
                    import org.springframework.util.StringUtils;
                    
                    /**
                     * 
                     * @author Oliver Hutchison
                     * @see Interceptor
                     */
                    public class SpringInterceptor implements Interceptor, InitializingBean,
                            BeanFactoryAware {
                        private BeanFactory beanFactory;
                    
                        private SessionFactory sessionFactory;
                    
                        private String sessionFactoryBeanName;
                    
                        private Map persistantClassBeanNames = new HashMap();
                    
                        private Interceptor delegate;
                    
                        /**
                         * Constructor
                         */
                        public SpringInterceptor() {
                            super();
                        }
                    
                        public void setBeanFactory(BeanFactory beanFactory) {
                            this.beanFactory = beanFactory;
                        }
                    
                        public void setSessionFactory(SessionFactory sessionFactory) {
                            this.sessionFactory = sessionFactory;
                        }
                    
                        public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
                            this.sessionFactoryBeanName = sessionFactoryBeanName;
                        }
                    
                        public void setPersistantClassBeanNames(Properties persistantClassBeanNames) {
                            ClassEditor ce = new ClassEditor();
                    
                            for (Iterator i = persistantClassBeanNames.keySet().iterator(); i
                                    .hasNext();) {
                                String className = (String) i.next();
                                String beanName = persistantClassBeanNames.getProperty(className);
                    
                                ce.setAsText(className);
                    
                                Class clazz = (Class) ce.getValue();
                    
                                this.persistantClassBeanNames.put(clazz, beanName);
                            }
                        }
                    
                        public void setDelegateInterceptor(Interceptor delegateInterceptor) {
                            this.delegate = delegateInterceptor;
                        }
                    
                        public void afterPropertiesSet() {
                            if (beanFactory == null) {
                                throw new IllegalArgumentException("beanFactory is required");
                            }
                    
                            if ((sessionFactory == null)
                                    && !StringUtils.hasText(sessionFactoryBeanName)) {
                                throw new IllegalArgumentException(
                                        "sessionFactory or sessionFactoryBeanName is required");
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#instantiate(java.lang.Class,
                         *      java.io.Serializable)
                         */
                        public Object instantiate(Class beanClass, Serializable id)
                                throws CallbackException {
                            String name = (String) persistantClassBeanNames.get(beanClass);
                            if (name == null) {
                                if (delegate == null) {
                                    return null;
                                } else {
                                    return delegate.instantiate(beanClass, id);
                                }
                            }
                            if (beanFactory.isSingleton(name)) {
                                throw new UnsupportedOperationException("Bean name [" + name
                                        + "] must be a prototype. i.e. singleton=\"false\"");
                            }
                            Object newEntity = beanFactory.getBean(name);
                            try {
                                BeanWrapper wrapper = new BeanWrapperImpl(newEntity);
                    
                                wrapper.setPropertyValue(getSessionFactory().getClassMetadata(
                                        beanClass).getIdentifierPropertyName(), id);
                            } catch (HibernateException e) {
                                throw new CallbackException(
                                        "Error getting identifier property for class " + beanClass,
                                        e);
                            }
                            return newEntity;
                        }
                    
                        private SessionFactory getSessionFactory() {
                            if (sessionFactory == null) {
                                sessionFactory = (SessionFactory) beanFactory
                                        .getBean(sessionFactoryBeanName);
                            }
                    
                            return sessionFactory;
                        }
                    
                        /**
                         * 
                         * @see net.sf.hibernate.Interceptor#onLoad(java.lang.Object,
                         *      java.io.Serializable, java.lang.Object[], java.lang.String[],
                         *      net.sf.hibernate.type.Type[])
                         */
                        public boolean onLoad(Object entity, Serializable id, Object[] state,
                                String[] propertyNames, Type[] types) throws CallbackException {
                            if (delegate != null) {
                                return delegate.onLoad(entity, id, state, propertyNames, types);
                            } else {
                                return false;
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#onFlushDirty(java.lang.Object,
                         *      java.io.Serializable, java.lang.Object[], java.lang.Object[],
                         *      java.lang.String[], net.sf.hibernate.type.Type[])
                         */
                        public boolean onFlushDirty(Object entity, Serializable id,
                                Object[] currentState, Object[] previousState,
                                String[] propertyNames, Type[] types) throws CallbackException {
                            if (delegate != null) {
                                return delegate.onFlushDirty(entity, id, currentState,
                                        previousState, propertyNames, types);
                            } else {
                                return false;
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#onSave(java.lang.Object,
                         *      java.io.Serializable, java.lang.Object[], java.lang.String[],
                         *      net.sf.hibernate.type.Type[])
                         */
                        public boolean onSave(Object entity, Serializable id, Object[] state,
                                String[] propertyNames, Type[] types) throws CallbackException {
                            if (delegate != null) {
                                return delegate.onSave(entity, id, state, propertyNames, types);
                            } else {
                                return false;
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#onDelete(java.lang.Object,
                         *      java.io.Serializable, java.lang.Object[], java.lang.String[],
                         *      net.sf.hibernate.type.Type[])
                         */
                        public void onDelete(Object entity, Serializable id, Object[] state,
                                String[] propertyNames, Type[] types) throws CallbackException {
                            if (delegate != null) {
                                delegate.onDelete(entity, id, state, propertyNames, types);
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#preFlush(java.util.Iterator)
                         */
                        public void preFlush(Iterator entities) throws CallbackException {
                            if (delegate != null) {
                                delegate.preFlush(entities);
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#postFlush(java.util.Iterator)
                         */
                        public void postFlush(Iterator entities) throws CallbackException {
                            if (delegate != null) {
                                delegate.postFlush(entities);
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#isUnsaved(java.lang.Object)
                         */
                        public Boolean isUnsaved(Object entity) {
                            if (delegate != null) {
                                return delegate.isUnsaved(entity);
                            } else {
                                return null;
                            }
                        }
                    
                        /**
                         * @see net.sf.hibernate.Interceptor#findDirty(java.lang.Object,
                         *      java.io.Serializable, java.lang.Object[], java.lang.Object[],
                         *      java.lang.String[], net.sf.hibernate.type.Type[])
                         */
                        public int[] findDirty(Object entity, Serializable id,
                                Object[] currentState, Object[] previousState,
                                String[] propertyNames, Type[] types) {
                            if (delegate != null) {
                                return delegate.findDirty(entity, id, currentState, previousState,
                                        propertyNames, types);
                            } else {
                                return null;
                            }
                        }
                    }

                    Comment


                    • #11
                      Intercepting Hibernate

                      I have a question related to that SpringInterceptor

                      Let's suposse we have mapped 200 classes to tables but we need inject dependencies to about a 20% (40 of them)

                      Does SpringInterceptor enter on scene with every type of object Hibernate hidrate?

                      In other words, if a query returns 1000 object instances with all of them belonging to the remaining 80%, is SpringInterceptors going to be called 1000 times in vain?

                      If it is, there exists some workaround for such undesirable situation?

                      Comment


                      • #12
                        There might be a way to first query the BeanFactory to see if it handles a particular class. That might be a quick lookup.

                        Or, if you know which classes you need to manage, you could configure that to the INterceptor, by checking a list of classes you want Spring to manage. That would at least bypass BeanFactory immediately with the added expense of a little more to configure.

                        Comment


                        • #13
                          Let's suposse we have mapped 200 classes to tables but we need inject dependencies to about a 20% (40 of them)
                          You can specify the 20% using "persistantClassBeanNames" property.

                          Let's suposse we have mapped 200 classes to tables but we need inject dependencies to about a 20% (40 of them)

                          Does SpringInterceptor enter on scene with every type of object Hibernate hidrate?
                          Yes, this is how hibernate works, and this is a well know limitation for Hibernate Interceptors.

                          If it is, there exists some workaround for such undesirable situation?
                          even Hibernate3 new Listeners will be called for each event / object.

                          Comment


                          • #14
                            Originally posted by sethladd
                            There might be a way to first query the BeanFactory to see if it handles a particular class. That might be a quick lookup.

                            Or, if you know which classes you need to manage, you could configure that to the INterceptor, by checking a list of classes you want Spring to manage. That would at least bypass BeanFactory immediately with the added expense of a little more to configure.
                            This functionality is already implemented in SpringInterceptor. See my above post.

                            Comment


                            • #15
                              We have solved the problem of business object dependency injection after Hibernate loading

                              Our solution is not the best, it has PROS and CONS but we have tried to mitigate its CONS

                              I suggest you take a look and make your opinions

                              We have in our "domain" packages classes which model business objects

                              Some of them needs to be injected with behavior after being recovered from database records. Let's say com.company.domain.product.Product as an example

                              So, what we have done was subclass Product as an Hibernate specific class in a integration layer package

                              Thus, we have obtained com.company.integration.hibernate.product.ProductH ibernate

                              ProductHibernate implements an old (deprecated?) Hibernate interfase: Lifecycle with some methods of interest, in this case, onLoad(Session, ...)

                              In "Hibernate in Action", Bauer and King suggest not to use that interfase because implies adding Hibernate specific methods to a class which has only business methods

                              We realize that point but try to mitigate it by isolating Hibernate specific logic in a subclass in a strictly isolated hibernate package, warrantying the fact that in business layer no class will know that Product's are really ProductHibernate

                              If we have to change business logic, we change Product

                              Some PROS: we don't interfere Hibernate on every loaded class. Only in those classes we want to be interfered (really less than 5% of O/R mappings ocurred during execution)

                              Some limitation: what happen if tomorrow we need, in business layer, subclassing Product in ProductSpecial? Shall we need in such case to subclass again in ProductSpecialHibernate?

                              Really we now our solution has serious pitfalls but, with certain conditions, works fine

                              Comment

                              Working...
                              X