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