1 56 57 package org.objectstyle.cayenne.dba; 58 59 import java.sql.Connection ; 60 import java.sql.DatabaseMetaData ; 61 import java.sql.ResultSet ; 62 import java.sql.SQLException ; 63 import java.sql.Statement ; 64 import java.sql.Types ; 65 import java.util.ArrayList ; 66 import java.util.Collections ; 67 import java.util.HashMap ; 68 import java.util.Iterator ; 69 import java.util.List ; 70 import java.util.Map ; 71 72 import org.objectstyle.cayenne.CayenneRuntimeException; 73 import org.objectstyle.cayenne.access.DataNode; 74 import org.objectstyle.cayenne.access.QueryLogger; 75 import org.objectstyle.cayenne.access.util.DefaultOperationObserver; 76 import org.objectstyle.cayenne.map.DbAttribute; 77 import org.objectstyle.cayenne.map.DbEntity; 78 import org.objectstyle.cayenne.map.DbKeyGenerator; 79 import org.objectstyle.cayenne.map.ObjAttribute; 80 import org.objectstyle.cayenne.query.Query; 81 import org.objectstyle.cayenne.query.SQLTemplate; 82 import org.objectstyle.cayenne.util.IDUtil; 83 84 90 public class JdbcPkGenerator implements PkGenerator { 91 public static final int DEFAULT_PK_CACHE_SIZE = 20; 92 93 96 protected static final String NEXT_ID = "NEXT_ID"; 97 98 101 protected static final ObjAttribute[] objDesc = 102 new ObjAttribute[] { new ObjAttribute("nextId", Integer .class.getName(), null)}; 103 104 107 protected static final DbAttribute[] resultDesc = 108 new DbAttribute[] { new DbAttribute(NEXT_ID, Types.INTEGER, null)}; 109 110 111 protected Map pkCache = new HashMap (); 112 protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE; 113 114 static { 115 objDesc[0].setDbAttributePath(NEXT_ID); 116 } 117 118 public void createAutoPk(DataNode node, List dbEntities) throws Exception { 119 121 if (!autoPkTableExists(node)) { 123 runUpdate(node, pkTableCreateString()); 124 } 125 126 runUpdate(node, pkDeleteString(dbEntities)); 128 129 Iterator it = dbEntities.iterator(); 131 while (it.hasNext()) { 132 DbEntity ent = (DbEntity) it.next(); 133 runUpdate(node, pkCreateString(ent.getName())); 134 } 135 } 136 137 public List createAutoPkStatements(List dbEntities) { 138 List list = new ArrayList (); 139 140 list.add(pkTableCreateString()); 141 list.add(pkDeleteString(dbEntities)); 142 143 Iterator it = dbEntities.iterator(); 144 while (it.hasNext()) { 145 DbEntity ent = (DbEntity) it.next(); 146 list.add(pkCreateString(ent.getName())); 147 } 148 149 return list; 150 } 151 152 156 public void dropAutoPk(DataNode node, List dbEntities) throws Exception { 157 if (autoPkTableExists(node)) { 158 runUpdate(node, dropAutoPkString()); 159 } 160 } 161 162 public List dropAutoPkStatements(List dbEntities) { 163 List list = new ArrayList (); 164 list.add(dropAutoPkString()); 165 return list; 166 } 167 168 protected String pkTableCreateString() { 169 StringBuffer buf = new StringBuffer (); 170 buf 171 .append("CREATE TABLE AUTO_PK_SUPPORT (") 172 .append(" TABLE_NAME CHAR(100) NOT NULL,") 173 .append(" NEXT_ID INTEGER NOT NULL") 174 .append(")"); 175 176 return buf.toString(); 177 } 178 179 protected String pkDeleteString(List dbEntities) { 180 StringBuffer buf = new StringBuffer (); 181 buf.append("DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ("); 182 int len = dbEntities.size(); 183 for (int i = 0; i < len; i++) { 184 if (i > 0) { 185 buf.append(", "); 186 } 187 DbEntity ent = (DbEntity) dbEntities.get(i); 188 buf.append('\'').append(ent.getName()).append('\''); 189 } 190 buf.append(')'); 191 return buf.toString(); 192 } 193 194 protected String pkCreateString(String entName) { 195 StringBuffer buf = new StringBuffer (); 196 buf 197 .append("INSERT INTO AUTO_PK_SUPPORT") 198 .append(" (TABLE_NAME, NEXT_ID)") 199 .append(" VALUES ('") 200 .append(entName) 201 .append("', 200)"); 202 return buf.toString(); 203 } 204 205 protected String pkSelectString(String entName) { 206 StringBuffer buf = new StringBuffer (); 207 buf 208 .append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '") 209 .append(entName) 210 .append('\''); 211 return buf.toString(); 212 } 213 214 protected String pkUpdateString(String entName) { 215 StringBuffer buf = new StringBuffer (); 216 buf 217 .append("UPDATE AUTO_PK_SUPPORT") 218 .append(" SET NEXT_ID = NEXT_ID + ") 219 .append(pkCacheSize) 220 .append(" WHERE TABLE_NAME = '") 221 .append(entName) 222 .append('\''); 223 return buf.toString(); 224 } 225 226 protected String dropAutoPkString() { 227 return "DROP TABLE AUTO_PK_SUPPORT"; 228 } 229 230 233 protected boolean autoPkTableExists(DataNode node) throws SQLException { 234 Connection con = node.getDataSource().getConnection(); 235 boolean exists = false; 236 try { 237 DatabaseMetaData md = con.getMetaData(); 238 ResultSet tables = md.getTables(null, null, "AUTO_PK_SUPPORT", null); 239 try { 240 exists = tables.next(); 241 } finally { 242 tables.close(); 243 } 244 } finally { 245 con.close(); 247 } 248 249 return exists; 250 } 251 252 258 public int runUpdate(DataNode node, String sql) throws SQLException { 259 QueryLogger.logQuery(QueryLogger.DEFAULT_LOG_LEVEL, sql, Collections.EMPTY_LIST); 260 261 Connection con = node.getDataSource().getConnection(); 262 try { 263 Statement upd = con.createStatement(); 264 try { 265 return upd.executeUpdate(sql); 266 } finally { 267 upd.close(); 268 } 269 } finally { 270 con.close(); 271 } 272 } 273 274 277 public String generatePkForDbEntityString(DbEntity ent) { 278 StringBuffer buf = new StringBuffer (); 279 buf.append(pkSelectString(ent.getName())).append('\n').append( 280 pkUpdateString(ent.getName())); 281 return buf.toString(); 282 } 283 284 292 293 public Object generatePkForDbEntity(DataNode node, DbEntity ent) throws Exception { 294 295 Object binPK = binaryPK(ent); 297 if (binPK != null) { 298 return binPK; 299 } 300 301 DbKeyGenerator pkGenerator = ent.getPrimaryKeyGenerator(); 302 int cacheSize; 303 if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null) 304 cacheSize = pkGenerator.getKeyCacheSize().intValue(); 305 else 306 cacheSize = pkCacheSize; 307 308 if (cacheSize <= 1) { 310 return new Integer (pkFromDatabase(node, ent)); 311 } 312 313 synchronized (pkCache) { 314 PkRange r = (PkRange) pkCache.get(ent.getName()); 315 316 if (r == null) { 317 r = new PkRange(1, 0); 319 pkCache.put(ent.getName(), r); 320 } 321 322 if (r.isExhausted()) { 323 int val = pkFromDatabase(node, ent); 324 r.reset(val, val + cacheSize - 1); 325 } 326 327 return r.getNextPrimaryKey(); 328 } 329 } 330 331 336 protected byte[] binaryPK(DbEntity entity) { 337 List pkColumns = entity.getPrimaryKey(); 338 if (pkColumns.size() == 1) { 339 DbAttribute pk = (DbAttribute) pkColumns.get(0); 340 if (pk.getMaxLength() > 0 341 && (pk.getType() == Types.BINARY || pk.getType() == Types.VARBINARY)) { 342 return IDUtil.pseudoUniqueByteSequence(pk.getMaxLength()); 343 } 344 } 345 346 return null; 347 } 348 349 360 protected int pkFromDatabase(DataNode node, DbEntity ent) throws Exception { 361 String select = "SELECT #result('NEXT_ID' 'int' 'NEXT_ID') " 362 + "FROM AUTO_PK_SUPPORT " 363 + "WHERE TABLE_NAME = '" 364 + ent.getName() 365 + '\''; 366 367 List queries = new ArrayList (2); 369 queries.add(new SQLTemplate(ent, select, true)); 370 queries.add(new SQLTemplate(ent, pkUpdateString(ent.getName()), false)); 371 372 PkRetrieveProcessor observer = new PkRetrieveProcessor(ent.getName()); 373 node.performQueries(queries, observer); 374 return observer.getNextId(); 375 } 376 377 382 public int getPkCacheSize() { 383 return pkCacheSize; 384 } 385 386 399 public void setPkCacheSize(int pkCacheSize) { 400 this.pkCacheSize = (pkCacheSize < 1) ? 1 : pkCacheSize; 401 } 402 403 public void reset() { 404 pkCache.clear(); 405 } 406 407 408 protected class PkRetrieveProcessor extends DefaultOperationObserver { 409 protected boolean success; 410 protected Integer nextId; 411 protected String entName; 412 413 public PkRetrieveProcessor(String entName) { 414 this.entName = entName; 415 } 416 417 public int getNextId() { 418 if (nextId != null) { 419 return nextId.intValue(); 420 } else { 421 throw new CayenneRuntimeException("No key was retrieved."); 422 } 423 } 424 425 public void nextDataRows(Query query, List dataRows) { 426 super.nextDataRows(query, dataRows); 427 428 if (dataRows == null || dataRows.size() == 0) { 430 throw new CayenneRuntimeException( 431 "Error generating PK : entity not supported: " + entName); 432 } 433 if (dataRows.size() > 1) { 434 throw new CayenneRuntimeException( 435 "Error generating PK : too many rows for entity: " + entName); 436 } 437 438 Map lastPk = (Map ) dataRows.get(0); 439 nextId = (Integer ) lastPk.get("NEXT_ID"); 440 if (nextId == null) { 441 throw new CayenneRuntimeException("Error generating PK : null nextId."); 442 } 443 } 444 445 public void nextCount(Query query, int resultCount) { 446 super.nextCount(query, resultCount); 447 448 if (resultCount != 1) 449 throw new CayenneRuntimeException( 450 "Error generating PK : update count is wrong: " + resultCount); 451 } 452 453 public void nextQueryException(Query query, Exception ex) { 454 super.nextQueryException(query, ex); 455 String entityName = 456 ((query != null) && (query.getRoot() != null)) 457 ? query.getRoot().toString() 458 : null; 459 throw new CayenneRuntimeException( 460 "Error generating PK for entity '" + entityName + "'.", 461 ex); 462 } 463 464 public void nextGlobalException(Exception ex) { 465 super.nextGlobalException(ex); 466 throw new CayenneRuntimeException("Error generating PK.", ex); 467 } 468 } 469 } | Popular Tags |