Following are the code details. Some (minor?) issues are still pending though:
- In the code, I have set the
BLOCK_SIZE
hard to 1. This is because my project is still under heavy development, and consequentely, I still have to redeploy a lot, hence I need a unique persistent sequence every time it is used (the SLSB is reset after redeployment, so with caching enabled (BLOCK_SIZE
> 1) I may “loose” sequence numbers).
I also still have to figure out how to import a parameter from the deployment descriptor, but this is a really minor technical detail. - In the original code, an exception handling mechanism for a
TransactionRolledbackException
is present. However, my development environment complains that this exception is never thrown. This is why I have commented out this section (including the associated retry-count variable, if this exception occurs).
Of course, any comments on the above issues are welcome, but so far everything works fine.
First the code for the Sequence entity bean:
import javax.ejb.CreateException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @ejb.bean name="Sequence" * jndi-name="Sequence" * type="CMP" * primkey-field="sequenceName" * schema="SequenceSchema" * cmp-version="2.x" * data-source="defaultDS" * * @ejb.persistence * table-name="Sequence" * * @ejb.finder * query="SELECT OBJECT(a) FROM SequenceSchema as a" * signature="java.util.Collection findAll()" */ public abstract class SequenceBean implements javax.ejb.EntityBean { private static final Log log = LogFactory.getLog(SequenceBean.class); // EJB Default Methods ----------------------------------------------------- /** * * @param sequenceName * @return * @throws CreateException * @ejb.create-method */ public String ejbCreate(String sequenceName) throws javax.ejb.CreateException { log.debug("Creating sequence bean, sequence name=" + sequenceName); setSequenceName(sequenceName); setCurrentKeyValue(new Integer(0)); //Set initial value return sequenceName; } /** * * @param sequenceName * @throws CreateException */ public void ejbPostCreate(String sequenceName) throws javax.ejb.CreateException { } /** * CMP Field sequenceName * * @return the sequenceName * * @ejb.persistent-field * @ejb.persistence * column-name="sequence_name" * jdbc-type="VARCHAR" * sql-type="VARCHAR" * read-only="false" * @ejb.pk-field * * @ejb.interface-method */ public abstract java.lang.String getSequenceName(); /** * Sets the sequenceName * * @param java.lang.String the new sequenceName value * * @ejb.interface-method */ public abstract void setSequenceName(java.lang.String sequenceName); /** * CMP Field currentKeyValue * * @return the currentKeyValue * * @ejb.persistent-field * @ejb.persistence * column-name="current_key_value" * jdbc-type="INTEGER" * sql-type="INTEGER" * read-only="false" * * @ejb.interface-method */ public abstract java.lang.Integer getCurrentKeyValue(); /** * Sets the currentKeyValue * * @param java.lang.Integer the new currentKeyValue value * * @ejb.interface-method */ public abstract void setCurrentKeyValue(java.lang.Integer currentKeyValue); /** * @ejb.interface-method * view-type="local" **/ public int getValueAfterIncrementingBy(int blockSize) { setCurrentKeyValue(new Integer(getCurrentKeyValue().intValue() + blockSize)); return getCurrentKeyValue().intValue(); } }
Finally, I conclude this post with the listing of the SequenceGenerator SLSB. The only method that clients will normally use is the getNextSequenceNumber(String name)
method. Note that the SequenceGenerator allows multiple sequences to be maintained (for primary keys in more than one table).
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bibtexml.entity.SequenceLocal; import org.bibtexml.entity.SequenceLocalHome; import org.bibtexml.entity.SequenceUtil; /** * @ejb.bean * name="SequenceGenerator" * jndi-name="SequenceGeneratorBean" * type="Stateless" * * @ejb.ejb-ref * ejb-name="Sequence" * view-type="local" * ref-name="ejb/SequenceLocal" * * @jboss.ejb-ref-jndi * ref-name="SequenceLocal" * jndi-name="ejb/SequenceLocal" * public class SequenceGeneratorBean implements javax.ejb.SessionBean { private class Entry { SequenceLocal sequence; int last; } private Log log = LogFactory.getLog(SequenceGeneratorBean.class); private java.util.Hashtable _entries = new java.util.Hashtable(); //private int _retryCount = 5; // Make value of sequence persistent in database after blockSize increments private int blockSize = 1; private SequenceLocalHome sequenceLocalHome; public void setSessionContext( javax.ejb.SessionContext sessionContext) { // TODO: sort out how we can configure the generator via the deployment descriptor } /** * @ejb.create-method * */ public void ejbCreate() { try { sequenceLocalHome = SequenceUtil.getLocalHome(); } catch (Exception e) { log.error(e); } } /** * Generates the next unique sequence number * * @return next unique sequence number * * @ejb.interface-method * view-type="remote" */ public int getNextSequenceNumber(String name) { try { Entry entry = (Entry) _entries.get(name); if (entry == null) { // add an entry to the sequence table entry = new Entry(); try { entry.sequence = sequenceLocalHome.findByPrimaryKey(name); entry.last = entry.sequence.getValueAfterIncrementingBy(blockSize); log.debug("Entry last = " + entry.last); } catch(javax.ejb.FinderException e) { log.info("Sequence " + name + " not found...creating it now!"); // if we couldn't find it, then create it... entry.sequence = sequenceLocalHome.create(name); } _entries.put(name, entry); } if(entry.last % blockSize == 0 && entry.last != 0) { /* for(int retry = 0; true; retry++) { try { */ entry.last = entry.sequence.getValueAfterIncrementingBy(blockSize); /* break; } catch(javax.transaction.TransactionRolledbackException e) { if(retry < _retryCount) { // we hit a concurrency exception, so try again... continue; } else { // we tried too many times, so fail... throw new javax.ejb.EJBException(e); } } } */ } return entry.last++; } catch(javax.ejb.CreateException e) { throw new javax.ejb.EJBException(e); } } }
Good god, some people really love pain!
I agree with Zeger’s quote from Johnson too – there are simpler ways. Just use JDBC to get from the sequence, perhaps?
According to Rod Johnson’s book “Expert one-to-one J2EE design and development”, the sequence entity bean illustrates the dangers of rating a portable solution above a good solution! He even calls this solution “unworkable”!
Instead, he suggests alternative unique ID generation mechanisms, for example using the facilities of the Java language (by using an algorithm based on e.g. random numbers, system time, network traffic, or etc.). This is a fast and portable solution!
More info on this topic can be found on page 271 of his book.