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