%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.torque.oid.IDBroker |
|
|
1 | package org.apache.torque.oid; |
|
2 | ||
3 | /* |
|
4 | * Licensed to the Apache Software Foundation (ASF) under one |
|
5 | * or more contributor license agreements. See the NOTICE file |
|
6 | * distributed with this work for additional information |
|
7 | * regarding copyright ownership. The ASF licenses this file |
|
8 | * to you under the Apache License, Version 2.0 (the |
|
9 | * "License"); you may not use this file except in compliance |
|
10 | * with the License. You may obtain a copy of the License at |
|
11 | * |
|
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
13 | * |
|
14 | * Unless required by applicable law or agreed to in writing, |
|
15 | * software distributed under the License is distributed on an |
|
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
17 | * KIND, either express or implied. See the License for the |
|
18 | * specific language governing permissions and limitations |
|
19 | * under the License. |
|
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 | // NOTE: |
|
42 | // It would be nice to decouple this from |
|
43 | // Torque. This is a great stand-alone utility. |
|
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 476550 2006-11-18 16:08:37Z tfischer $ |
|
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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | private Thread houseKeeperThread = null; |
156 | ||
157 | /** |
|
158 | * Are transactions supported? |
|
159 | */ |
|
160 | 0 | private boolean transactionsSupported = false; |
161 | ||
162 | /** |
|
163 | * The value of ONE! |
|
164 | */ |
|
165 | 0 | 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 | 0 | 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 | 0 | this(database.getName()); |
192 | 0 | } |
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 | 0 | this(tMap.getDatabaseMap().getName()); |
204 | 0 | } |
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 | 0 | { |
215 | 0 | this.databaseName = databaseName; |
216 | 0 | configuration = Torque.getConfiguration(); |
217 | ||
218 | // Start the housekeeper thread only if prefetch has not been disabled |
|
219 | 0 | if (configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) |
220 | { |
|
221 | 0 | houseKeeperThread = new Thread(this); |
222 | // Indicate that this is a system thread. JVM will quit only when |
|
223 | // there are no more active user threads. Settings threads spawned |
|
224 | // internally by Torque as daemons allows commandline applications |
|
225 | // using Torque terminate in an orderly manner. |
|
226 | 0 | houseKeeperThread.setDaemon(true); |
227 | 0 | houseKeeperThread.setName("Torque - ID Broker thread"); |
228 | 0 | houseKeeperThread.start(); |
229 | } |
|
230 | ||
231 | // Check for Transaction support. Give warning message if |
|
232 | // IDBroker is being used with a database that does not |
|
233 | // support transactions. |
|
234 | 0 | Connection dbCon = null; |
235 | try |
|
236 | { |
|
237 | 0 | dbCon = Torque.getConnection(databaseName); |
238 | } |
|
239 | 0 | catch (Throwable t) |
240 | { |
|
241 | 0 | log.error("Could not open a connection to the database " |
242 | + databaseName, |
|
243 | t); |
|
244 | 0 | transactionsSupported = false; |
245 | 0 | } |
246 | try |
|
247 | { |
|
248 | 0 | transactionsSupported = dbCon.getMetaData().supportsTransactions(); |
249 | } |
|
250 | 0 | catch (Exception e) |
251 | { |
|
252 | 0 | log.warn("Could not read from connection Metadata" |
253 | + " whether transactions are supported for the database " |
|
254 | + databaseName, |
|
255 | e); |
|
256 | 0 | transactionsSupported = false; |
257 | } |
|
258 | finally |
|
259 | { |
|
260 | 0 | try |
261 | { |
|
262 | // Return the connection to the pool. |
|
263 | 0 | dbCon.close(); |
264 | } |
|
265 | 0 | catch (Exception e) |
266 | { |
|
267 | 0 | log.warn("Could not close the connection which was used " |
268 | + "for testing whether transactions are supported", |
|
269 | e); |
|
270 | 0 | } |
271 | 0 | } |
272 | 0 | if (!transactionsSupported) |
273 | { |
|
274 | 0 | 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 | 0 | } |
283 | ||
284 | /** |
|
285 | * Set the configuration |
|
286 | * |
|
287 | * @param configuration the configuration |
|
288 | */ |
|
289 | public void setConfiguration(Configuration configuration) |
|
290 | { |
|
291 | 0 | this.configuration = configuration; |
292 | 0 | } |
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 | 0 | 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 | 0 | 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 | 0 | BigDecimal[] id = getNextIds((String) tableName, 1, connection); |
348 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | if (tableName == null) |
434 | { |
|
435 | 0 | throw new Exception("getNextIds(): tableName == null"); |
436 | } |
|
437 | ||
438 | // A note about the synchronization: I (jmcnally) looked at |
|
439 | // the synchronized blocks to avoid thread issues that were |
|
440 | // being used in this and the storeId method. I do not think |
|
441 | // they were being effective, so I synchronized the method. |
|
442 | // I have left the blocks that did exist commented in the code |
|
443 | // to make it easier for others to take a look, because it |
|
444 | // would be preferrable to avoid the synchronization on the |
|
445 | // method |
|
446 | ||
447 | 0 | List availableIds = (List) ids.get(tableName); |
448 | ||
449 | 0 | if (availableIds == null || availableIds.size() < numOfIdsToReturn) |
450 | { |
|
451 | 0 | if (availableIds == null) |
452 | { |
|
453 | 0 | log.debug("Forced id retrieval - no available list"); |
454 | 0 | } |
455 | else |
|
456 | { |
|
457 | 0 | log.debug("Forced id retrieval - " + availableIds.size()); |
458 | } |
|
459 | 0 | storeIDs(tableName, true, connection); |
460 | 0 | availableIds = (List) ids.get(tableName); |
461 | } |
|
462 | ||
463 | 0 | int size = availableIds.size() < numOfIdsToReturn |
464 | ? availableIds.size() : numOfIdsToReturn; |
|
465 | ||
466 | 0 | BigDecimal[] results = new BigDecimal[size]; |
467 | ||
468 | // We assume that availableIds will always come from the ids |
|
469 | // Hashtable and would therefore always be the same object for |
|
470 | // a specific table. |
|
471 | // synchronized (availableIds) |
|
472 | // { |
|
473 | 0 | for (int i = size - 1; i >= 0; i--) |
474 | { |
|
475 | 0 | results[i] = (BigDecimal) availableIds.get(i); |
476 | 0 | availableIds.remove(i); |
477 | } |
|
478 | // } |
|
479 | ||
480 | 0 | 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 | 0 | 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 | 0 | boolean exists = false; |
501 | 0 | Connection dbCon = null; |
502 | try |
|
503 | { |
|
504 | 0 | dbCon = Torque.getConnection(databaseName); |
505 | 0 | Statement statement = dbCon.createStatement(); |
506 | 0 | ResultSet rs = statement.executeQuery(query); |
507 | 0 | exists = rs.next(); |
508 | 0 | statement.close(); |
509 | } |
|
510 | finally |
|
511 | { |
|
512 | // Return the connection to the pool. |
|
513 | 0 | try |
514 | { |
|
515 | 0 | dbCon.close(); |
516 | } |
|
517 | 0 | catch (Exception e) |
518 | { |
|
519 | 0 | log.error("Release of connection failed.", e); |
520 | 0 | } |
521 | 0 | } |
522 | 0 | 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 | 0 | log.debug("IDBroker thread was started."); |
533 | ||
534 | 0 | Thread thisThread = Thread.currentThread(); |
535 | 0 | while (houseKeeperThread == thisThread) |
536 | { |
|
537 | try |
|
538 | { |
|
539 | 0 | Thread.sleep(SLEEP_PERIOD); |
540 | } |
|
541 | 0 | catch (InterruptedException exc) |
542 | { |
|
543 | // ignored |
|
544 | 0 | } |
545 | ||
546 | // logger.info("IDBroker thread checking for more keys."); |
|
547 | 0 | Iterator it = ids.keySet().iterator(); |
548 | 0 | while (it.hasNext()) |
549 | { |
|
550 | 0 | String tableName = (String) it.next(); |
551 | 0 | if (log.isDebugEnabled()) |
552 | { |
|
553 | 0 | log.debug("IDBroker thread checking for more keys " |
554 | + "on table: " + tableName); |
|
555 | } |
|
556 | 0 | List availableIds = (List) ids.get(tableName); |
557 | 0 | int quantity = getQuantity(tableName, null).class="keyword">intValue(); |
558 | 0 | if (quantity > availableIds.size()) |
559 | { |
|
560 | try |
|
561 | { |
|
562 | // Second parameter is false because we don't |
|
563 | // want the quantity to be adjusted for thread |
|
564 | // calls. |
|
565 | 0 | storeIDs(tableName, false, null); |
566 | 0 | if (log.isDebugEnabled()) |
567 | { |
|
568 | 0 | log.debug("Retrieved more ids for table: " + tableName); |
569 | } |
|
570 | } |
|
571 | 0 | catch (Exception exc) |
572 | { |
|
573 | 0 | log.error("There was a problem getting new IDs " |
574 | + "for table: " + tableName, exc); |
|
575 | 0 | } |
576 | } |
|
577 | 0 | } |
578 | 0 | } |
579 | 0 | log.debug("IDBroker thread finished."); |
580 | 0 | } |
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 | 0 | houseKeeperThread = null; |
592 | 0 | } |
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 | // Check if quantity changing is switched on. |
|
606 | // If prefetch is turned off, changing quantity does not make sense |
|
607 | 0 | if (!configuration.getBoolean(DB_IDBROKER_CLEVERQUANTITY, true) |
608 | || !configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) |
|
609 | { |
|
610 | 0 | return; |
611 | } |
|
612 | ||
613 | // Get the last id request for this table. |
|
614 | 0 | java.util.Date lastTime = (java.util.Date) lastQueryTime.get(tableName); |
615 | 0 | java.util.Date now = new java.util.Date(); |
616 | ||
617 | 0 | if (lastTime != null) |
618 | { |
|
619 | 0 | long thenLong = lastTime.getTime(); |
620 | 0 | long nowLong = now.getTime(); |
621 | 0 | int timeLapse = (class="keyword">int) (nowLong - thenLong); |
622 | 0 | if (timeLapse < SLEEP_PERIOD && timeLapse > 0) |
623 | { |
|
624 | 0 | if (log.isDebugEnabled()) |
625 | { |
|
626 | 0 | log.debug("Unscheduled retrieval of more ids for table: " |
627 | + tableName); |
|
628 | } |
|
629 | // Increase quantity, so that hopefully this does not |
|
630 | // happen again. |
|
631 | 0 | float rate = getQuantity(tableName, null).class="keyword">floatValue() |
632 | / (float) timeLapse; |
|
633 | 0 | quantityStore.put(tableName, new BigDecimal( |
634 | Math.ceil(SLEEP_PERIOD * rate * SAFETY_MARGIN))); |
|
635 | } |
|
636 | } |
|
637 | 0 | lastQueryTime.put(tableName, now); |
638 | 0 | } |
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 | 0 | BigDecimal nextId = null; |
656 | 0 | BigDecimal quantity = null; |
657 | ||
658 | // Block on the table. Multiple tables are allowed to ask for |
|
659 | // ids simultaneously. |
|
660 | // TableMap tMap = dbMap.getTable(tableName); |
|
661 | // synchronized(tMap) see comment in the getNextIds method |
|
662 | // { |
|
663 | 0 | if (adjustQuantity) |
664 | { |
|
665 | 0 | checkTiming(tableName); |
666 | } |
|
667 | ||
668 | 0 | boolean useNewConnection = (connection == null) || (configuration |
669 | .getBoolean(DB_IDBROKER_USENEWCONNECTION, true)); |
|
670 | try |
|
671 | { |
|
672 | 0 | if (useNewConnection) |
673 | { |
|
674 | 0 | connection = Transaction.beginOptional(databaseName, |
675 | transactionsSupported); |
|
676 | } |
|
677 | ||
678 | // Write the current value of quantity of keys to grab |
|
679 | // to the database, primarily to obtain a write lock |
|
680 | // on the table/row, but this value will also be used |
|
681 | // as the starting value when an IDBroker is |
|
682 | // instantiated. |
|
683 | 0 | quantity = getQuantity(tableName, connection); |
684 | 0 | updateQuantity(connection, tableName, quantity); |
685 | ||
686 | // Read the next starting ID from the ID_TABLE. |
|
687 | 0 | BigDecimal[] results = selectRow(connection, tableName); |
688 | 0 | nextId = results[0]; // NEXT_ID column |
689 | ||
690 | // Update the row based on the quantity in the |
|
691 | // ID_TABLE. |
|
692 | 0 | BigDecimal newNextId = nextId.add(quantity); |
693 | 0 | updateNextId(connection, tableName, newNextId.toString()); |
694 | ||
695 | 0 | if (useNewConnection) |
696 | { |
|
697 | 0 | Transaction.commit(connection); |
698 | } |
|
699 | } |
|
700 | 0 | catch (Exception e) |
701 | { |
|
702 | 0 | if (useNewConnection) |
703 | { |
|
704 | 0 | Transaction.rollback(connection); |
705 | } |
|
706 | 0 | throw e; |
707 | 0 | } |
708 | ||
709 | 0 | List availableIds = (List) ids.get(tableName); |
710 | 0 | if (availableIds == null) |
711 | { |
|
712 | 0 | availableIds = new ArrayList(); |
713 | 0 | ids.put(tableName, availableIds); |
714 | } |
|
715 | ||
716 | // Create the ids and store them in the list of available ids. |
|
717 | 0 | int numId = quantity.class="keyword">intValue(); |
718 | 0 | for (int i = 0; i < numId; i++) |
719 | { |
|
720 | 0 | availableIds.add(nextId); |
721 | 0 | nextId = nextId.add(ONE); |
722 | } |
|
723 | // } |
|
724 | 0 | } |
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 | 0 | BigDecimal quantity = null; |
743 | ||
744 | // If prefetch is turned off we simply return 1 |
|
745 | 0 | if (!configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) |
746 | { |
|
747 | 0 | quantity = new BigDecimal(1); |
748 | 0 | } |
749 | // Initialize quantity, if necessary. |
|
750 | 0 | else if (quantityStore.containsKey(tableName)) |
751 | { |
|
752 | 0 | quantity = (BigDecimal) quantityStore.get(tableName); |
753 | 0 | } |
754 | else |
|
755 | { |
|
756 | 0 | Connection dbCon = null; |
757 | try |
|
758 | { |
|
759 | 0 | if (connection == null || configuration |
760 | .getBoolean(DB_IDBROKER_USENEWCONNECTION, true)) |
|
761 | { |
|
762 | // Get a connection to the db |
|
763 | 0 | dbCon = Torque.getConnection(databaseName); |
764 | } |
|
765 | ||
766 | // Read the row from the ID_TABLE. |
|
767 | 0 | BigDecimal[] results = selectRow(dbCon, tableName); |
768 | ||
769 | // QUANTITY column. |
|
770 | 0 | quantity = results[1]; |
771 | 0 | quantityStore.put(tableName, quantity); |
772 | } |
|
773 | 0 | catch (Exception e) |
774 | { |
|
775 | 0 | quantity = new BigDecimal(10); |
776 | } |
|
777 | finally |
|
778 | { |
|
779 | // Return the connection to the pool. |
|
780 | 0 | try |
781 | { |
|
782 | 0 | dbCon.close(); |
783 | } |
|
784 | 0 | catch (Exception e) |
785 | { |
|
786 | 0 | log.error("Release of connection failed.", e); |
787 | 0 | } |
788 | 0 | } |
789 | } |
|
790 | 0 | 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 | 0 | StringBuffer stmt = new StringBuffer(); |
806 | 0 | 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 | 0 | Statement statement = null; |
819 | ||
820 | 0 | BigDecimal[] results = new BigDecimal[2]; |
821 | try |
|
822 | { |
|
823 | 0 | statement = con.createStatement(); |
824 | 0 | ResultSet rs = statement.executeQuery(stmt.toString()); |
825 | ||
826 | 0 | if (rs.next()) |
827 | { |
|
828 | // work around for MySQL which appears to support |
|
829 | // getBigDecimal in the source code, but the binary |
|
830 | // is throwing an NotImplemented exception. |
|
831 | 0 | results[0] = new BigDecimal(rs.getString(1)); // next_id |
832 | 0 | results[1] = new BigDecimal(rs.getString(2)); // quantity |
833 | 0 | } |
834 | else |
|
835 | { |
|
836 | 0 | throw new TorqueException("The table " + tableName |
837 | + " does not have a proper entry in the " + ID_TABLE); |
|
838 | } |
|
839 | } |
|
840 | finally |
|
841 | { |
|
842 | 0 | if (statement != null) |
843 | { |
|
844 | 0 | statement.close(); |
845 | 0 | } |
846 | 0 | } |
847 | ||
848 | 0 | 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 | 0 | StringBuffer stmt = new StringBuffer(id.length() |
866 | + tableName.length() + 50); |
|
867 | 0 | 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 | 0 | Statement statement = null; |
879 | ||
880 | 0 | if (log.isDebugEnabled()) |
881 | { |
|
882 | 0 | log.debug("updateNextId: " + stmt.toString()); |
883 | } |
|
884 | ||
885 | try |
|
886 | { |
|
887 | 0 | statement = con.createStatement(); |
888 | 0 | statement.executeUpdate(stmt.toString()); |
889 | } |
|
890 | finally |
|
891 | { |
|
892 | 0 | if (statement != null) |
893 | { |
|
894 | 0 | statement.close(); |
895 | 0 | } |
896 | 0 | } |
897 | 0 | } |
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 | 0 | StringBuffer stmt = new StringBuffer(quantity.toString().length() |
913 | + tableName.length() + 50); |
|
914 | 0 | 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 | 0 | Statement statement = null; |
927 | ||
928 | 0 | if (log.isDebugEnabled()) |
929 | { |
|
930 | 0 | log.debug("updateQuantity: " + stmt.toString()); |
931 | } |
|
932 | ||
933 | try |
|
934 | { |
|
935 | 0 | statement = con.createStatement(); |
936 | 0 | statement.executeUpdate(stmt.toString()); |
937 | } |
|
938 | finally |
|
939 | { |
|
940 | 0 | if (statement != null) |
941 | { |
|
942 | 0 | statement.close(); |
943 | 0 | } |
944 | 0 | } |
945 | 0 | } |
946 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |