View Javadoc

1   package org.apache.torque.oid;
2   
3   /* ====================================================================
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution,
22   *    if any, must include the following acknowledgment:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.apache.org/)."
25   *    Alternately, this acknowledgment may appear in the software itself,
26   *    if and wherever such third-party acknowledgments normally appear.
27   *
28   * 4. The names "Apache" and "Apache Software Foundation" and
29   *    "Apache Turbine" must not be used to endorse or promote products
30   *    derived from this software without prior written permission. For
31   *    written permission, please contact apache@apache.org.
32   *
33   * 5. Products derived from this software may not be called "Apache",
34   *    "Apache Turbine", nor may "Apache" appear in their name, without
35   *    prior written permission of the Apache Software Foundation.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.apache.org/>.
55   */
56  
57  import java.math.BigDecimal;
58  import java.sql.Connection;
59  import java.sql.ResultSet;
60  import java.sql.Statement;
61  import java.util.ArrayList;
62  import java.util.Hashtable;
63  import java.util.Iterator;
64  import java.util.List;
65  
66  import org.apache.commons.configuration.Configuration;
67  
68  import org.apache.commons.logging.Log;
69  import org.apache.commons.logging.LogFactory;
70  
71  import org.apache.torque.Torque;
72  import org.apache.torque.TorqueException;
73  import org.apache.torque.map.DatabaseMap;
74  import org.apache.torque.map.TableMap;
75  import org.apache.torque.util.Transaction;
76  
77  //!!
78  // NOTE:
79  // It would be nice to decouple this from
80  // Torque. This is a great stand-alone utility.
81  
82  /***
83   * This method of ID generation is used to ensure that code is
84   * more database independent.  For example, MySQL has an auto-increment
85   * feature while Oracle uses sequences.  It caches several ids to
86   * avoid needing a Connection for every request.
87   *
88   * This class uses the table ID_TABLE defined in
89   * conf/master/id-table-schema.xml.  The columns in ID_TABLE are used as
90   * follows:<br>
91   *
92   * ID_TABLE_ID - The PK for this row (any unique int).<br>
93   * TABLE_NAME - The name of the table you want ids for.<br>
94   * NEXT_ID - The next id returned by IDBroker when it queries the
95   *           database (not when it returns an id from memory).<br>
96   * QUANTITY - The number of ids that IDBroker will cache in memory.<br>
97   * <p>
98   * Use this class like this:
99   * <pre>
100  * int id = dbMap.getIDBroker().getNextIdAsInt(null, "TABLE_NAME");
101  *  - or -
102  * BigDecimal[] ids = ((IDBroker)dbMap.getIDBroker())
103  *     .getNextIds("TABLE_NAME", numOfIdsToReturn);
104  * </pre>
105  *
106  * NOTE: When the ID_TABLE must be updated we must ensure that
107  * IDBroker objects running in different JVMs do not overwrite each
108  * other.  This is accomplished using using the transactional support
109  * occuring in some databases.  Using this class with a database that
110  * does not support transactions should be limited to a single JVM.
111  *
112  * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
113  * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
114  * @version $Id: IDBroker.java,v 1.27 2003/08/25 16:33:22 henning Exp $
115  */
116 public class IDBroker implements Runnable, IdGenerator
117 {
118     /***
119      * Name of the ID_TABLE = ID_TABLE
120      */
121     public static final String ID_TABLE = "ID_TABLE";
122 
123     /***
124      * Fully qualified Table_Name column name
125      */
126     public static final String TABLE_NAME = ID_TABLE + ".TABLE_NAME";
127 
128     /***
129      * Fully qualified Next_ID column name
130      */
131     public static final String NEXT_ID = ID_TABLE + ".NEXT_ID";
132 
133     /***
134      * Fully qualified Quantity column name
135      */
136     public static final String QUANTITY = ID_TABLE + ".QUANTITY";
137 
138     /*** The TableMap referencing the ID_TABLE for this IDBroker. */
139     private TableMap tableMap;
140 
141     /***
142      * The default size of the per-table meta data <code>Hashtable</code>
143      * objects.
144      */
145     private static final int DEFAULT_SIZE = 40;
146 
147     /***
148      * The cached IDs for each table.
149      *
150      * Key: String table name.
151      * Value: List of Integer IDs.
152      */
153     private Hashtable ids = new Hashtable(DEFAULT_SIZE);
154 
155     /***
156      * The quantity of ids to grab for each table.
157      *
158      * Key: String table name.
159      * Value: Integer quantity.
160      */
161     private Hashtable quantityStore = new Hashtable(DEFAULT_SIZE);
162 
163     /***
164      * The last time this IDBroker queried the database for ids.
165      *
166      * Key: String table name.
167      * Value: Date of last id request.
168      */
169     private Hashtable lastQueryTime = new Hashtable(DEFAULT_SIZE);
170 
171     /***
172      * Amount of time for the thread to sleep
173      */
174     private static final int SLEEP_PERIOD = 1 * 60000;
175 
176     /***
177      * The safety Margin
178      */
179     private static final float SAFETY_MARGIN = 1.2f;
180 
181     /***
182      * The houseKeeperThread thread
183      */
184     private Thread houseKeeperThread = null;
185 
186     /***
187      * Are transactions supported?
188      */
189     private boolean transactionsSupported = false;
190 
191     /***
192      * The value of ONE!
193      */
194     private static final BigDecimal ONE = new BigDecimal("1");
195 
196     /*** the configuration */
197     private Configuration configuration;
198 
199     /*** property name */
200     private static final String DB_IDBROKER_CLEVERQUANTITY =
201         "idbroker.clever.quantity";
202 
203     /*** property name */
204     private static final String DB_IDBROKER_PREFETCH =
205         "idbroker.prefetch";
206 
207     /*** property name */
208     private static final String DB_IDBROKER_USENEWCONNECTION =
209         "idbroker.usenewconnection";
210 
211     /*** the log */
212     private Log log = LogFactory.getLog(IDBroker.class);
213 
214     /***
215      * Creates an IDBroker for the ID table.
216      *
217      * @param tMap A TableMap.
218      */
219     public IDBroker(TableMap tMap)
220     {
221         this.tableMap = tMap;
222         configuration = Torque.getConfiguration();
223 
224         // Start the housekeeper thread only if prefetch has not been disabled
225         if (configuration.getBoolean(DB_IDBROKER_PREFETCH, true))
226         {
227             houseKeeperThread = new Thread(this);
228             // Indicate that this is a system thread. JVM will quit only when
229             // there are no more active user threads. Settings threads spawned
230             // internally by Torque as daemons allows commandline applications
231             // using Torque terminate in an orderly manner.
232             houseKeeperThread.setDaemon(true);
233             houseKeeperThread.start();
234         }
235 
236         // Check for Transaction support.  Give warning message if
237         // IDBroker is being used with a database that does not
238         // support transactions.
239         String dbName = tMap.getDatabaseMap().getName();
240         Connection dbCon = null;
241         try
242         {
243             dbCon = Torque.getConnection(dbName);
244             transactionsSupported = dbCon.getMetaData().supportsTransactions();
245         }
246         catch (Exception e)
247         {
248             transactionsSupported = false;
249         }
250         finally
251         {
252             try
253             {
254                 // Return the connection to the pool.
255                 dbCon.close();
256             }
257             catch (Exception e)
258             {
259             }
260         }
261         if (!transactionsSupported)
262         {
263             log.warn("IDBroker is being used with db '" + dbName
264                     + "', which does not support transactions. IDBroker "
265                     + "attempts to use transactions to limit the possibility "
266                     + "of duplicate key generation.  Without transactions, "
267                     + "duplicate key generation is possible if multiple JVMs "
268                     + "are used or other means are used to write to the "
269                     + "database.");
270         }
271     }
272 
273     /***
274      * Set the configuration
275      *
276      * @param configuration the configuration
277      */
278     public void setConfiguration(Configuration configuration)
279     {
280         this.configuration = configuration;
281     }
282 
283     /***
284      * Returns an id as a primitive int.  Note this method does not
285      * require a Connection, it just implements the KeyGenerator
286      * interface.  if a Connection is needed one will be requested.
287      * To force the use of the passed in connection set the configuration
288      * property torque.idbroker.usenewconnection = false
289      *
290      * @param connection A Connection.
291      * @param tableName an Object that contains additional info.
292      * @return An int with the value for the id.
293      * @exception Exception Database error.
294      */
295     public int getIdAsInt(Connection connection, Object tableName)
296         throws Exception
297     {
298         return getIdAsBigDecimal(connection, tableName).intValue();
299     }
300 
301 
302     /***
303      * Returns an id as a primitive long. Note this method does not
304      * require a Connection, it just implements the KeyGenerator
305      * interface.  if a Connection is needed one will be requested.
306      * To force the use of the passed in connection set the configuration
307      * property torque.idbroker.usenewconnection = false
308      *
309      * @param connection A Connection.
310      * @param tableName a String that identifies a table.
311      * @return A long with the value for the id.
312      * @exception Exception Database error.
313      */
314     public long getIdAsLong(Connection connection, Object tableName)
315         throws Exception
316     {
317         return getIdAsBigDecimal(connection, tableName).longValue();
318     }
319 
320     /***
321      * Returns an id as a BigDecimal. Note this method does not
322      * require a Connection, it just implements the KeyGenerator
323      * interface.  if a Connection is needed one will be requested.
324      * To force the use of the passed in connection set the configuration
325      * property torque.idbroker.usenewconnection = false
326      *
327      * @param connection A Connection.
328      * @param tableName a String that identifies a table..
329      * @return A BigDecimal id.
330      * @exception Exception Database error.
331      */
332     public BigDecimal getIdAsBigDecimal(Connection connection,
333                                         Object tableName)
334         throws Exception
335     {
336         BigDecimal[] id = getNextIds((String) tableName, 1, connection);
337         return id[0];
338     }
339 
340     /***
341      * Returns an id as a String. Note this method does not
342      * require a Connection, it just implements the KeyGenerator
343      * interface.  if a Connection is needed one will be requested.
344      * To force the use of the passed in connection set the configuration
345      * property torque.idbroker.usenewconnection = false
346      *
347      * @param connection A Connection should be null.
348      * @param tableName a String that identifies a table.
349      * @return A String id
350      * @exception Exception Database error.
351      */
352     public String getIdAsString(Connection connection, Object tableName)
353         throws Exception
354     {
355         return getIdAsBigDecimal(connection, tableName).toString();
356     }
357 
358 
359     /***
360      * A flag to determine the timing of the id generation     *
361      * @return a <code>boolean</code> value
362      */
363     public boolean isPriorToInsert()
364     {
365         return true;
366     }
367 
368     /***
369      * A flag to determine the timing of the id generation
370      *
371      * @return a <code>boolean</code> value
372      */
373     public boolean isPostInsert()
374     {
375         return false;
376     }
377 
378     /***
379      * A flag to determine whether a Connection is required to
380      * generate an id.
381      *
382      * @return a <code>boolean</code> value
383      */
384     public boolean isConnectionRequired()
385     {
386         return false;
387     }
388 
389     /***
390      * This method returns x number of ids for the given table.
391      *
392      * @param tableName The name of the table for which we want an id.
393      * @param numOfIdsToReturn The desired number of ids.
394      * @return A BigDecimal.
395      * @exception Exception Database error.
396      */
397     public synchronized BigDecimal[] getNextIds(String tableName,
398                                                 int numOfIdsToReturn)
399         throws Exception
400     {
401         return getNextIds(tableName, numOfIdsToReturn, null);
402     }
403 
404     /***
405      * This method returns x number of ids for the given table.
406      * Note this method does not require a Connection.
407      * If a Connection is needed one will be requested.
408      * To force the use of the passed in connection set the configuration
409      * property torque.idbroker.usenewconnection = false
410      *
411      * @param tableName The name of the table for which we want an id.
412      * @param numOfIdsToReturn The desired number of ids.
413      * @param connection A Connection.
414      * @return A BigDecimal.
415      * @exception Exception Database error.
416      */
417     public synchronized BigDecimal[] getNextIds(String tableName,
418                                                 int numOfIdsToReturn,
419                                                 Connection connection)
420         throws Exception
421     {
422         if (tableName == null)
423         {
424             throw new Exception("getNextIds(): tableName == null");
425         }
426 
427         // A note about the synchronization:  I (jmcnally) looked at
428         // the synchronized blocks to avoid thread issues that were
429         // being used in this and the storeId method.  I do not think
430         // they were being effective, so I synchronized the method.
431         // I have left the blocks that did exist commented in the code
432         // to make it easier for others to take a look, because it
433         // would be preferrable to avoid the synchronization on the
434         // method
435 
436         List availableIds = (List) ids.get(tableName);
437 
438         if (availableIds == null || availableIds.size() < numOfIdsToReturn)
439         {
440             if (availableIds == null)
441             {
442                 log.debug("Forced id retrieval - no available list");
443             }
444             else
445             {
446                 log.debug("Forced id retrieval - " + availableIds.size());
447             }
448             storeIDs(tableName, true, connection);
449             availableIds = (List) ids.get(tableName);
450         }
451 
452         int size = availableIds.size() < numOfIdsToReturn
453                 ? availableIds.size() : numOfIdsToReturn;
454 
455         BigDecimal[] results = new BigDecimal[size];
456 
457         // We assume that availableIds will always come from the ids
458         // Hashtable and would therefore always be the same object for
459         // a specific table.
460         //        synchronized (availableIds)
461         //        {
462         for (int i = size - 1; i >= 0; i--)
463         {
464             results[i] = (BigDecimal) availableIds.get(i);
465             availableIds.remove(i);
466         }
467         //        }
468 
469         return results;
470     }
471 
472     /***
473      * Describe <code>exists</code> method here.
474      *
475      * @param tableName a <code>String</code> value that is used to identify
476      * the row
477      * @return a <code>boolean</code> value
478      * @exception TorqueException if an error occurs
479      * @exception Exception a generic exception.
480      */
481     public boolean exists(String tableName)
482         throws TorqueException, Exception
483     {
484         String query = new StringBuffer(100)
485             .append("select ")
486             .append(TABLE_NAME)
487             .append(" where ")
488             .append(TABLE_NAME).append("='").append(tableName).append('\'')
489             .toString();
490 
491         boolean exists = false;
492         Connection dbCon = null;
493         try
494         {
495             String databaseName = tableMap.getDatabaseMap().getName();
496 
497             dbCon = Torque.getConnection(databaseName);
498             Statement statement = dbCon.createStatement();
499             ResultSet rs = statement.executeQuery(query);
500             exists = rs.next();
501             statement.close();
502         }
503         finally
504         {
505             // Return the connection to the pool.
506             try
507             {
508                 dbCon.close();
509             }
510             catch (Exception e)
511             {
512                 log.error("Release of connection failed.", e);
513             }
514         }
515         return exists;
516     }
517 
518     /***
519      * A background thread that tries to ensure that when someone asks
520      * for ids, that there are already some loaded and that the
521      * database is not accessed.
522      */
523     public void run()
524     {
525         log.debug("IDBroker thread was started.");
526 
527         Thread thisThread = Thread.currentThread();
528         while (houseKeeperThread == thisThread)
529         {
530             try
531             {
532                 houseKeeperThread.sleep(SLEEP_PERIOD);
533             }
534             catch (InterruptedException exc)
535             {
536                 // ignored
537             }
538 
539             // logger.info("IDBroker thread checking for more keys.");
540             Iterator it = ids.keySet().iterator();
541             while (it.hasNext())
542             {
543                 String tableName = (String) it.next();
544                 if (log.isDebugEnabled())
545                 {
546                     log.debug("IDBroker thread checking for more keys "
547                             + "on table: " + tableName);
548                 }
549                 List availableIds = (List) ids.get(tableName);
550                 int quantity = getQuantity(tableName, null).intValue();
551                 if (quantity > availableIds.size())
552                 {
553                     try
554                     {
555                         // Second parameter is false because we don't
556                         // want the quantity to be adjusted for thread
557                         // calls.
558                         storeIDs(tableName, false, null);
559                         if (log.isDebugEnabled())
560                         {
561                             log.debug("Retrieved more ids for table: " + tableName);
562                         }
563                     }
564                     catch (Exception exc)
565                     {
566                         log.error("There was a problem getting new IDs "
567                                      + "for table: " + tableName, exc);
568                     }
569                 }
570             }
571         }
572         log.debug("IDBroker thread finished.");
573     }
574 
575     /***
576      * Shuts down the IDBroker thread.
577      *
578      * Calling this method stops the thread that was started for this
579      * instance of the IDBroker. This method should be called during
580      * MapBroker Service shutdown.
581      */
582     public void stop()
583     {
584         houseKeeperThread = null;
585     }
586 
587     /***
588      * Check the frequency of retrieving new ids from the database.
589      * If the frequency is high then we increase the amount (i.e.
590      * quantity column) of ids retrieved on each access.  Tries to
591      * alter number of keys grabbed so that IDBroker retrieves a new
592      * set of ID's prior to their being needed.
593      *
594      * @param tableName The name of the table for which we want an id.
595      */
596     private void checkTiming(String tableName)
597     {
598         // Check if quantity changing is switched on.
599         // If prefetch is turned off, changing quantity does not make sense
600         if (!configuration.getBoolean(DB_IDBROKER_CLEVERQUANTITY, true)
601             || !configuration.getBoolean(DB_IDBROKER_PREFETCH, true))
602         {
603             return;
604         }
605 
606         // Get the last id request for this table.
607         java.util.Date lastTime = (java.util.Date) lastQueryTime.get(tableName);
608         java.util.Date now = new java.util.Date();
609 
610         if (lastTime != null)
611         {
612             long thenLong = lastTime.getTime();
613             long nowLong = now.getTime();
614             int timeLapse = (int) (nowLong - thenLong);
615             if (timeLapse < SLEEP_PERIOD && timeLapse > 0)
616             {
617                 if (log.isDebugEnabled())
618                 {
619                     log.debug("Unscheduled retrieval of more ids for table: "
620                             + tableName);
621                 }
622                 // Increase quantity, so that hopefully this does not
623                 // happen again.
624                 float rate = getQuantity(tableName, null).floatValue()
625                     / (float) timeLapse;
626                 quantityStore.put(tableName, new BigDecimal(
627                     Math.ceil(SLEEP_PERIOD * rate * SAFETY_MARGIN)));
628             }
629         }
630         lastQueryTime.put(tableName, now);
631     }
632 
633     /***
634      * Grabs more ids from the id_table and stores it in the ids
635      * Hashtable.  If adjustQuantity is set to true the amount of id's
636      * retrieved for each call to storeIDs will be adjusted.
637      *
638      * @param tableName The name of the table for which we want an id.
639      * @param adjustQuantity True if amount should be adjusted.
640      * @param connection a Connection
641      * @exception Exception a generic exception.
642      */
643     private void storeIDs(String tableName,
644                           boolean adjustQuantity,
645                           Connection connection)
646         throws Exception
647     {
648         BigDecimal nextId = null;
649         BigDecimal quantity = null;
650         DatabaseMap dbMap = tableMap.getDatabaseMap();
651 
652         // Block on the table.  Multiple tables are allowed to ask for
653         // ids simultaneously.
654         //        TableMap tMap = dbMap.getTable(tableName);
655         //        synchronized(tMap)  see comment in the getNextIds method
656         //        {
657         if (adjustQuantity)
658         {
659             checkTiming(tableName);
660         }
661 
662         boolean useNewConnection = (connection == null) || (configuration
663                 .getBoolean(DB_IDBROKER_USENEWCONNECTION, true));
664         try
665         {
666             if (useNewConnection)
667             {
668                 connection = Transaction.beginOptional(dbMap.getName(),
669                     transactionsSupported);
670             }
671 
672             // Write the current value of quantity of keys to grab
673             // to the database, primarily to obtain a write lock
674             // on the table/row, but this value will also be used
675             // as the starting value when an IDBroker is
676             // instantiated.
677             quantity = getQuantity(tableName, connection);
678             updateQuantity(connection, tableName, quantity);
679 
680             // Read the next starting ID from the ID_TABLE.
681             BigDecimal[] results = selectRow(connection, tableName);
682             nextId = results[0]; // NEXT_ID column
683 
684             // Update the row based on the quantity in the
685             // ID_TABLE.
686             BigDecimal newNextId = nextId.add(quantity);
687             updateNextId(connection, tableName, newNextId.toString());
688 
689             if (useNewConnection)
690             {
691                 Transaction.commit(connection);
692             }
693         }
694         catch (Exception e)
695         {
696             if (useNewConnection)
697             {
698                 Transaction.rollback(connection);
699             }
700             throw e;
701         }
702 
703         List availableIds = (List) ids.get(tableName);
704         if (availableIds == null)
705         {
706             availableIds = new ArrayList();
707             ids.put(tableName, availableIds);
708         }
709 
710         // Create the ids and store them in the list of available ids.
711         int numId = quantity.intValue();
712         for (int i = 0; i < numId; i++)
713         {
714             availableIds.add(nextId);
715             nextId = nextId.add(ONE);
716         }
717         //        }
718     }
719 
720     /***
721      * This method allows you to get the number of ids that are to be
722      * cached in memory.  This is either stored in quantityStore or
723      * read from the db. (ie the value in ID_TABLE.QUANTITY).
724      *
725      * Though this method returns a BigDecimal for the quantity, it is
726      * unlikey the system could withstand whatever conditions would lead
727      * to really needing a large quantity, it is retrieved as a BigDecimal
728      * only because it is going to be added to another BigDecimal.
729      *
730      * @param tableName The name of the table we want to query.
731      * @param connection a Connection
732      * @return An int with the number of ids cached in memory.
733      */
734     private BigDecimal getQuantity(String tableName, Connection connection)
735     {
736         BigDecimal quantity = null;
737 
738         // If prefetch is turned off we simply return 1
739         if (!configuration.getBoolean(DB_IDBROKER_PREFETCH, true))
740         {
741             quantity = new BigDecimal(1);
742         }
743         // Initialize quantity, if necessary.
744         else if (quantityStore.containsKey(tableName))
745         {
746             quantity = (BigDecimal) quantityStore.get(tableName);
747         }
748         else
749         {
750             Connection dbCon = null;
751             try
752             {
753                 if (connection == null || configuration
754                     .getBoolean(DB_IDBROKER_USENEWCONNECTION, true))
755                 {
756                     String databaseName = tableMap.getDatabaseMap().getName();
757                     // Get a connection to the db
758                     dbCon = Torque.getConnection(databaseName);
759                 }
760 
761                 // Read the row from the ID_TABLE.
762                 BigDecimal[] results = selectRow(dbCon, tableName);
763 
764                 // QUANTITY column.
765                 quantity = results[1];
766                 quantityStore.put(tableName, quantity);
767             }
768             catch (Exception e)
769             {
770                 quantity = new BigDecimal(10);
771             }
772             finally
773             {
774                 // Return the connection to the pool.
775                 try
776                 {
777                     dbCon.close();
778                 }
779                 catch (Exception e)
780                 {
781                     log.error("Release of connection failed.", e);
782                 }
783             }
784         }
785         return quantity;
786     }
787 
788     /***
789      * Helper method to select a row in the ID_TABLE.
790      *
791      * @param con A Connection.
792      * @param tableName The properly escaped name of the table to
793      * identify the row.
794      * @return A BigDecimal[].
795      * @exception Exception a generic exception.
796      */
797     private BigDecimal[] selectRow(Connection con, String tableName)
798         throws Exception
799     {
800         StringBuffer stmt = new StringBuffer();
801         stmt.append("SELECT " + NEXT_ID + ", " + QUANTITY)
802             .append(" FROM " + ID_TABLE)
803             .append(" WHERE TABLE_NAME = '")
804             .append(tableName)
805             .append('\'');
806 
807         Statement statement = null;
808 
809         BigDecimal[] results = new BigDecimal[2];
810         try
811         {
812             statement = con.createStatement();
813             ResultSet rs = statement.executeQuery(stmt.toString());
814 
815             if (rs.next())
816             {
817                 // work around for MySQL which appears to support
818                 // getBigDecimal in the source code, but the binary
819                 // is throwing an NotImplemented exception.
820                 results[0] = new BigDecimal(rs.getString(1)); // next_id
821                 results[1] = new BigDecimal(rs.getString(2)); // quantity
822             }
823             else
824             {
825                 throw new TorqueException("The table " + tableName
826                         + " does not have a proper entry in the " + ID_TABLE);
827             }
828         }
829         finally
830         {
831             if (statement != null)
832             {
833                 statement.close();
834             }
835         }
836 
837         return results;
838     }
839 
840     /***
841      * Helper method to update a row in the ID_TABLE.
842      *
843      * @param con A Connection.
844      * @param tableName The properly escaped name of the table to identify the
845      * row.
846      * @param id An int with the value to set for the id.
847      * @exception Exception Database error.
848      */
849     private void updateNextId(Connection con, String tableName, String id)
850         throws Exception
851     {
852 
853 
854         StringBuffer stmt = new StringBuffer(id.length()
855                                              + tableName.length() + 50);
856         stmt.append("UPDATE " + ID_TABLE)
857             .append(" SET NEXT_ID = ")
858             .append(id)
859             .append(" WHERE TABLE_NAME = '")
860             .append(tableName)
861             .append('\'');
862 
863         Statement statement = null;
864 
865         if (log.isDebugEnabled())
866         {
867             log.debug("updateNextId: " + stmt.toString());
868         }
869 
870         try
871         {
872             statement = con.createStatement();
873             statement.executeUpdate(stmt.toString());
874         }
875         finally
876         {
877             if (statement != null)
878             {
879                 statement.close();
880             }
881         }
882     }
883 
884     /***
885      * Helper method to update a row in the ID_TABLE.
886      *
887      * @param con A Connection.
888      * @param tableName The properly escaped name of the table to identify the
889      * row.
890      * @param quantity An int with the value of the quantity.
891      * @exception Exception Database error.
892      */
893     private void updateQuantity(Connection con, String tableName,
894                                 BigDecimal quantity)
895         throws Exception
896     {
897         StringBuffer stmt = new StringBuffer(quantity.toString().length()
898                                              + tableName.length() + 50);
899         stmt.append("UPDATE " + ID_TABLE)
900             .append(" SET QUANTITY = ")
901             .append(quantity)
902             .append(" WHERE TABLE_NAME = '")
903             .append(tableName)
904             .append('\'');
905 
906         Statement statement = null;
907 
908         if (log.isDebugEnabled())
909         {
910             log.debug("updateQuantity: " + stmt.toString());
911         }
912 
913         try
914         {
915             statement = con.createStatement();
916             statement.executeUpdate(stmt.toString());
917         }
918         finally
919         {
920             if (statement != null)
921             {
922                 statement.close();
923             }
924         }
925     }
926 }