KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > versant > core > jdbc > sql > HighLowJdbcKeyGenerator


1
2 /*
3  * Copyright (c) 1998 - 2005 Versant Corporation
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  * Versant Corporation - initial API and implementation
11  */

12 package com.versant.core.jdbc.sql;
13
14 import com.versant.core.metadata.MDStatics;
15 import com.versant.core.jdbc.JdbcKeyGenerator;
16 import com.versant.core.jdbc.JdbcKeyGeneratorFactory;
17 import com.versant.core.jdbc.JdbcMetaDataBuilder;
18 import com.versant.core.jdbc.metadata.JdbcColumn;
19 import com.versant.core.jdbc.metadata.JdbcTable;
20 import com.versant.core.jdbc.metadata.JdbcMappingResolver;
21
22 import java.sql.*;
23 import java.util.HashSet JavaDoc;
24 import java.util.Iterator JavaDoc;
25
26 import com.versant.core.common.BindingSupportImpl;
27
28 /**
29  * This key generator uses a last used id table and a grab size to generate
30  * primary keys. Each instance generates keys for a single class.
31  */

32 public class HighLowJdbcKeyGenerator implements JdbcKeyGenerator {
33
34     /**
35      * Our args bean.
36      */

37     public static class Args {
38
39
40         private String JavaDoc tableName = "jdo_keygen";
41
42
43         private String JavaDoc keyColumnName = "table_name";
44         private String JavaDoc valueColumnName = "last_used_id";
45         private int keyColumnLength = 64;
46         private int grabSize = 10;
47         private int start;
48         private boolean createTable = true;
49         private String JavaDoc pkConstraint;
50
51         public Args() {
52         }
53
54         public String JavaDoc getTableName() {
55             return tableName;
56         }
57
58         public void setTableName(String JavaDoc tableName) {
59             this.tableName = tableName;
60         }
61
62         public String JavaDoc getKeyColumnName() {
63             return keyColumnName;
64         }
65
66         public void setKeyColumnName(String JavaDoc keyColumnName) {
67             this.keyColumnName = keyColumnName;
68         }
69
70         public String JavaDoc getValueColumnName() {
71             return valueColumnName;
72         }
73
74         public void setValueColumnName(String JavaDoc valueColumnName) {
75             this.valueColumnName = valueColumnName;
76         }
77
78         public int getKeyColumnLength() {
79             return keyColumnLength;
80         }
81
82         public void setKeyColumnLength(int keyColumnLength) {
83             this.keyColumnLength = keyColumnLength;
84         }
85
86         public int getGrabSize() {
87             return grabSize;
88         }
89
90         public void setGrabSize(int grabSize) {
91             this.grabSize = grabSize;
92         }
93
94         public int getStart() {
95             return start;
96         }
97
98         public void setStart(int start) {
99             this.start = start;
100         }
101
102         public boolean isCreateTable() {
103             return createTable;
104         }
105
106         public void setCreateTable(boolean createTable) {
107             this.createTable = createTable;
108         }
109
110         public String JavaDoc getPkConstraint() {
111             return pkConstraint;
112         }
113
114         public void setPkConstraint(String JavaDoc pkConstraint) {
115             this.pkConstraint = pkConstraint;
116         }
117     }
118
119     /**
120      * Our factory.
121      */

122     public static class Factory implements JdbcKeyGeneratorFactory{
123
124         /**
125          * Create a javabean to hold args for a createJdbcKeyGenerator call or null
126          * if the key generator does not accept any arguments.
127          */

128         public Object JavaDoc createArgsBean() {
129             return new Args();
130         }
131
132         /**
133          * Create a JdbcKeyGenerator for class using props as parameters. The
134          * instance returned may be new or may be a shared instance.
135          */

136         public JdbcKeyGenerator createJdbcKeyGenerator(String JavaDoc className,
137                 JdbcTable classTable, Object JavaDoc args) {
138             HighLowJdbcKeyGenerator kg = new HighLowJdbcKeyGenerator(
139                     classTable, (Args)args);
140             return kg;
141         }
142     }
143
144     protected JdbcTable classTable;
145     protected JdbcColumn classPk;
146     protected int pkJavaTypeCode;
147
148     protected String JavaDoc tableName;
149     protected String JavaDoc keyColumnName;
150     protected String JavaDoc valueColumnName;
151     protected int keyColumnLength;
152     protected int grabSize;
153     protected int start;
154     protected boolean createTable;
155     protected String JavaDoc pkConstraint;
156
157     protected String JavaDoc updateSql;
158     protected String JavaDoc selectSql;
159     protected int lastUsed;
160     protected int grabLeft;
161
162     public HighLowJdbcKeyGenerator(JdbcTable classTable, Args args) {
163         if (classTable.pk.length > 1) {
164             throw new IllegalArgumentException JavaDoc("Cannot use HIGH/LOW key generator on a table with multiple " +
165                     "primary key columns");
166         }
167         this.classTable = classTable;
168         classPk = classTable.pk[0];
169         pkJavaTypeCode = classPk.javaTypeCode;
170         tableName = args.getTableName();
171         keyColumnName = args.getKeyColumnName();
172         valueColumnName = args.getValueColumnName();
173         keyColumnLength = args.getKeyColumnLength();
174         grabSize = args.getGrabSize();
175         start = args.getStart();
176         createTable = args.isCreateTable();
177         pkConstraint = args.getPkConstraint();
178     }
179
180     /**
181      * Initialize this key generator. This is called when the JDO
182      * implementation initializes before any keys are generated. Key
183      * generators should use this to avoid popular race conditions and
184      * deadlock opportunities (e.g. multiple 'select max(id) from table'
185      * statements executing at the same time). If the same key generator
186      * instance is used on more than one class this will be called once
187      * for each class.
188      *
189      * @param className The name of the class
190      * @param classTable The table for the class
191      * @param con Connection to the DataSource for the class
192      */

193     public void init(String JavaDoc className, JdbcTable classTable,
194             Connection con) throws SQLException {
195
196         // generate our update and select statements
197
String JavaDoc where = " where " + keyColumnName + " = '" + classTable.name + "'";
198         updateSql = "update " + tableName + " set " + valueColumnName +
199                 " = " + valueColumnName + " + ?" +
200                 where;
201         selectSql = "select " + valueColumnName + " from " + tableName +
202                 where;
203
204         // make sure there is a row in our keygen table for our class
205
Statement stat = null;
206         PreparedStatement ps = null;
207         try {
208             ps = con.prepareStatement(updateSql);
209             ps.setInt(1, 0);
210             if (ps.executeUpdate() == 0) {
211                 stat = con.createStatement();
212                 int first = start;
213                 if (first == 0) {
214                     String JavaDoc sql =
215                             "select max(" + classPk.name + ") from " + classTable.name;
216                     ResultSet rs = null;
217                     try {
218                         rs = stat.executeQuery(sql);
219                         rs.next();
220                         first = rs.getInt(1);
221                     } finally {
222                         cleanup(rs);
223                     }
224                 }
225                 String JavaDoc sql =
226                         "insert into " + tableName + " (" + keyColumnName + ", " +
227                         valueColumnName + ") values ('" + classTable.name + "', " +
228                         first + ")";
229                 stat.execute(sql);
230             }
231         } finally {
232             cleanup(ps);
233             cleanup(stat);
234         }
235     }
236
237     /**
238      * If the new key can only be detirmined after the new row has been
239      * inserted (e.g. if using a database autoincrement column) then this
240      * should return true.
241      */

242     public boolean isPostInsertGenerator() {
243         return false;
244     }
245
246     /**
247      * Does this key generator require its own connection? If it does then
248      * one will be obtained to generate the key and committed after the
249      * key has been generated.
250      */

251     public boolean isRequiresOwnConnection() {
252         return grabSize > 1 && grabLeft == 0;
253     }
254
255     /**
256      * Add any JdbcTable instances that this key generator requires to the
257      * supplied set. This method is called once per key generator during meta
258      * data generation. Any tables returned will be added to the meta data and
259      * will get into SQL scripts and so on. If the same key generator
260      * instance is returned more than once by a factory then this method
261      * will still only be called once per instance.
262      */

263     public void addKeyGenTables(HashSet JavaDoc set, JdbcMetaDataBuilder mdb) {
264         if (!createTable) return;
265
266         // do not create a table if there is already one with our tableName
267
for (Iterator JavaDoc i = set.iterator(); i.hasNext();) {
268             JdbcTable t = (JdbcTable)i.next();
269             if (t.name.equals(tableName)) return;
270         }
271
272         // create the table and add it
273
JdbcTable t = new JdbcTable();
274         t.sqlDriver = mdb.getSqlDriver();
275         t.name = tableName;
276         t.comment = getClass().getName();
277         t.pkConstraintName = pkConstraint == null ? "pk_" + tableName : pkConstraint;
278         JdbcMappingResolver mr = mdb.getMappingResolver();
279         JdbcColumn keyCol = new JdbcColumn(mr.resolveMapping(String JavaDoc.class), mr);
280         keyCol.name = keyColumnName;
281         keyCol.length = keyColumnLength;
282         keyCol.nulls = false;
283         JdbcColumn valueCol =
284                 new JdbcColumn(mr.resolveMapping(Integer.TYPE), mr);
285
286         valueCol.name = valueColumnName;
287         valueCol.nulls = false;
288         t.cols = new JdbcColumn[]{keyCol, valueCol};
289         t.setPk(new JdbcColumn[]{keyCol});
290         set.add(t);
291     }
292
293     /**
294      * Generate a new primary key value for a new instance of the supplied
295      * class prior to the row being inserted. The values generated will be used
296      * to populate a new OID and then set on a PreparedStatement for the
297      * insert. This is called if isPostInsertGenerator returns false.
298      * <p/>
299      * The newObjectCount parameter indicates the number of new objects that
300      * will be inserted (including this one) in the same transaction using
301      * this key generator. This may be used to optimize the behavior of the
302      * key generator or be ignored. The highlow key generator uses this value
303      * instead of its grabSize to avoid executing redundant updates and
304      * selects.<p>
305      *
306      * @param className The name of the class
307      * @param classTable The table for the class
308      * @param newObjectCount The number of new objects being created
309      * @param data The array to store the key values in.
310      * @param con Connection to the DataSource for the class.
311      * @throws SQLException on errors
312      */

313     public synchronized void generatePrimaryKeyPre(String JavaDoc className,
314             JdbcTable classTable, int newObjectCount, Object JavaDoc[] data,
315             Connection con)
316             throws SQLException {
317         int pk;
318         if (grabSize == 1) {
319             pk = lookupNewNumber(con, grabSize);
320         } else {
321             if (grabLeft == 0) {
322                 int effectiveGrabSize = newObjectCount;
323                 if (effectiveGrabSize < grabSize) effectiveGrabSize = grabSize;
324                 lastUsed = lookupNewNumber(con, effectiveGrabSize);
325                 grabLeft = effectiveGrabSize - 1;
326             } else {
327                 --grabLeft;
328             }
329             pk = lastUsed++;
330         }
331         switch (pkJavaTypeCode) {
332             case MDStatics.INTW:
333             case MDStatics.INT:
334                 data[0] = new Integer JavaDoc((int)pk);
335                 break;
336             case MDStatics.SHORTW:
337             case MDStatics.SHORT:
338                 data[0] = new Short JavaDoc((short)pk);
339                 break;
340             case MDStatics.BYTEW:
341             case MDStatics.BYTE:
342                 data[0] = new Byte JavaDoc((byte)pk);
343                 break;
344             case MDStatics.LONGW:
345             case MDStatics.LONG:
346                 data[0] = new Long JavaDoc(pk);
347                 break;
348             default:
349                 throw BindingSupportImpl.getInstance().internal(
350                         "Unhandled java type code: " + pkJavaTypeCode);
351         }
352     }
353
354     /**
355      * Run SQL to get a new number. This does an update and a select for
356      * our classes row in the keygen table.
357      */

358     protected int lookupNewNumber(Connection con, int effectiveGrabSize)
359             throws SQLException {
360         PreparedStatement ps = null;
361         try {
362             ps = con.prepareStatement(updateSql);
363             ps.setInt(1, effectiveGrabSize);
364             if (ps.executeUpdate() == 0) {
365                 throw BindingSupportImpl.getInstance().fatalDatastore("Row not found in keygen table:\n" +
366                         updateSql);
367             }
368             Statement stat = null;
369             ResultSet rs = null;
370             try {
371                 stat = con.createStatement();
372                 rs = stat.executeQuery(selectSql);
373                 rs.next();
374                 return rs.getInt(1) - (effectiveGrabSize - 1);
375             } finally {
376                 cleanup(rs);
377                 cleanup(stat);
378             }
379         } finally {
380             cleanup(ps);
381         }
382     }
383
384     private void cleanup(ResultSet rs) {
385         try {
386             if (rs != null) rs.close();
387         } catch (SQLException e) {
388             // ignore
389
}
390     }
391
392     private void cleanup(Statement s) {
393         try {
394             if (s != null) s.close();
395         } catch (SQLException e) {
396             // ignore
397
}
398     }
399
400     /**
401      * Generate a new primary key value for a new instance of the supplied
402      * class after the row has been inserted. The values generated will be used
403      * to populate a new OID and then set on a PreparedStatement for the
404      * insert. This is called if isPostInsertGenerator returns true.
405      *
406      * @param className The name of the class
407      * @param classTable The table for the class
408      * @param data The array to store the key values in.
409      * @param con Connection to the DataSource for the class.
410      * @param stat Statement created from con. Do not close it. This will have
411      * just been used to insert the new row.
412      * @throws SQLException on errors
413      */

414     public void generatePrimaryKeyPost(String JavaDoc className,
415             JdbcTable classTable, Object JavaDoc[] data,
416             Connection con, Statement stat) throws SQLException {
417         throw BindingSupportImpl.getInstance().internal("not a postInsertGenerator");
418     }
419
420     /**
421      * Get extra SQL to be appended to the insert statement. This is only
422      * called for post insert key generators. Return null if no extra SQL
423      * is required. Key generators can use this as an alternative to running
424      * a separate query to get the primary key for the just inserted row.
425      */

426     public String JavaDoc getPostInsertSQLSuffix(JdbcTable classTable) {
427         throw BindingSupportImpl.getInstance().internal("not a postInsertGenerator");
428     }
429
430 }
431
Popular Tags