View Javadoc

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