%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.torque.oid.IDBroker |
|
|
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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | private Thread houseKeeperThread = null; |
155 | ||
156 | /** |
|
157 | * Are transactions supported? |
|
158 | */ |
|
159 | 1 | private boolean transactionsSupported = false; |
160 | ||
161 | /** |
|
162 | * The value of ONE! |
|
163 | */ |
|
164 | 1 | 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 | 2 | 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 | 1 | { |
191 | 1 | this.tableMap = tMap; |
192 | 1 | configuration = Torque.getConfiguration(); |
193 | ||
194 | // Start the housekeeper thread only if prefetch has not been disabled |
|
195 | 1 | if (configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) |
196 | { |
|
197 | 0 | 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 | 0 | houseKeeperThread.setDaemon(true); |
203 | 0 | houseKeeperThread.setName("Torque - ID Broker thread"); |
204 | 0 | 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 | 1 | String dbName = tMap.getDatabaseMap().getName(); |
211 | 1 | Connection dbCon = null; |
212 | try |
|
213 | { |
|
214 | 1 | dbCon = Torque.getConnection(dbName); |
215 | 0 | transactionsSupported = dbCon.getMetaData().supportsTransactions(); |
216 | } |
|
217 | 1 | catch (Exception e) |
218 | { |
|
219 | 1 | transactionsSupported = false; |
220 | } |
|
221 | finally |
|
222 | { |
|
223 | 0 | try |
224 | { |
|
225 | // Return the connection to the pool. |
|
226 | 1 | dbCon.close(); |
227 | } |
|
228 | 1 | catch (Exception e) |
229 | { |
|
230 | 0 | } |
231 | 1 | } |
232 | 1 | if (!transactionsSupported) |
233 | { |
|
234 | 1 | 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 | 1 | } |
243 | ||
244 | /** |
|
245 | * Set the configuration |
|
246 | * |
|
247 | * @param configuration the configuration |
|
248 | */ |
|
249 | public void setConfiguration(Configuration configuration) |
|
250 | { |
|
251 | 0 | this.configuration = configuration; |
252 | 0 | } |
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 | 0 | 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 | 0 | 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 | 0 | BigDecimal[] id = getNextIds((String) tableName, 1, connection); |
308 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | if (tableName == null) |
394 | { |
|
395 | 0 | 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 | 0 | List availableIds = (List) ids.get(tableName); |
408 | ||
409 | 0 | if (availableIds == null || availableIds.size() < numOfIdsToReturn) |
410 | { |
|
411 | 0 | if (availableIds == null) |
412 | { |
|
413 | 0 | log.debug("Forced id retrieval - no available list"); |
414 | } |
|
415 | else |
|
416 | { |
|
417 | 0 | log.debug("Forced id retrieval - " + availableIds.size()); |
418 | } |
|
419 | 0 | storeIDs(tableName, true, connection); |
420 | 0 | availableIds = (List) ids.get(tableName); |
421 | } |
|
422 | ||
423 | 0 | int size = availableIds.size() < numOfIdsToReturn |
424 | ? availableIds.size() : numOfIdsToReturn; |
|
425 | ||
426 | 0 | 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 | 0 | for (int i = size - 1; i >= 0; i--) |
434 | { |
|
435 | 0 | results[i] = (BigDecimal) availableIds.get(i); |
436 | 0 | availableIds.remove(i); |
437 | } |
|
438 | // } |
|
439 | ||
440 | 0 | 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 | 0 | 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 | 0 | boolean exists = false; |
463 | 0 | Connection dbCon = null; |
464 | try |
|
465 | { |
|
466 | 0 | String databaseName = tableMap.getDatabaseMap().getName(); |
467 | ||
468 | 0 | dbCon = Torque.getConnection(databaseName); |
469 | 0 | Statement statement = dbCon.createStatement(); |
470 | 0 | ResultSet rs = statement.executeQuery(query); |
471 | 0 | exists = rs.next(); |
472 | 0 | statement.close(); |
473 | 0 | } |
474 | finally |
|
475 | { |
|
476 | // Return the connection to the pool. |
|
477 | 0 | try |
478 | { |
|
479 | 0 | dbCon.close(); |
480 | } |
|
481 | 0 | catch (Exception e) |
482 | { |
|
483 | 0 | log.error("Release of connection failed.", e); |
484 | 0 | } |
485 | 0 | } |
486 | 0 | 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 | 0 | log.debug("IDBroker thread was started."); |
497 | ||
498 | 0 | Thread thisThread = Thread.currentThread(); |
499 | 0 | while (houseKeeperThread == thisThread) |
500 | { |
|
501 | try |
|
502 | { |
|
503 | 0 | Thread.sleep(SLEEP_PERIOD); |
504 | } |
|
505 | 0 | catch (InterruptedException exc) |
506 | { |
|
507 | // ignored |
|
508 | 0 | } |
509 | ||
510 | // logger.info("IDBroker thread checking for more keys."); |
|
511 | 0 | Iterator it = ids.keySet().iterator(); |
512 | 0 | while (it.hasNext()) |
513 | { |
|
514 | 0 | String tableName = (String) it.next(); |
515 | 0 | if (log.isDebugEnabled()) |
516 | { |
|
517 | 0 | log.debug("IDBroker thread checking for more keys " |
518 | + "on table: " + tableName); |
|
519 | } |
|
520 | 0 | List availableIds = (List) ids.get(tableName); |
521 | 0 | int quantity = getQuantity(tableName, null).class="keyword">intValue(); |
522 | 0 | 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 | 0 | storeIDs(tableName, false, null); |
530 | 0 | if (log.isDebugEnabled()) |
531 | { |
|
532 | 0 | log.debug("Retrieved more ids for table: " + tableName); |
533 | } |
|
534 | } |
|
535 | 0 | catch (Exception exc) |
536 | { |
|
537 | 0 | log.error("There was a problem getting new IDs " |
538 | + "for table: " + tableName, exc); |
|
539 | 0 | } |
540 | } |
|
541 | } |
|
542 | } |
|
543 | 0 | log.debug("IDBroker thread finished."); |
544 | 0 | } |
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 | 0 | houseKeeperThread = null; |
556 | 0 | } |
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 | 0 | if (!configuration.getBoolean(DB_IDBROKER_CLEVERQUANTITY, true) |
572 | || !configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) |
|
573 | { |
|
574 | 0 | return; |
575 | } |
|
576 | ||
577 | // Get the last id request for this table. |
|
578 | 0 | java.util.Date lastTime = (java.util.Date) lastQueryTime.get(tableName); |
579 | 0 | java.util.Date now = new java.util.Date(); |
580 | ||
581 | 0 | if (lastTime != null) |
582 | { |
|
583 | 0 | long thenLong = lastTime.getTime(); |
584 | 0 | long nowLong = now.getTime(); |
585 | 0 | int timeLapse = (class="keyword">int) (nowLong - thenLong); |
586 | 0 | if (timeLapse < SLEEP_PERIOD && timeLapse > 0) |
587 | { |
|
588 | 0 | if (log.isDebugEnabled()) |
589 | { |
|
590 | 0 | 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 | 0 | float rate = getQuantity(tableName, null).class="keyword">floatValue() |
596 | / (float) timeLapse; |
|
597 | 0 | quantityStore.put(tableName, new BigDecimal( |
598 | Math.ceil(SLEEP_PERIOD * rate * SAFETY_MARGIN))); |
|
599 | } |
|
600 | } |
|
601 | 0 | lastQueryTime.put(tableName, now); |
602 | 0 | } |
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 | 0 | BigDecimal nextId = null; |
620 | 0 | BigDecimal quantity = null; |
621 | 0 | 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 | 0 | if (adjustQuantity) |
629 | { |
|
630 | 0 | checkTiming(tableName); |
631 | } |
|
632 | ||
633 | 0 | boolean useNewConnection = (connection == null) || (configuration |
634 | .getBoolean(DB_IDBROKER_USENEWCONNECTION, true)); |
|
635 | try |
|
636 | { |
|
637 | 0 | if (useNewConnection) |
638 | { |
|
639 | 0 | 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 | 0 | quantity = getQuantity(tableName, connection); |
649 | 0 | updateQuantity(connection, tableName, quantity); |
650 | ||
651 | // Read the next starting ID from the ID_TABLE. |
|
652 | 0 | BigDecimal[] results = selectRow(connection, tableName); |
653 | 0 | nextId = results[0]; // NEXT_ID column |
654 | ||
655 | // Update the row based on the quantity in the |
|
656 | // ID_TABLE. |
|
657 | 0 | BigDecimal newNextId = nextId.add(quantity); |
658 | 0 | updateNextId(connection, tableName, newNextId.toString()); |
659 | ||
660 | 0 | if (useNewConnection) |
661 | { |
|
662 | 0 | Transaction.commit(connection); |
663 | } |
|
664 | } |
|
665 | 0 | catch (Exception e) |
666 | { |
|
667 | 0 | if (useNewConnection) |
668 | { |
|
669 | 0 | Transaction.rollback(connection); |
670 | } |
|
671 | 0 | throw e; |
672 | 0 | } |
673 | ||
674 | 0 | List availableIds = (List) ids.get(tableName); |
675 | 0 | if (availableIds == null) |
676 | { |
|
677 | 0 | availableIds = new ArrayList(); |
678 | 0 | ids.put(tableName, availableIds); |
679 | } |
|
680 | ||
681 | // Create the ids and store them in the list of available ids. |
|
682 | 0 | int numId = quantity.class="keyword">intValue(); |
683 | 0 | for (int i = 0; i < numId; i++) |
684 | { |
|
685 | 0 | availableIds.add(nextId); |
686 | 0 | nextId = nextId.add(ONE); |
687 | } |
|
688 | // } |
|
689 | 0 | } |
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 | 0 | BigDecimal quantity = null; |
708 | ||
709 | // If prefetch is turned off we simply return 1 |
|
710 | 0 | if (!configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) |
711 | { |
|
712 | 0 | quantity = new BigDecimal(1); |
713 | } |
|
714 | // Initialize quantity, if necessary. |
|
715 | 0 | else if (quantityStore.containsKey(tableName)) |
716 | { |
|
717 | 0 | quantity = (BigDecimal) quantityStore.get(tableName); |
718 | } |
|
719 | else |
|
720 | { |
|
721 | 0 | Connection dbCon = null; |
722 | try |
|
723 | { |
|
724 | 0 | if (connection == null || configuration |
725 | .getBoolean(DB_IDBROKER_USENEWCONNECTION, true)) |
|
726 | { |
|
727 | 0 | String databaseName = tableMap.getDatabaseMap().getName(); |
728 | // Get a connection to the db |
|
729 | 0 | dbCon = Torque.getConnection(databaseName); |
730 | } |
|
731 | ||
732 | // Read the row from the ID_TABLE. |
|
733 | 0 | BigDecimal[] results = selectRow(dbCon, tableName); |
734 | ||
735 | // QUANTITY column. |
|
736 | 0 | quantity = results[1]; |
737 | 0 | quantityStore.put(tableName, quantity); |
738 | 0 | } |
739 | 0 | catch (Exception e) |
740 | { |
|
741 | 0 | quantity = new BigDecimal(10); |
742 | 0 | } |
743 | finally |
|
744 | { |
|
745 | // Return the connection to the pool. |
|
746 | 0 | try |
747 | { |
|
748 | 0 | dbCon.close(); |
749 | } |
|
750 | 0 | catch (Exception e) |
751 | { |
|
752 | 0 | log.error("Release of connection failed.", e); |
753 | 0 | } |
754 | 0 | } |
755 | } |
|
756 | 0 | 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 | 0 | StringBuffer stmt = new StringBuffer(); |
772 | 0 | 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 | 0 | Statement statement = null; |
785 | ||
786 | 0 | BigDecimal[] results = new BigDecimal[2]; |
787 | try |
|
788 | { |
|
789 | 0 | statement = con.createStatement(); |
790 | 0 | ResultSet rs = statement.executeQuery(stmt.toString()); |
791 | ||
792 | 0 | 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 | 0 | results[0] = new BigDecimal(rs.getString(1)); // next_id |
798 | 0 | results[1] = new BigDecimal(rs.getString(2)); // quantity |
799 | } |
|
800 | else |
|
801 | { |
|
802 | 0 | throw new TorqueException("The table " + tableName |
803 | + " does not have a proper entry in the " + ID_TABLE); |
|
804 | } |
|
805 | } |
|
806 | finally |
|
807 | { |
|
808 | 0 | if (statement != null) |
809 | { |
|
810 | 0 | statement.close(); |
811 | } |
|
812 | } |
|
813 | ||
814 | 0 | 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 | 0 | StringBuffer stmt = new StringBuffer(id.length() |
832 | + tableName.length() + 50); |
|
833 | 0 | 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 | 0 | Statement statement = null; |
845 | ||
846 | 0 | if (log.isDebugEnabled()) |
847 | { |
|
848 | 0 | log.debug("updateNextId: " + stmt.toString()); |
849 | } |
|
850 | ||
851 | try |
|
852 | { |
|
853 | 0 | statement = con.createStatement(); |
854 | 0 | statement.executeUpdate(stmt.toString()); |
855 | } |
|
856 | finally |
|
857 | { |
|
858 | 0 | if (statement != null) |
859 | { |
|
860 | 0 | statement.close(); |
861 | } |
|
862 | } |
|
863 | 0 | } |
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 | 0 | StringBuffer stmt = new StringBuffer(quantity.toString().length() |
879 | + tableName.length() + 50); |
|
880 | 0 | 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 | 0 | Statement statement = null; |
893 | ||
894 | 0 | if (log.isDebugEnabled()) |
895 | { |
|
896 | 0 | log.debug("updateQuantity: " + stmt.toString()); |
897 | } |
|
898 | ||
899 | try |
|
900 | { |
|
901 | 0 | statement = con.createStatement(); |
902 | 0 | statement.executeUpdate(stmt.toString()); |
903 | } |
|
904 | finally |
|
905 | { |
|
906 | 0 | if (statement != null) |
907 | { |
|
908 | 0 | statement.close(); |
909 | } |
|
910 | } |
|
911 | 0 | } |
912 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |