View relationships

Discussions

J2EE patterns: View relationships

  1. View relationships (11 messages)

    This design uses a value object as a simple object to contain the values of a particular entity bean, which is participating in a relationship. The ValueObject uses direct database access to obtain the values of the related entities.
    Example.
    Support group has many students (possibly thousands)

    We may want to retrieve all of those students within 200kms of the support group address, for example.

    To perform this query without instantiating EJBs for each student is difficult. In object-land, one would simply instantiate all of the objects and iterate over those.

    In EJB land, this approach is not reasonable. The DAO pattern addresses this concern, but not specifically for CMR.

    The design here uses a transient collection to contain view objects representing all of the beans in the relationship, and is populated on ejbLoad. It is simply a SQL statement from a DAO.

    For example, select * from student where support_group_id=XXX

    Once instantiated, these objects can be used for view based queries, filtering, etc. It also can be easily modified to cater for logical deletes in databases (something CMR cannot do). And it works in a very similar manner to normal object relationships.

    Further, the view object can instantiate an entity bean corresponding to its own data. So if the previous example was performed, and a flag was required to be set on 10% of the students, those 10% could be instantiated as entity beans.


    Example implementation (from support group with the supportGroup.getStudent functionality):
    ------------------------------
    The loadRelationships method is called from ejbLoad.
    ------------------------------
    private void loadRelationships() {
       try {
         studentViewObjects = new SupportGroupDAO().
    getStudentViewsForSupportGroup(((Long)this.entityContext.getPrimaryKey()));
      
       } catch (Exception e) {
         throw new EJBException("Failure retrieving view relationships"+e.getMessage(),e);
       }
      }



    ------------------------------
    This method is a accessor method on the Entity Bean
    ------------------------------
    public Collection getStudentViewsForSupportGroup(Long groupPrimaryKey) throws Exception {
      Connection conn = null;
      PreparedStatement stmt = null;
      ResultSet rs = null;
      Long seqNum = null;
     
      Collection valueObjectsRepresentingRelationship = new ArrayList();
      try {
          conn = super.getConnection();
                stmt = conn.prepareStatement(
          "select s.* from student s where s.group_id=?");

          stmt.setLong(1, groupPrimaryKey.longValue());
          rs = stmt.executeQuery();

          while((rs != null) && (rs.next())) {
            StudentViewObject v = new StudentViewObject(rs);
            valueObjectsRepresentingRelationship.add(v);
          }

          super.cleanUp(conn, stmt, rs);
          return valueObjectsRepresentingRelationship;
        }
        catch(Exception ex) {
          if (logCategory.isDebugEnabled()) {
            logCategory.debug("Exception on SupportGroupDAO: " + ex);
          }
          throw ex;
        }
      }

    ------------------------------
    In the client.
    This function asks a Student Entity bean for its support group view objects. It iterates through these and instantiates an entity bean for each, and then asks for the student view objects for each support group bean.
    ------------------------------
    Collection students = new ArrayList();
    Student student = home.findByPrimaryKey(studentID);
      for(Iterator it = student.getSupportGroupsView().iterator(); it.hasNext(); )
      {
        SupportGroupViewObject ssg = ((SupportGroupViewObject)it.next());
        for (Iterator it2 = ssg.getEntityBean().getStudentViews().iterator(); it2.hasNext(); )
        {
          students.add(((StudentViewObject)it2.next()).studentID);

    The point to note about the above example is that very little code is different from what would be required with CMR. The SupportGroupViewObject is asked for its corresponding entity bean (as we decided that no relationship data would be contained in the view object), and this entity bean is asked to get its students (as view objects).
    This technique can be used everywhere that CMR relationships would normally be used, and CMR can be used if updates to the data are required.

    Many additions to this can be envisioned, especially more completely wrapping CMR relationships to hide the CUD behaviour.

    Questions, comments, criticisms and downright derision all appreciated!

    Cheers
    Greg

    Threaded Messages (11)

  2. View relationships[ Go to top ]

    I don't understand what the two different methods are supposed to demonstrate. They both appear to do the same thing, but it's hard to understand without a more straightforward problem domain, or a database schema.

    Plus, I'm not sure what problem this is solving. It seems to me that if you want to avoid doing CMR, than you it would much simpler to put finder methods in your beans, and then implement the relationship methods manually using them. For example, if you have a Person who has a relationship to a PhoneNumber, you would have:

    PhoneNumberHome:
       public List findByPersonId(long personId);

    Person:
       public List getPhoneNumbers();

    PersonBean:

       public List getPhoneNumbers()
       {
          // assume phoneNumberHome was init'ed previously
          return phoneNumberHome.findByPersonId(getId());
       }

    Then, PhoneNumberBean has the repsonsiblity to determine how to get the right list of PhoneNumbers (possibly using a DAO, or possibly using CMP, or whatever it wants).

    This seems much simpler and easier to understand. Plus, there is less coupling between the two entities involved in the relationship.
  3. View relationships[ Go to top ]

    Apologies for not being more clear.

    What I am trying to achieve is to make CMR actually useful, without breaking the relationship paradigm by using finders. CMR is fine if you are dealing with small numbers of objects in relationships. It does not work when numbers become large, except for CUD ops, where it works fine.

    The problem with finders is that business logic needs to be implemented in them, and that EJB-QL is a poor substitute for the processing power of java.

    In your example, what if you want to perform complex business-logic processing to decide what phone numbers are relevant? What if the person had 5000 phone numbers, and you needed to look at the data in all of them, maybe compare their data to another datasource? With CMR, you would need to instantiate all 5000 phone numbers as entity beans.

    Ouch.

    What I am trying to overcome is the practical inability to achieve anything useful with CMR (apart from CUD). I want to use CMR, as it is a nice technique for CUD operations. And I am trying to stay as close as possible to the object-relationship paradigm.

    Finders are somewhat useful, but are ultimately too restrictive. And you start having to mix finders, with CMR, with filtering, and DAO, if you want to achieve anything useful with large numbers of objects in relationships.

    This technique gives you one way that can address all of the issues with CMR, while keeping the CUD benefits. It allows the full flexibility of java processing without requiring EJB-QL (and the subsequent 'hiding' of business logic), and also addresses the view object requirement for relationships.

    I think this is a superior method than mixing CMR with finders, or manually managing relationships.

    Let me know what you think!

    Cheers
    Greg
  4. View relationships[ Go to top ]

    I agree that it is lame that you cannot implement your own finder while using CMP and CMR for persistence, though (although you can implement finders in Java with CMP/CMR, you just have to do it in the XML file and pull your hair out trying to debug it).

    I just don't see how this solves that problem. I mean, unless you can do all your comparisons and testing in the database, it doesn't seem like it makes much difference. If you could accomplish your task via a SQL statement, you could certainly do it with EJB-QL, and if not, you either must create a stored procedure (business logic in DB, yuck), or get the data and do the comparisons in code. In that case, you are creating a bazillion objects anyway, so why go through the trouble? Local Entities should be fairly efficient. The biggest performance issue with EJBs is the network overhead, which you don't have when using Local Interfaces. This basically brings you down to a matter of object creation, which you will do in any case.

    It seems like if you are trying to create a situation in which you can put business logic in finder-type methods, implemented in Java, the more straightforward approach would be to implement said methods in Stateless Session EJBs, or in custom methods of an Entity Bean.
  5. View relationships[ Go to top ]

    What it lets you do is deal with all your entity data as normal java objects, and only instantiate them as entity beans if you want to modify the data.

    In my experience, Local Entities are better than Remote, but the overhead of creating them is still excessive, if you are dealing with thousands of objects, and only want to update some small percentage.

    Java is much more powerful for querying and filtering than SQL, so it makes sense to have that code in the mid-tier java.

    Dealing with the problem at a session bean level is certainly an option, but that is then saying that CMR is useless, and you will never say something like: supportGroup.getStudents(). You would instead say, sessionBean.getStudentsForSupportGroup(SupportGroup). This is ok, but has nothing to do with CMR.

    The design is essentially a wrapper for CMR to make CMR useful regardless of the number of underlying rows in the table, without resorting to finders, which instantiate expensive beans. The technology (CMR) is there, and it sorta works, but this way makes it sorta work in a more generalisable way, which expands to every case where relationships need to be queried.

    Cheers
    Greg
  6. View relationships[ Go to top ]

    "In my experience, Local Entities are better than Remote, but the overhead of creating them is still excessive, if you are dealing with thousands of objects, and only want to update some small percentage."

    I was under the impression (from various Performance books) that object creation is what is usually a big hit, along with network access. Local Beans remove the problems of network access, and as far as object creation goes, your pattern creates just as many as using a Finder and Local interfaces does, so I'm still not buying the performance gain (at least not a significant one).

    I guess perhaps the assumption you are making is that if you are using CMR and the "to-many" end of the relationship has a ton of entities, performance is siginificantly worse than if you were to implement the relatinship "manually", by using JDBC to create plain Java objects. I'm not convinced of that, based on logic above (although empirical evidence could disuade me). Furthermore, you now have two codebases to keep in sync with your database schema. That is not fun to deal with.

  7. View relationships[ Go to top ]

    Thanks for the reply.
    >I guess perhaps the assumption you are making is that if
    >you are using CMR and the "to-many" end of the
    >relationship has a ton of entities, performance is
    >siginificantly worse than if you were to implement the
    >relatinship "manually", by using JDBC to create plain Java
    >objects. I'm not convinced of that, based on logic above

    Sorry, I dont have direct empirical evidence to present, but this certainly is not an assumption! If it wasnt an issue, I would never have proposed this solution.
    :)
    Local beans do have a large performance hit, as compared to straight objects. I have tested this (with BES 5.1, maybe others do differently. BES also has a cool windowing strategy, but it doesnt seem to help a lot), and I am convinced of it. It should be very easy for you to test, simply have a relationship with a couple of thousand entities
    (school->students) for example, and attempt to instantiate all of the students (you will have to iterate through the collection obtaining some field).

    >Furthermore, you now have two codebases to keep in sync
    >with your database schema. That is not fun to deal with.
    Well, no not really. You have one, which is the SQL code for the view objects. The CMP code remains the same, so there is no duplication of code.

    It does present an overhead, and I dont consider it a pattern. It is a workaround for deficiencies in EJB's. But I think it is one that is reasonably straightforward to implement, and fits on top of CMR quite well. And it allows you to do high level processing in Java not SQL, for a relatively small overhead.



    Cheers
    Greg
  8. ejbSelect[ Go to top ]

    Provided that your app server is smart enough with it's CMR/ejbql, would it be better to define the relationship as CMR then have a seperate ejbSelect method that uses said relationhsip ?

    Say in the supportgroupbean ejbSelect:

    public Collection getStudentsWithinDistance(double distance)
    {
    return ejbSelectAspectResult((Student)entityContext.getEJBLocalObject(), aspect);
            }
            catch(FinderException e)
            {
                throw new EJBException(e.toString());
            }
        }


    Collection ejbSelectStudentsWithinDistnance(SupportGroup group, double distance)

    with ejb-ql:

    SELECT OBJECT(result) FROM SupportGroup group, IN(group.students) AS result WHERE
    group = ?1 AND result.distance
  9. ejbSelect[ Go to top ]

    Sorry, posted that before fully editing, that method should be:

     public Collection getStudentsWithinDistance(double distance)
     {
               return ejbSelectStudentsWithinDistance(
    (SupportGroup)entityContext.getEJBLocalObject(), distance);
     }
  10. View relationships cont...[ Go to top ]

    Hi, thanks for your reply.

    What you suggest would be ideal, if EJB-QL approached the processing capability of Java. Even SQL is limited in the amount and types of processing it can do.

    What I was expecting with CMRs, I guess, is that they behaved and were as useful as standard object relationships, because that is what they pretended to be.

    Once I started using them, I found out that their effectiveness was essentially nil, except for allowing a convenient persistance mechanism. It perhaps is more an inditement on Entity Beans rather than CMR itself, because entity beans are way too inefficient to be used like objects. Which is really unfortunate, because most times with development of EJB systems, this is the exact functionality you require.

    So this pattern is intended to stop you instantiating entity beans if you can help it, but keeping essentially the same mechanism as CMR provides. Of course, there are many other ways to do this, but I think this one is the clearest, the most powerful, and the least likely to cause people headaches... but feel free to disagree!

    Cheers
    Greg
  11. Nice Pattern![ Go to top ]

    Very cool. I needed something just like this. I was wondering if there was a better way to bulk load, as even the local interfaces have to iterate and load on every cycle.

    I agree that writing sql for large queries is easy. We all know sql. Thats why lots of people do this in session beans. But i like this approach better, its clean and its also an easy way to persist the relationship.

    Yes, you have object creation and memory usage, but that's manageable when you consider how fast it is (hence the session bean pattern in the first place).

    On my desktop (i.e. not fast) i can load 300 medium to small sized objects from a session bean calling entity beans by local interfaces in 4 seconds. And that's *after* they were initially loaded into the containers "pre-cache" - i.e. in memory. The cost of getting them all loaded in the first place cost > 9 seconds. The cost of this pattern is 2 seconds. plus you get free relationships. seems cool to me.
       

    Thanks!

    Trevor
  12. Nice Pattern![ Go to top ]

    Do you think the pattern could be extended so that it only read from the database when the entity beans backing some or all of the data in the view change? on ejbStore() of all of the affected entity beans set a flag on this ViewRelationshipBean so that its ejbLoad() only calls the Data Access Object when boolean doLoad = true. save the relationship in an instance variable of the ViewRelationshipBean. of course, this only makes sense of data that is viewed very frequently and you want to save database queries and excessive object creation? maybe?