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