Version 4

    CMP field state-factory

     

    Since 3.2.3.

     

    First of all, let's define the state of the field. A state of a CMP field is some value which is calculated using the field's value (with some mathematical function or any weird rule you could invent and code in Java).

     

    A CMP field state factory is a class that generates field states using field values. Its other function is to compare the field states calculated at different point in time and determine whether state of the field changed.

     

    As you might guess, this mechanism is used for dirty checking. Each CMP field has a CMP field state factory assigned to it at deployment time. At runtime, when a CMP field is loaded, its CMP field state factory generates the state of the field using just loaded field value. This state is stored in memory. When its time to update the database (e.g. transaction commits), the CMP field state factory generates a new state using the current field value. Then the two states (at the beginning of the transaction and at the end) are compared. If they are not equal the field is supposed to be dirty and is updated in the database.

     

    Dirty checking is potentially an expensive process. The cost of dirty checking depends on the specific Java type or class implementation which is used as a type for CMP field. Some time ago as a dirty checking mechanism JBossCMP used a comparison of field values at the beginning and at the end of the transaction with equals(Object o) method. Now we use field states instead which allows us to optimize dirty checking per CMP field type. Plus, the meanings of 'not equal' and 'dirty' might not be the same in some applications.

     

    Here is the interface CMP field state factories implement

     

    /*
     * JBoss, the OpenSource J2EE webOS
     *
     * Distributable under LGPL license.
     * See terms of license at gnu.org.
     */
    package org.jboss.ejb.plugins.cmp.jdbc;
    
    /**
     * Implementations of this interface are used to create and compare field states
     * for equality.
     *
     * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
     */
    public interface CMPFieldStateFactory
    {
       /**
        * Calculates and returns an object that represents the state of the field.
        * The states produced by this method will be used to check whether the field
        * is dirty at synchronization time.
        *
        * @param fieldValue  field's value.
        * @return an object representing the field's state.
        */
       Object getFieldState(Object fieldValue);
    
       /**
        * Checks whether the field's state <code>state</code>
        * is equal to the field value's state (possibly, calculated with
        * the <code>getFieldState()</code> method).
        *
        * @param state  the state to compare with field value's state.
        * @param fieldValue  field's value, the state of which will be compared
        *                    with <code>state</code>.
        * @return  true if <code>state</code> equals to <code>fieldValue</code>'s state.
        */
       boolean isStateValid(Object state, Object fieldValue);
    }
    

     

    A simple implemntation that relies on equals() would be

     

       /**
        * This implementation uses field's value as its state.
        */
       public static final CMPFieldStateFactory EQUALS = new CMPFieldStateFactory()
       {
          public Object getFieldState(Object fieldValue)
          {
             return fieldValue;
          }
    
          public boolean isStateValid(Object state, Object fieldValue)
          {
             return state == null ? fieldValue == null : state.equals(fieldValue);
          }
       };
    

     

    Another default implementation is

     

       /**
        * This implementation will always suppose that the state is invalid unless
        * both states are null.
        */
       private static final CMPFieldStateFactory INVALID_UNLESS_NULL = new CMPFieldStateFactory()
       {
          public Object getFieldState(Object fieldValue)
          {
             return fieldValue;
          }
    
          public boolean isStateValid(Object state, Object fieldValue)
          {
             return state == null ? fieldValue == null : false;
          }
       };
    

     

    There are other default state factory implementations:

    • MAP for java.util.Map implementations;

    • LIST for java.util.List implementations;

    • SET for java.util.Set implementations;

    • ARRAY for array types.

     

    It is obvious for which types MAP, LIST, SET and ARRAY are used. What about INVALID_UNLESS_NULL and EQUALS? EQUALS is used for primitive types, their wrappers, java.lang.String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.sql.Date, java.sql.Time and java.sql.Timestamp. Fields of other types are assigned INVALID_UNLESS_NULL state factory by default (since we chose to not rely on equals()).

     

    If you are not satisfied with defaults, you can always implement your own CMP state factory and assign it in jbosscmp-jdbc.xml for a cmp-field:

     

    <cmp-field>
       <field-name>name</field-name>
       <check-dirty-after-get>true</check-dirty-after-get>
       <state-factory>research.NameStateFactory</state-factory>
    </cmp-field>
    

     

    Or per user-type-mapping:

     

    <user-type-mapping>
       <java-type>research.MyBean</java-type>
       <mapped-type>java.lang.String</mapped-type>
       <mapper>research.MyBeanMapper</mapper>
       <check-dirty-after-get>true</check-dirty-after-get>
       <state-factory>research.MyBeanStateFactory</state-factory>
    </user-type-mapping>
    

     

    In this case all CMP fields of type research.MyBean will by default will use research.MyBeanStateFactory which, again, can be overriden on the cmp-field level.