Technical Blog

1 Post tagged with the sequence tag

Every order in Elastic Path Commerce must have a unique number. Out of the box, we use an auto-incrementing value stored in the TORDERNUMBERGENERATOR table. Here is the OpenJPA annotation for the orderNumber property in OrderImpl.java:

      @Basic
      @Column(name = "ORDER_NUMBER", length = GUID_LENGTH, nullable = false, unique = true)
      @GeneratedValue(strategy = GenerationType.TABLE, generator = NEXT_ORDER_NUMBER)
      @TableGenerator(name = NEXT_ORDER_NUMBER, table = "TORDERNUMBERGENERATOR", pkColumnName = "UIDPK",
                 valueColumnName = NEXT_ORDER_NUMBER, pkColumnValue = "1", allocationSize = 1)
      public String getOrderNumber() {
            return this.orderNumber;
      }

The first annotation declares orderNumber as a basic property. The second annotation declares ORDER_NUMBER as the column to save/load the property. The third annotation declares this as an auto-generated value, so no need to explicitly assign a value when the order is created in our code. The fourth annotation says that we are using the table generator. The next available order number is retrieved from the NEXT_ORDER_NUMBER column in TORDERNUMBERGENERATOR.

 

You may need to customize the number generation. For example, one of our customers has multiple data centers and each data center is running its own separate production deployment of Elastic Path Commerce. Order data from the different data centers is sent to a centralized financial application. The financial application needs to have unique order numbers, but each Elastic Path deployment uses the same order number generator algorithm, so there will be duplicate order numbers across the different data centers.

 

Having a centralized order number generator was not an option. The solution was to add a prefix to every order number on all data centers. The prefix would identify the data center where the order was created and ensure that order numbers would be unique across all data centers. To implement this, we would need to do the following:


  • Create a CUSTOMIZEDORDERNUMBERGENERATOR database table with columns (UIDPK number, PREPEND varchar 3, NEXT_ORDER_NUMBER varchar 100). Each data center would store a different prefix in the PREPEND column.
  • Create a CustomizedOrderNumberGenerator, which would read the PREPEND column and prepend it to  the NEXT_ORDER_NUMBER.
  • Override the annotations for OrderImpl.getOrderNumber to use the new custom generator.


OpenJPA represents all generators internally with the org.apache.openjpa.kernel.Seq interface. This interface supplies all the context we need to create  our own custom generators, including the current persistence  environment, the JDBC DataSource,  and other essentials. The org.apache.openjpa.jdbc.kernel.AbstractJDBCSeq helps us create custom JDBC-based sequences. There are many  implementations in OpenJPA to generate different values as well, like AbstractJDBCSeq, ClassTableJDBCSeq, DelegatingSeq, NativeJDBCSeq, TableJDBCSeq, TimeSeededSeq, UUIDHexSeq, UUIDStringSeq, UUIDType4HexSeq, UUIDType4StringSeq, ValueTableJDBCSeq. Here is our CustomizedOrderNumberGenerator:

public class CustomizedOrderNumberGenerator extends ValueTableJDBCSeq {
 
    private static final String TABLE_NAME = "CUSTOMIZEDORDERNUMBERGENERATOR";
    private static final String PRIMARY_KEY_COLUMN = "UIDPK";
    private static final String PRIMARY_KEY_VALUE = "1";
    private static final String SEQUENCE_COLUMN = "NEXT_ORDER_NUMBER";
    private static final String PREFIX_COLUMN = "PREPEND";
 
    protected Object currentInternal(final JDBCStore store, final ClassMapping mapping) throws Exception {        
        return getPrefix(store, mapping) + super.currentInternal(store, mapping);    
    }
    
    protected Object nextInternal(final JDBCStore store, final ClassMapping mapping) throws Exception {
        return getPrefix(store, mapping) + super.nextInternal(store, mapping);    
    }
    
    protected String getPrefix(final JDBCStore store, final ClassMapping mapping) throws EpSystemException, SQLException {    
        if (prefix == null) {
            // Load from db
            Object primaryKey = getPrimaryKey(mapping);
    
            if (primaryKey == null) {    
                LOG.error(ERROR_CANNOT_GET_ORDER_PREFIX);
                throw new EpSystemException(ERROR_CANNOT_GET_ORDER_PREFIX);            
            }
            
            Connection conn = getConnection(store);            
            try {            
                prefix = getPrefix(conn);            
            } finally {            
                closeConnection(conn);            
            }         
        }            
        return prefix;            
    }
        
    
    private String getPrefix(final Connection conn) throws EpSystemException, SQLException {
        // Prepare the statement
        DBDictionary dict = getConfiguration().getDBDictionaryInstance();
        SQLBuffer sel = new SQLBuffer(dict).append(PREFIX_COLUMN);
        SQLBuffer where = new SQLBuffer(dict).append(PRIMARY_KEY_COLUMN).append(" = ").append(PRIMARY_KEY_VALUE);
        SQLBuffer tables = new SQLBuffer(dict).append(TABLE_NAME);
        
        SQLBuffer select = dict.toSelect(sel, null, tables, where, null, null,
                null, false, false, 0, Long.MAX_VALUE, false, true);
 
        PreparedStatement stmnt = select.prepareStatement(conn);
        
        ResultSet resultSet = null;
        
        try {
            //Do query
            resultSet = stmnt.executeQuery();
 
            if (!resultSet.next()) {
                LOG.error(ERROR_CANNOT_GET_ORDER_PREFIX);
                throw new EpSystemException(ERROR_CANNOT_GET_ORDER_PREFIX);
            }
            return dict.getString(resultSet, 1);
    
        } finally {        
            ...        
        }   
    }
}

 

The last step is to overwrite  the generator in order-orm.xml:

    <entity class="OrderImpl">
        <sequence-generator name="NEXT_ORDER_NUMBER" sequence-name="com.customize.CustomizedOrderNumberGenerator()" allocation-size="1" />
       ...
   </entity>

 

For more information on table generators, see the following links:

http://openjpa.apache.org/builds/1.2.0/apache-openjpa-1.2.0/docs/manual/jpa_overview_mapping_sequence.html#jpa_overview_mapping_sequence_tablegen

http://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/jpa_overview_mapping_sequence.html

http://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/ref_guide_sequence.html

1 Comments Permalink