CoarseGrained BMP fat key pattern

Discussions

J2EE patterns: CoarseGrained BMP fat key pattern

  1. CoarseGrained BMP fat key pattern (2 messages)

    Hi:
        This pattern is designed in the purpose of minimise the times of hitting database.
        My pattern hits DB number of times from 3 to n+1, depends which strategy you are using to store the data to DB, by using this pattern. It could be only 3 times. For ejbLoad, it is always 3 times hitting DB.
        For my pattern, it offers these benifits:
    1. every find method, returns a single PK, means a single bean instance. It can minimise the bean
    number, instance overhead, memory, GC, etc.
    2. To access/read/modify n row data in DB, the bean ejbFind hits DB once, ejbload hits DB once,
    ejbStore hits DB once.
    3. If no data is changed in bizz method at all, the ejbStore will not hit DB, and more, subsequent
    ejbLoad will not hit DB. (assume only update DB by beans, no isql call to DB)

    4. If data is changed in bizz method, ejbStore can store only these changed columns, not all columns.
    And, if only few rows are changed, only these changed rows will be saved to DB.

    5. All entity beans come down from this parent bean, so all beans no need to write ejbfind, ejbload,
    ejbStore methods. Only concentrate on bizz method.

    6. If no data is changed, all bean instances of the same class (same DB table) no need to ejbload in
    next Tx.

    7. If one bean indeed updates table, it will notify all other peer instances, to ejbload in next Tx. It
    may further change to only ejbload the few columns changed, etc.

    8. It can control how to use WLS cache, concurrency stragegy depends on how to implements equals,
    hashcode in pk class.

        EJB spec does not allow EJB inheritance. But we can still use java language inheritance. This patter uses this.
        All my entity beans subclass this Coarsegrainbean. For each bean, there are 5 classes, they are:
    CoarsegrainPK, (the primary key wrapper class)
    CoarsegrainData, (the value object, mapping to the table)
    Coarsegrain, (EJBObject, provides update, getData, selectDataFromDB, etc. methods)
    CoarsegrainHome, (the only class not extended by sub class)
    CoarsegrainBean (the super class of all entity beans).
    Correspondingly all the beans have 5 classes, all inherite from
    coarsegrainbean, except home interface (reason: java compile error,
    return different type)

        Basically the data class is the wrap of row in database, plus few
    fixed format member varialbes (refer to my CoarsegrianData class), which
    are used by refection in my ejbLoad() and ejbStore().
        The pk class has several flags used to indicate whether the data is
    modified, and which store strategy will be used for ejbStore(), etc.
    (refer to my coarsegrainPK class below).
        And, this parent coarsegrainBean class defines the ejbLoad(),
    ejbStore(), and findByPrimaryKey(), findByNothing(), ejbCreate(), etc,
    all the basic ejb methods, including update(), getData(), etc bizz
    method.
        So the subclass (the actual entity beans) onle needs to define its own bizz
    method normally.

        The coarsegrain will be in the first response.

        Welcome your feedback on this pattern.
         

        minjiang

    ----------------------------------pk class-----------

    package com.fairex.base.coarsegrain;

    import java.io.Serializable;
    import java.sql.*;


    public abstract class CoarseGrainPK implements Serializable
    {

    // some constructor, accessing etc methods here

       public boolean equals(Object pk)
       {
          if (pk==null || !(pk instanceof CoarseGrainPK))
             return false;

          CoarseGrainPK pk1=(CoarseGrainPK)pk;

          if (pk1.isFindAll==true && isFindAll==true && pk1.isReadOnly==true
    && isReadOnly==true)
            return true;

          return super.equals(pk);

       }

       public int hashCode()
       {
           if (isFindAll & isReadOnly) return 1234567;
           else return 7654321;

       // return System.identityHashCode(this);
        //super.hashCode();
       }

       public String toString()
       {
          try
          {
            return this.getClass()+":"+System.identityHashCode(this)
            +" isReadOnly="+isReadOnly
            +" isFindAll="+isFindAll
            +" isNeedRefresh="+isNeedRefresh
            +"
    isTraditionalUpdateRowByRowAllColumns="+isTraditionalUpdateRowByRowAllColumns

            +" isModified="+isModified+" modifyCounter="+modifyCounter
            +" isAllRowsModified="+isAllRowsModified
            ;

        }
        catch (Exception e)
        {
            return "sdfrdsafsfdsafsdf";
        }
        }

       // compare two string
       public boolean compare(String str1, String str2)
       {
          if (str1!=null && str2!=null && str1.hashCode()==str2.hashCode()
    && str1.equals(str2))
             return true;
          if (str1==null && str2==null)
             return true;
          return false;
       }


       public boolean compare(Timestamp str1, Timestamp str2) {
            if (str1!=null && str2!=null && str1.hashCode()==str2.hashCode()
    && str1.equals(str2)) return true;
            if (str1==null && str2==null) return true;
            return false;
        }


       /** This method is used for the ejbStore() method to reset the
    primary key.
       */
       protected void reset() {
            isNeedRefresh = true;
            isFindAll = false;
            isSorting = false;

            isModified = true;
            modifyCounter = 0;

            isTraditionalUpdateRowByRowAllColumns = true;
            isUpdateRowByRowSelectedColumns = false;
            isAllRowsModified = false;

            isReadOnly=false;
        }



       protected boolean isNeedRefresh = true;
       protected String[] refreshCols = null;
       protected boolean isFindAll = false;
       protected boolean isSorting = false;
       protected String sortingColumn = null;
       protected boolean isDesc = false;
       protected boolean isModified = true;
       protected int modifyCounter = 0;
       protected boolean isTraditionalUpdateRowByRowAllColumns = true;
       protected boolean isUpdateRowByRowSelectedColumns = false;
       protected boolean isAllRowsModified = false;
       protected String[] storeCols = null;
       protected boolean isReadOnly=false;
    }

    ----------------------------data class--------------

    package com.fairex.base.coarsegrain;

    import java.io.Serializable;

    import com.fairex.logger.*;

    public class CoarseGrainData implements Serializable, Cloneable
    {

       public CoarseGrainData()
       {
       }

       public Object clone() {
           try {
               return super.clone();
           } catch (Exception e) {
               RAFLog.warning(e, this);
           }

           return null;
       }

       /** This field is the table name undetlying in the database.
        */
       public static final String TABLENAME = null;

       /** This field is all the table column names.
        */
       public static final String[] COLUMNS = null;

       /** This field is the primary key columns of the table.
        */
       public static final String[] TABLE_PK = null;

    }



  2. To continue, here is the other classes:


    public interface CoarseGrain extends EJBObject
    {
       public void tableChanged(RefreshEvent e) throws RemoteException;

       /** Called when there is data needs to be updated.
        * If true is returned, then data is actually changed and updated.
        * If false is returned, then data is not changed.
        */
       public boolean update(CoarseGrainData data) throws RemoteException;
       
       /** This method should be overriden by ssub class, and it shall not be called by client program.
        * The reason is that the subclass has to overidern the myData field, for the specified detail type of subclass data type.
        */
       public CoarseGrainData[] getCoarseGrainData() throws RemoteException;
       
       /** This method is solely used by sub-class to find the rows from table directly. Usually it should be called only by findByNothing() instances.
        */
       public CoarseGrainData[] selectData(String queryString) throws FinderException, RemoteException;
    }



    public interface CoarseGrainHome extends EJBHome
    {

       /**
        * This method is used to insert new row for creating table.
        */
       public CoarseGrain create(CoarseGrainData data) throws CreateException, RemoteException;
       
       /* The data should not contain any duplicate key data, otherwise, the beans will throw exception.
        * This will not be checked earlier than insert.
        */
       public CoarseGrain create(CoarseGrainData[] data) throws CreateException, RemoteException;
        
       public CoarseGrain findByPrimaryKey(CoarseGrainPK pk) throws FinderException, RemoteException;
      
       public CoarseGrain findSortedData(CoarseGrainPK order, String sortingColumn, boolean isDesc) throws FinderException, RemoteException;
        
       public CoarseGrain findAllData() throws FinderException, RemoteException;

       public CoarseGrain findReadOnlyBean() throws FinderException, RemoteException;
    }

    Remeber, this CoarseGrainHome is the only class which cannot be sub class by entity beans. But the specified methods in CoarseGrainHome is already supplied in CoarseGrainBean class, and the sub class typically only needs to forward the call.

    Here is one typical sub class: (this one is not fully using the store performance)

    public class CollateralBean extends CoarseGrainBean {


       CollateralData[] myData;
        
        
        public CollateralData getCollateralData() {
            setEJBStore(false);
            return myData[0];
        }
     
        public CollateralPK ejbFindByNothing() throws FinderException {
            return (CollateralPK)super.ejbFindReadOnlyBean();
        }
        
        public CollateralPK ejbFindByPrimaryKey(CollateralPK pk) throws FinderException {
            return (CollateralPK)super.ejbFindByPrimaryKey(pk);
        }
        
        
        public CollateralPK ejbCreate(CollateralData data) throws CreateException {
           return (CollateralPK)super.ejbCreate(data);
        }
        
        public void ejbPostCreate(CollateralData data) {}
        
          
    }

    So, here we can see the sub class only needs to care about the bizz methods, with the full performance benifts, by calling setEJBStore() method.

    minjiang
  3. public abstract class CoarseGrainBean

       public CoarseGrainData[] getCoarseGrainData() {
          setEJBStore(false);
           return myData;
       }
       

     public CoarseGrainPK ejbCreate(CoarseGrainData data) throws CreateException
       {
          print("\r\ncoarseGrainBean...----------ejbCreate().\r\n");
        
          try
          {
             if (CoarseGrainHelper.insertPrepared(data, getConnection()))
    {
                myData=getNewDataArray(1);
                myData[0]=(CoarseGrainData)data.clone();
                 
                return mapDataToPK(data);
             }
             else
    throw new CreateException("The row cannot be created!"+" CoarseGrainData="+data);
          }
          catch (Exception e)
          {
             printException(e);
             processException(e);
          }
        
          return null;
       }
       
       public void ejbRemove() throws RemoveException
       {
                // delete from db
       }
       
       public CoarseGrainPK ejbFindReadOnlyBean() throws FinderException {
            print("coarseGrainBean...ejbFindByNothing()");
            CoarseGrainPK pk=getNewPK();
            pk.isReadOnly=true;
            return pk;
        }


       public CoarseGrainPK ejbFindByPrimaryKey(CoarseGrainPK pk) throws FinderException
       {
          print("coarseGrainBean...ejbFindByPrimaryKey() for "+pk);
          if (pk.isNull())
             throw new EJBException("Primary key is null! pk="+pk);
             
          // check DB, to decide if throwing FinderException
          
          return pk;
       }


        public boolean update(CoarseGrainData data) {
            print("coarseGrainBean...update().");
            
            if (myData.equals(data)) {
                setEJBStore(false);
                return false;
            }
            
            myData=(CoarseGrainData[])java.lang.reflect.Array.newInstance(data.getClass(), 1);
            
            myData[0]=data;
    setEJBStore(true);
            return true;
        }

        public void ejbLoad()
       {
          try
          {
            pk = (CoarseGrainPK) myEntityCtx.getPrimaryKey();
            if (pk.isNull() && !pk.isFindAll) return;
            if (pk.isReadOnly) return;
            if (pk.isNeedRefresh)
                refresh(pk);
            afterLoad();
          }
          catch (Exception e)
          {
             printException(e);
             processException(e);
          }
       } // end ejbLoad
     
     
       public void ejbStore()
       {
          try
          {
            pk = (CoarseGrainPK) myEntityCtx.getPrimaryKey();
            if (pk.isReadOnly) return;
            if (pk.isNull() && !pk.isFindAll) return;
            if (myData == null)
                 return;
             
            if ((pk.modifyCounter!=0) && (!pk.isModified) &&(!pk.isTraditionalUpdateRowByRowAllColumns))
            {
                pk.isModified = true;
                pk.modifyCounter = 0;
                return;
            }
            if (pk.modifyCounter == 0)
                 pk.isTraditionalUpdateRowByRowAllColumns = true;
             if (pk.isTraditionalUpdateRowByRowAllColumns)
    {
                for (int i=0, j=myData.length; i<j; i++)
    {
                   CoarseGrainHelper.updatePrepared(myData[i], getConnection());
                }
             }
             else if (pk.isUpdateRowByRowSelectedColumns)
    {
                for (int i=0, j=myData.length; i<j; i++)
    {
                   CoarseGrainHelper.updatePrepared(myData[i], getConnection(), pk.storeCols);
                }
             }
             else if (pk.isAllRowsModified)
    {
                CoarseGrainHelper.updatePrepared(myData, getConnection(), pk);
             }
    else
    {
                CoarseGrainHelper.updatePrepared(myData, getConnection(), pk, dataModified);
             }
          }
          catch (Exception e)
          {
             printException(e);
             processException(e);
          }
          finally {
            pk.reset();
          }
       } // end ejbStore

       protected void setTraditionalUpdate(boolean needUpdate) {
            if (needUpdate) {
                pk.isModified=true;
                pk.isTraditionalUpdateRowByRowAllColumns=true;
            }
            else {
                pk.isModified=false;
                pk.isTraditionalUpdateRowByRowAllColumns=false;
            }
        
        }
       protected void setReadOnlyStore(boolean t) {
           
           if (!t) return;
           if (!pk.isReadOnly) return;

           pk.isReadOnly=false;
           pk.isModified=true;
           
       }

       protected void setEJBStore(boolean isModified)
       {
          pk.modifyCounter++;
               
          if (isModified) {
                pk.isTraditionalUpdateRowByRowAllColumns=true;
                pk.isModified = true;
          }
     
       }

       protected void setEJBStore(boolean isAllRowsModified, String[] storeCols, boolean[] dataModified)
       {
                //store accordingly
       }
       
        protected abstract void afterLoad();
        protected abstract String prepareQuery(CoarseGrainPK pk);
        protected abstract void prepareQuery(PreparedStatement ps, CoarseGrainPK pk) throws EJBException;
       
       protected CoarseGrainData[] myData;
       protected CoarseGrainPK pk;
       
       protected boolean[] dataModified;
    }

    Here is the final part of the main CoarseGrainBean.
    There are several abstract methods which are required to implement in sub entity bean.

    welcome your comments.
    minjiang