001package gu.sql2java; 002 003import java.sql.Array; 004import java.sql.Blob; 005import java.sql.Clob; 006import java.sql.Connection; 007import java.sql.DatabaseMetaData; 008 009import static com.google.common.base.Preconditions.*; 010import static gu.sql2java.SimpleLog.*; 011 012import java.math.BigDecimal; 013import java.net.URL; 014import java.nio.ByteBuffer; 015import java.sql.PreparedStatement; 016import java.sql.Ref; 017import java.sql.ResultSet; 018import java.sql.SQLException; 019import java.sql.Statement; 020import java.sql.Time; 021import java.sql.Timestamp; 022import java.sql.Types; 023import java.text.ParseException; 024import java.text.SimpleDateFormat; 025import java.util.Calendar; 026import java.util.Collections; 027import java.util.EnumMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Properties; 032import java.util.StringTokenizer; 033import java.util.concurrent.Callable; 034import javax.sql.DataSource; 035 036import com.google.common.base.Function; 037import com.google.common.base.Strings; 038import com.google.common.collect.ImmutableMap; 039import com.google.common.collect.Lists; 040import com.mchange.v2.c3p0.ComboPooledDataSource; 041import com.mchange.v2.c3p0.DataSources; 042 043import gu.sql2java.exception.DaoException; 044import gu.sql2java.exception.DataAccessException; 045import gu.sql2java.exception.RuntimeDaoException; 046 047 048/** 049 * The Manager provides connections and manages transactions transparently. 050 * <br> 051 * It is a singleton, you get its instance with the getInstance() method. 052 * All of the XxxxManager classes use the Manager to get database connections. 053 * Before doing any operation, you must pass either a 054 * datasource or a jdbc driver/url/username/password. 055 * @author guyadong 056 */ 057public final class Manager implements Constant,SqlRunner 058{ 059 private static class Singleton{ 060 private static final Manager INSTANCE = new Manager(); 061 } 062 private static InheritableThreadLocal<Connection> transactionConnection = new InheritableThreadLocal<Connection>(); 063 /** JDBC properties from properties file */ 064 private static Properties databaseProperties; 065 066 static{ 067 String envVar="config_folder"; 068 String propFile="database.properties"; 069 String confFolder="conf"; 070 databaseProperties = ConfigUtils.loadAllProperties(propFile, confFolder, envVar, Manager.class, false); 071 } 072 /** 073 * inject properties to {@link #databaseProperties}<br> 074 * be effected only while called before initializing singleton instance 075 * @param properties 076 * @see JdbcProperty 077 */ 078 public static final void injectProperties(Map<String,String> properties){ 079 if(null != properties){ 080 EnumMap<JdbcProperty, String> enumMap = new EnumMap<JdbcProperty,String>(JdbcProperty.class); 081 JdbcProperty property; 082 for(Entry<String, String> entry:properties.entrySet()){ 083 if(null != (property = JdbcProperty.fromKey(entry.getKey()))){ 084 enumMap.put(property, entry.getValue()); 085 } 086 } 087 injectProperties(enumMap); 088 } 089 } 090 /** 091 * inject properties to {@link #databaseProperties}<br> 092 * be effected only while called before initializing singleton instance 093 * @param properties 094 */ 095 public static final void injectProperties(EnumMap<JdbcProperty,String> properties){ 096 if(null != properties){ 097 Boolean isDebug = false; 098 if(properties.containsKey(JdbcProperty.DEBUG)){ 099 isDebug = "true".equalsIgnoreCase(properties.get(JdbcProperty.DEBUG)); 100 databaseProperties.setProperty(JdbcProperty.DEBUG.key, isDebug.toString()); 101 } 102 String prefix=isDebug?"debug.":"work."; 103 String value; 104 JdbcProperty key; 105 for(Entry<JdbcProperty, String> entry : properties.entrySet()){ 106 value = entry.getValue(); 107 key = entry.getKey(); 108 if( key != JdbcProperty.DEBUG && null !=value && !value.isEmpty()){ 109 databaseProperties.setProperty(key.withPrefix(prefix), value); 110 } 111 } 112 } 113 } 114 private volatile DataSource dataSource = null; 115 private String jdbcDriver = null; 116 private String jdbcUrl = null; 117 private String jdbcUsername = null; 118 private String jdbcPassword = null; 119 private volatile String generatedkeyRetrieve = null; 120 private volatile String generatedkeyStatement = null; 121 private volatile AutoKeyRetrieveType retrieveType; 122 private String maxIdleTime; 123 private String idleConnectionTestPeriod; 124 private String maxPoolSize; 125 private String minPoolSize; 126 private boolean isDebug = false; 127 128 /** 129 * Returns the manager singleton instance. 130 */ 131 private Manager() 132 { 133 loadProperties(databaseProperties); 134 } 135 136 /** 137 * Returns the manager singleton instance. 138 */ 139 public static Manager getInstance() 140 { 141 return Singleton.INSTANCE; 142 } 143 144 /** dispose pool */ 145 public void disposePool(){ 146 try{ 147 DataSources.destroy(dataSource); 148 }catch (Exception e) { 149 log("dispose pool wrong ..." + e); 150 } 151 } 152 153 /** 154 * configure with the parameters given in the properties object 155 * @param properties the properties object to be used 156 */ 157 public void loadProperties(Properties properties){ 158 isDebug = Boolean.valueOf(properties.getProperty("isDebug")); 159 String prefix=isDebug ? "debug." : "work."; 160 jdbcDriver = properties.getProperty(JdbcProperty.JDBC_DRIVER.withPrefix(prefix)); 161 jdbcUrl = properties.getProperty(JdbcProperty.JDBC_URL.withPrefix(prefix)); 162 jdbcUsername = properties.getProperty(JdbcProperty.JDBC_USERNAME.withPrefix(prefix)); 163 jdbcPassword = properties.getProperty(JdbcProperty.JDBC_PASSWORD.withPrefix(prefix)); 164 165 generatedkeyRetrieve = properties.getProperty(JdbcProperty.GENERATEDKEY_RETRIEVE.name()); 166 generatedkeyStatement = properties.getProperty(JdbcProperty.GENERATEDKEY_STATEMENT.name()); 167 168 maxPoolSize = properties.getProperty(JdbcProperty.C3P0_MAXPOOLSIZE.withPrefix(prefix)); 169 minPoolSize = properties.getProperty(JdbcProperty.C3P0_MINPOOLSIZE.withPrefix(prefix)); 170 maxIdleTime = properties.getProperty(JdbcProperty.C3P0_MAXIDLETIME.withPrefix(prefix)); 171 idleConnectionTestPeriod = properties.getProperty(JdbcProperty.C3P0_IDLECONNECTIONTESTPERIOD.withPrefix(prefix)); 172 } 173 private void logDatabaseParameters(){ 174 if(isDebug){ 175 log("database using debug environment parameter: "); 176 log("jdbcUrl = " + jdbcUrl); 177 log("jdbcUsername = " + jdbcUsername); 178 log("jdbcPassword = " + jdbcPassword); 179 log("maxPoolSize = " + maxPoolSize); 180 log("minPoolSize = " + minPoolSize); 181 log("maxIdleTime = " + maxIdleTime); 182 log("idleConnectionTestPeriod = " + idleConnectionTestPeriod); 183 } 184 } 185 /** 186 * use key synchronized to be sure the ds created once 187 * @return 188 */ 189 private DataSource getDataSource(){ 190 // double check 191 if (dataSource == null){ 192 synchronized (this) { 193 if (dataSource == null){ 194 try{ 195 logDatabaseParameters(); 196 //set C3P0 properties 197 ComboPooledDataSource cpds = new ComboPooledDataSource(); 198 cpds.setDriverClass(jdbcDriver); 199 cpds.setUser(jdbcUsername); 200 cpds.setPassword(jdbcPassword); 201 cpds.setJdbcUrl(jdbcUrl); 202 cpds.setMaxPoolSize(Integer.parseInt(maxPoolSize)); 203 cpds.setMinPoolSize(Integer.parseInt(minPoolSize)); 204 cpds.setMaxIdleTime(Integer.parseInt(maxIdleTime)); 205 cpds.setIdleConnectionTestPeriod(Integer.parseInt(idleConnectionTestPeriod)); 206 dataSource = cpds; 207 }catch (Exception e){ 208 throw new IllegalArgumentException(String.format("can't get connection by argument...driver/url/username/password[%s/%s/%s/%s]",jdbcDriver,jdbcUrl,jdbcUsername,jdbcPassword),e); 209 } 210 } 211 } 212 } 213 return dataSource; 214 } 215 216 /** 217 * Gets an auto commit connection. 218 * <br> 219 * Normally you do not need this method that much ;-) 220 * 221 * @return an auto commit connection 222 */ 223 public Connection getConnection() throws SQLException 224 { 225 Connection tc = transactionConnection.get(); 226 if (tc != null) { 227 return tc; 228 } 229 return getDataSource().getConnection(); 230 } 231 232 /** 233 * Releases the database connection. 234 * <br> 235 * Normally you should not need this method ;-) 236 */ 237 public void releaseConnection(Connection c) 238 { 239 Connection tc = transactionConnection.get(); 240 if (tc != null){ 241 return; 242 } 243 try{ 244 if (c != null){ 245 c.close(); 246 } 247 }catch (SQLException x){ 248 log("Could not release the connection: "+x.toString()); 249 } 250 } 251 252 /** 253 * Initiates a database transaction. 254 * <br> 255 * When working within a transaction, you should invoke this method first. 256 * The connection is returned just in case you need to set the isolation level. 257 * 258 * @return a non-auto commit connection with the default transaction isolation level 259 */ 260 private Connection beginTransaction() throws SQLException 261 { 262 Connection c = this.getConnection(); 263 c.setAutoCommit(false); 264 transactionConnection.set(c); 265 return c; 266 } 267 268 /** 269 * Releases connection used for the transaction and performs a commit or rollback. 270 * 271 * @param commit tells whether this connection should be committed 272 * true for commit(), false for rollback() 273 */ 274 private void endTransaction(boolean commit) throws SQLException 275 { 276 Connection c = transactionConnection.get(); 277 if (c == null) 278 { 279 return; 280 } 281 282 try 283 { 284 if (commit) 285 { 286 c.commit(); 287 } 288 else 289 { 290 c.rollback(); 291 } 292 } 293 finally 294 { 295 c.setAutoCommit(true); 296 transactionConnection.set(null); 297 releaseConnection(c); 298 } 299 } 300 /** 301 * Run {@code Callable<T>} as a transaction.<br> 302 * all exceptions but {@code SQLException} threw by {@code Callable<T>} is wrapped into {@code RuntimeException}<br> 303 * throw {@code NullPointerException} if {@code fun} be {@code null}<br> 304 * @param <T> type of return result 305 * @param fun 306 * @param transactionListener listener for transaction status 307 * @return 308 * @throws RuntimeDaoException 309 */ 310 <T>T runAsTransaction(Callable<T> fun,TransactionListener transactionListener) throws RuntimeDaoException{ 311 checkArgument(fun != null, "fun is null"); 312 checkArgument(null != transactionListener,"transactionListener is null"); 313 try { 314 beginTransaction(); 315 transactionListener.onBegin(); 316 boolean commit = false; 317 try { 318 T result = fun.call(); 319 commit = true; 320 transactionListener.onCommit(); 321 return result; 322 } catch (SQLException e) { 323 throw e; 324 } catch (RuntimeException e) { 325 throw e; 326 }catch (Exception e) { 327 throw new RuntimeException(e); 328 }finally { 329 endTransaction(commit); 330 transactionListener.onEnd(); 331 } 332 } catch (SQLException e) { 333 throw new RuntimeDaoException(e); 334 } 335 } 336 337 /** 338 * Run {@code Runnable} as a transaction.no return 339 * @param fun 340 * @param transactionListener listener for transaction status 341 * @throws RuntimeDaoException 342 * @see #runAsTransaction(Callable,TableListener.TransactionListener) 343 */ 344 void runAsTransaction(final Runnable fun,TransactionListener transactionListener) throws RuntimeDaoException{ 345 checkArgument(fun != null, "fun is null"); 346 runAsTransaction(new Callable<Object>(){ 347 348 @Override 349 public Object call() throws Exception { 350 fun.run(); 351 return null; 352 }},transactionListener); 353 } 354 355 @Override 356 public <T>T runAsTransaction(Callable<T> fun) throws RuntimeDaoException{ 357 return runAsTransaction(fun, ListenerContainer.TRANSACTION_LISTENER); 358 } 359 360 @Override 361 public void runAsTransaction(Runnable fun) throws RuntimeDaoException{ 362 runAsTransaction(fun,ListenerContainer.TRANSACTION_LISTENER); 363 } 364 365//////////////////////////////////////////////////// 366// cleaning method 367//////////////////////////////////////////////////// 368 369 /** 370 * Closes the passed Statement. 371 */ 372 void close(Statement s) 373 { 374 try 375 { 376 if (s != null) { 377 s.close(); 378 } 379 } 380 catch (SQLException x) 381 { 382 log("Could not close statement!: " + x.toString()); 383 } 384 } 385 386 /** 387 * Closes the passed ResultSet. 388 */ 389 void close(ResultSet rs) 390 { 391 try 392 { 393 if (rs != null) { 394 rs.close(); 395 } 396 } 397 catch (SQLException x) 398 { 399 log("Could not close result set!: " + x.toString()); 400 } 401 } 402 403 /** 404 * Closes the passed Statement and ResultSet. 405 */ 406 void close(Statement s, ResultSet rs) 407 { 408 close(rs); 409 close(s); 410 } 411 412 //////////////////////////////////////////////////// 413 // Helper methods for fetching numbers using IDs or names 414 //////////////////////////////////////////////////// 415 416 /** 417 * return all bytes in buffer (position~limit),no change status of buffer 418 * @param buffer 419 * @return 420 */ 421 private static final byte[] getBytesInBuffer(ByteBuffer buffer){ 422 if(null == buffer){ 423 return null; 424 } 425 int pos = buffer.position(); 426 try{ 427 byte[] bytes = new byte[buffer.remaining()]; 428 buffer.get(bytes); 429 return bytes; 430 }finally{ 431 buffer.position(pos); 432 } 433 } 434 /** 435 * Set a byte array to the passed prepared statement as a ByteBuffer or as null. 436 */ 437 private static void setBytes(int sqlType,PreparedStatement ps, int pos, ByteBuffer bytes) throws SQLException 438 { 439 setBytes(sqlType,ps,pos,getBytesInBuffer(bytes)); 440 } 441 /** 442 * Set a byte array to the passed prepared statement as a ByteBuffer or as null. 443 */ 444 private static void setBytes(int sqlType,PreparedStatement ps, int pos, byte[] bytes) throws SQLException 445 { 446 if (null == bytes){ 447 ps.setNull(pos, sqlType); 448 }else{ 449 ps.setBytes(pos, bytes); 450 } 451 } 452 /** 453 * Retrieves a date value from the passed result set as a Calendar object. 454 */ 455 static Calendar getCalendar(ResultSet rs, int pos) throws SQLException 456 { 457 Timestamp date = rs.getTimestamp(pos); 458 if (rs.wasNull()) { 459 return null; 460 } 461 Calendar calendar = Calendar.getInstance(); 462 calendar.setTime(date); 463 return calendar; 464 } 465 466 /** 467 * Retrieves a date value from the passed result set as a Calendar object. 468 */ 469 static Calendar getCalendar(ResultSet rs, String column) throws SQLException 470 { 471 Timestamp date = rs.getTimestamp(column); 472 if (null == date) { 473 return null; 474 } 475 Calendar calendar = Calendar.getInstance(); 476 calendar.setTime(date); 477 return calendar; 478 } 479 480 /** 481 * Set a Calendar object to the passed prepared statement as a date or as null. 482 */ 483 static void setCalendar(PreparedStatement ps, int pos, Calendar calendar) throws SQLException 484 { 485 if (calendar == null) 486 { 487 ps.setNull(pos, Types.TIMESTAMP); 488 } 489 else 490 { 491 ps.setTimestamp(pos, new java.sql.Timestamp(calendar.getTimeInMillis())); 492 } 493 } 494 495 /** 496 * 497 * For compatibility with old version {@link ResultSet} without getObject(int,Class) method 498 * @param rs 499 * @param columnIndex 500 * @param type 501 * @return 502 * @throws SQLException 503 */ 504 @SuppressWarnings("unchecked") 505 public static <T> T getObject(ResultSet rs,int columnIndex, Class<T> type) throws SQLException { 506 if (type == null) { 507 throw new SQLException("Type parameter can not be null"); 508 } 509 Object t = null; 510 if (type.equals(String.class)) { 511 t = rs.getString(columnIndex); 512 } else if (type.equals(BigDecimal.class)) { 513 t = rs.getBigDecimal(columnIndex); 514 } else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) { 515 t = Boolean.valueOf(rs.getBoolean(columnIndex)); 516 } else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) { 517 t = Integer.valueOf(rs.getInt(columnIndex)); 518 } else if (type.equals(Long.class) || type.equals(Long.TYPE)) { 519 t = Long.valueOf(rs.getLong(columnIndex)); 520 } else if (type.equals(Float.class) || type.equals(Float.TYPE)) { 521 t = Float.valueOf(rs.getFloat(columnIndex)); 522 } else if (type.equals(Double.class) || type.equals(Double.TYPE)) { 523 t = Double.valueOf(rs.getDouble(columnIndex)); 524 } else if (type.equals(byte[].class)) { 525 t = rs.getBytes(columnIndex); 526 } else if (type.equals(ByteBuffer.class)) { 527 byte[] array = rs.getBytes(columnIndex); 528 t = (array == null ? null : ByteBuffer.wrap(array)); 529 } else if (type.equals(java.sql.Date.class)) { 530 t = rs.getDate(columnIndex); 531 } else if (type.equals(Time.class)) { 532 t = rs.getTime(columnIndex); 533 } else if (type.equals(Timestamp.class)) { 534 t = rs.getTimestamp(columnIndex); 535 } else if (type.equals(Calendar.class)) { 536 java.util.Date date = (java.util.Date) rs.getObject(columnIndex); 537 if (date == null) { 538 return null; 539 } 540 Calendar calendar = Calendar.getInstance(); 541 calendar.setTime(date); 542 t = calendar; 543 } else if (type.equals(Clob.class)) { 544 t = rs.getClob(columnIndex); 545 } else if (type.equals(Blob.class)) { 546 t = rs.getBlob(columnIndex); 547 } else if (type.equals(Array.class)) { 548 t = rs.getArray(columnIndex); 549 } else if (type.equals(Ref.class)) { 550 t = rs.getRef(columnIndex); 551 } else if (type.equals(URL.class)) { 552 t = rs.getURL(columnIndex); 553 } else { 554 try { 555 t = type.cast(rs.getObject(columnIndex)); 556 } catch (ClassCastException e) { 557 throw new SQLException("Conversion not supported for type " + type.getName(), e); 558 } 559 } 560 return rs.wasNull() ? null : (T)t; 561 } 562 563//////////////////////////////////////////////////// 564// Date helper methods 565//////////////////////////////////////////////////// 566 567 /** 568 * pattern for received date processing. 569 */ 570 private static final String[] PATTERNS = new String[] 571 { 572 "EEE, dd MMM yyyy HH:mm:ss '-'S '('z')'", 573 "EEE, dd MMM yyyy HH:mm:ss '+'S '('z')'", 574 "EEE, dd MMM yyyy HH:mm:ss '-'S", 575 "EEE, dd MMM yyyy HH:mm:ss '+'S", 576 "EEE, dd MMM yyyy HH:mm:ss z", 577 "EEE, dd MMM yyyy HH:mm:ss Z", 578 "EEE, dd MMM yyyy HH:mm:ss", 579 "EEE, d MMM yyyy HH:mm:ss '-'S '('z')'", 580 "EEE, d MMM yyyy HH:mm:ss '+'S '('z')'", 581 "EEE, d MMM yyyy HH:mm:ss '-'S", 582 "EEE, d MMM yyyy HH:mm:ss '+'S", 583 "EEE, d MMM yyyy HH:mm:ss z", 584 "EEE, d MMM yyyy HH:mm:ss Z", 585 "EEE, d MMM yyyy HH:mm:ss", 586 587 "EEE, dd MMM yy HH:mm:ss '-'S '('z')'", 588 "EEE, dd MMM yy HH:mm:ss '+'S '('z')'", 589 "EEE, dd MMM yy HH:mm:ss '-'S", 590 "EEE, dd MMM yy HH:mm:ss '+'S", 591 "EEE, dd MMM yy HH:mm:ss z", 592 "EEE, dd MMM yy HH:mm:ss Z", 593 "EEE, dd MMM yy HH:mm:ss", 594 "EEE, d MMM yy HH:mm:ss '-'S '('z')'", 595 "EEE, d MMM yy HH:mm:ss '+'S '('z')'", 596 "EEE, d MMM yy HH:mm:ss '-'S", 597 "EEE, d MMM yy HH:mm:ss '+'S", 598 "EEE, d MMM yy HH:mm:ss z", 599 "EEE, d MMM yy HH:mm:ss Z", 600 "EEE, d MMM yy HH:mm:ss", 601 602 "dd MMM yyyy HH:mm:ss '-'S", 603 "dd MMM yyyy HH:mm:ss '+'S", 604 "dd MMM yyyy HH:mm:ss '-'S '('z')'", 605 "dd MMM yyyy HH:mm:ss '+'S '('z')'", 606 "dd MMM yyyy HH:mm:ss z", 607 "dd MMM yyyy HH:mm:ss Z", 608 "dd MMM yyyy HH:mm:ss", 609 610 "dd MMM yyy HH:mm:ss '-'S", 611 "dd MMM yyy HH:mm:ss '+'S", 612 "dd MMM yyy HH:mm:ss '-'S '('z')'", 613 "dd MMM yyy HH:mm:ss '+'S '('z')'", 614 "dd MMM yyy HH:mm:ss z", 615 "dd MMM yyy HH:mm:ss Z", 616 "dd MMM yyy HH:mm:ss", 617 618 "yyyy.MM.dd HH:mm:ss z", 619 "yyyy.MM.dd HH:mm:ss Z", 620 "yyyy.MM.d HH:mm:ss z", 621 "yyyy.MM.d HH:mm:ss Z", 622 "yyyy.MM.dd HH:mm:ss", 623 "yyyy.MM.d HH:mm:ss", 624 625 "yy.MM.dd HH:mm:ss z", 626 "yy.MM.dd HH:mm:ss Z", 627 "yy.MM.d HH:mm:ss z", 628 "yy.MM.d HH:mm:ss Z", 629 "yy.MM.dd HH:mm:ss", 630 "yy.MM.d HH:mm:ss", 631 632 "yyyy MM dd HH:mm:ss", 633 "yyyy MM d HH:mm:ss", 634 "yyyy MM dd HH:mm:ss z", 635 "yyyy MM dd HH:mm:ss Z", 636 "yyyy MM d HH:mm:ss z", 637 "yyyy MM d HH:mm:ss Z", 638 639 "yy MM dd HH:mm:ss", 640 "yy MM d HH:mm:ss", 641 "yy MM dd HH:mm:ss z", 642 "yy MM dd HH:mm:ss Z", 643 "yy MM d HH:mm:ss z", 644 "yy MM d HH:mm:ss Z", 645 646 "yyyy-MM-dd HH:mm:ss z", 647 "yyyy-MM-dd HH:mm:ss Z", 648 "yyyy-MM-d HH:mm:ss z", 649 "yyyy-MM-d HH:mm:ss Z", 650 "yyyy-MM-dd HH:mm:ss", 651 "yyyy-MM-d HH:mm:ss", 652 653 "yy-MM-dd HH:mm:ss z", 654 "yy-MM-dd HH:mm:ss Z", 655 "yy-MM-d HH:mm:ss z", 656 "yy-MM-d HH:mm:ss Z", 657 "yy-MM-dd HH:mm:ss", 658 "yy-MM-d HH:mm:ss", 659 660 "dd MMM yyyy", 661 "d MMM yyyy", 662 663 "dd.MMM.yyyy", 664 "d.MMM.yyyy", 665 666 "dd-MMM-yyyy", 667 "d-MMM-yyyy", 668 669 "dd MM yyyy", 670 "d MM yyyy", 671 672 "dd.MM.yyyy", 673 "d.MM.yyyy", 674 675 "dd-MM-yyyy", 676 "d-MM-yyyy", 677 678 "yyyy MM dd", 679 "yyyy MM d", 680 681 "yyyy.MM.dd", 682 "yyyy.MM.d", 683 684 "yyyy-MM-dd", 685 "yyyy-MM-d", 686 687 "dd MMM yy", 688 "d MMM yy", 689 690 "dd.MMM.yy", 691 "d.MMM.yy", 692 693 "dd-MMM-yy", 694 "d-MMM-yy", 695 696 "dd MM yy", 697 "d MM yy", 698 699 "dd.MM.yy", 700 "d.MM.yy", 701 702 "dd-MM-yy", 703 "d-MM-yy", 704 705 "yy MMM dd", 706 "yy MMM d", 707 708 "yy.MMM.dd", 709 "yy.MMM.d", 710 711 "yy-MMM-dd", 712 "yy-MMM-d", 713 714 "yy MMM dd", 715 "yy MMM d", 716 717 "yy.MMM.dd", 718 "yy.MMM.d", 719 720 "yy-MMM-dd", 721 "yy-MMM-d", 722 // ex: Wed 19, Feb 2003 723 "EEE dd, MMM yyyy", 724 // ex: Wed 19, Feb 03 725 "EEE dd, MMM yy" 726 }; 727 728 729 /** 730 * get a date from a date string representation in one of the registered formats 731 * @param strDate the date as string. 732 * @return Date object ,otherwise null If (null or empty) or correct pattern was not found 733 */ 734 static java.util.Date getDateFromString(String strDate) 735 { 736 java.util.Date dReceivedDate = null; 737 if (strDate == null) { 738 return dReceivedDate; 739 } else { 740 strDate = strDate.trim(); 741 } 742 743 SimpleDateFormat pSimpleDateFormat = new SimpleDateFormat(""); 744 if (!strDate.isEmpty()) 745 { 746 for (int i=0; i<PATTERNS.length; i++) 747 { 748 try 749 { 750 pSimpleDateFormat.applyPattern(PATTERNS[i]); 751 dReceivedDate = pSimpleDateFormat.parse(strDate); 752 if (dReceivedDate == null) 753 { 754 continue; 755 } 756 return dReceivedDate; 757 } 758 catch (ParseException pe) 759 { 760 ; // ignore this format try the next one 761 } 762 } 763 } 764 return dReceivedDate; 765 } 766 767 /** 768 * Verify that the string represantes the date with one of the registered formats 769 * @param strDate the date as string. 770 * @return boolean "true" if the string represantes the date in one of the registed formats. 771 */ 772 static boolean isDate(String strDate) 773 { 774 return null != getDateFromString(strDate); 775 } 776 777 static String buildProcedureCall(String packageName, String procedureName, int paramCount) { 778 return buildProcedureCall(packageName + "." + procedureName, paramCount); 779 } 780 781 static String buildProcedureCall(String procedureName, int paramCount) { 782 StringBuilder sb = new StringBuilder("{call ").append(procedureName).append("("); 783 for (int n = 1; n <= paramCount; n++) { 784 sb.append("?,"); 785 } 786 if (paramCount > 0) { 787 sb.setLength(sb.length()-1); 788 } 789 return sb.append(")}").toString(); 790 } 791 List<BaseBean> runPreparedStatementAsList(Map<String, Class<?>> targetTypes, 792 PreparedStatement ps){ 793 checkArgument(null !=ps,"ps is null"); 794 ResultSet rs = null; 795 try { 796 ps.setFetchSize(100); 797 rs = ps.executeQuery(); 798 UnnameRowMetaData metaData = new UnnameRowMetaData(rs.getMetaData(), targetTypes); 799 List<BaseBean> list = Lists.newLinkedList(); 800 while(rs.next()){ 801 BaseBean row = new UnnameRow(metaData); 802 for(int i=0; i<metaData.defaultColumnIdList.length; ++i){ 803 row.setValue(i, rs.getObject(i+1)); 804 } 805 list.add(row); 806 } 807 return list; 808 } catch (DaoException e) { 809 throw new RuntimeDaoException(e); 810 }catch (SQLException e) { 811 throw new RuntimeDaoException(new DataAccessException(e)); 812 } finally { 813 this.close(rs); 814 } 815 } 816 List<BaseBean> runSqlAsList(Map<String, Class<?>> targetTypes, String sql, Object... argList){ 817 checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty"); 818 PreparedStatement ps = null; 819 Connection connection = null; 820 try { 821 connection = this.getConnection(); 822 if(isDebug){ 823 log("runSqlAsList:" + sql); 824 } 825 ps = connection.prepareStatement(sql, 826 ResultSet.TYPE_FORWARD_ONLY, 827 ResultSet.CONCUR_READ_ONLY); 828 fillPrepareStatement(ps, argList); 829 return runPreparedStatementAsList(targetTypes,ps); 830 } catch (DaoException e) { 831 throw new RuntimeDaoException(e); 832 }catch (SQLException e) { 833 throw new RuntimeDaoException(new DataAccessException(e)); 834 } finally { 835 this.close(ps); 836 this.releaseConnection(connection); 837 } 838 } 839 840 private static Function<BaseBean,Map<String,Object>> VMAP_FUN = new Function<BaseBean,Map<String,Object>>(){ 841 842 @Override 843 public Map<String, Object> apply(BaseBean input) { 844 return input.asNameValueMap(); 845 }}; 846 847 @Override 848 public List<Map<String, Object>> runSqlForMap(Map<String,Class<?>> targetType, String sql,Object... argList) throws RuntimeDaoException{ 849 List<BaseBean> list = runSqlAsList(targetType, sql, argList); 850 if(list.isEmpty()){ 851 return Collections.emptyList(); 852 } 853 return Lists.transform(list, VMAP_FUN); 854 } 855 856 private <T> List<T> fetchOnlyOneColumn(List<BaseBean> list,final Class<T> targetType){ 857 if(list.isEmpty()){ 858 return Collections.emptyList(); 859 } 860 BaseBean row = list.get(0); 861 Object[] values = row.asValueArray(); 862 863 checkArgument(values.length == 1,"more than 1 column returned"); 864 return Lists.transform(list, new Function<BaseBean, T>() { 865 @Override 866 public T apply(BaseBean input) { 867 if(null == targetType){ 868 return input.getValue(0); 869 } 870 return targetType.cast(input.getValue(0)); 871 } 872 }); 873 } 874 <T> List<T> runPreparedStatementAsList(Class<T> targetType, PreparedStatement ps) throws RuntimeDaoException{ 875 List<BaseBean> list = runPreparedStatementAsList((Map<String,Class<?>>)null, ps); 876 return fetchOnlyOneColumn(list,targetType); 877 } 878 879 @Override 880 public List<BaseBean> runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{ 881 return runSqlAsList(Collections.<String,Class<?>>emptyMap(), sql, argList); 882 } 883 @Override 884 public <T> List<T> runSqlAsList(Class<T> targetType, String sql, Object... argList) throws RuntimeDaoException{ 885 List<BaseBean> list = runSqlAsList((Map<String,Class<?>>)null, sql, argList); 886 return fetchOnlyOneColumn(list,targetType); 887 } 888 889 @Override 890 public <T> T runSqlForValue(Class<T> targetType,String sql, Object... argList) throws RuntimeDaoException{ 891 List<T> list = runSqlAsList(targetType,sql,argList); 892 checkArgument(list.size() == 1,"more than 1 row returned for SQL: %s",sql); 893 return list.get(0); 894 } 895 <T> T runPreparedStatementForValue(Class<T> targetType,PreparedStatement ps) throws RuntimeDaoException{ 896 List<T> list = runPreparedStatementAsList(targetType,ps); 897 checkArgument(list.size() == 1,"more than 1 row returned"); 898 return list.get(0); 899 } 900 /** 901 * 填充PreparedStatement中的参数对象 902 * 903 * @param ps 904 * @param argList 905 * @throws SQLException 906 */ 907 protected static void fillPrepareStatement(PreparedStatement ps, Object[] argList) throws DaoException{ 908 try { 909 if (!(argList == null || ps == null)) { 910 for (int i = 0; i < argList.length; i++) { 911 if (argList[i] instanceof ByteBuffer) { 912 ps.setBytes(i + 1, getBytesInBuffer((ByteBuffer) argList[i])); 913 } else { 914 ps.setObject(i + 1, argList[i]); 915 } 916 } 917 } 918 } catch (SQLException e) { 919 throw new DaoException(e); 920 } 921 } 922 protected static void setPreparedStatement(PreparedStatement ps,int pos,Object value,int sqlType) 923 throws SQLException { 924 if(value instanceof ByteBuffer){ 925 setBytes(sqlType, ps, pos, (ByteBuffer)value); 926 }else{ 927 ps.setObject(pos,value,sqlType); 928 } 929 } 930 931 932 /** 933 * For those who do not want to read below, please simply pick up the 934 * configuration associated with your database. 935 * 936 * More explanation: 937 * When you save a bean whose primary key is numeric and has no value set, 938 * we assume that you want sql2java to retrieve a key's value generated 939 * on the database side. 940 * 941 * generatedkey.retrieve can take 3 values: 942 * 943 * auto - the standard approach when you have a JDBC 3.0 driver. 944 * 945 * before - the key's value is retrieved before inserting the record. 946 * 947 * after - the key's value is retrieved after inserting the record 948 * @author guyadong 949 * 950 */ 951 enum AutoKeyRetrieveType{ 952 auto,after,before 953 } 954 /** 955 * 956 * For those who do not want to read below, please simply pick up the 957 * configuration associated with your database. 958 * 959 * More explanation: 960 * When you save a bean whose primary key is numeric and has no value set, 961 * we assume that you want sql2java to retrieve a key's value generated 962 * on the database side. 963 * 964 * If you set it to before or after you also need to configure the 965 * autogeneratedkey.statement properties. 966 * <TABLE> is replaced at code generation time by the table name. 967 * <KEY> is replaced at code generation time by the column name. 968 * You may adjust this properties to fit your own naming convention. 969 * 970 * PICK THE CONFIGURATION ASSOCIATED WITH YOUR DATABASE 971 * (or create one, but in that case let us know so we can add it here... :-) 972 */ 973 private static final ImmutableMap<String, String> AUTOINC_MECHANISM = ImmutableMap.<String, String>builder() 974 /*-- HSQL ------*/ 975 .put("hsql.generatedkey.retrieve","after") 976 .put("hsql.generatedkey.statement","CALL IDENTITY()") 977 /*-- ORACLE 8i ------*/ 978 .put("oracle.generatedkey.retrieve","before") 979 .put("oracle.generatedkey.statement","SELECT SEQ_<TABLE>.nextval FROM DUAL") 980 /*-- MaxDB (SapDB) ------*/ 981 .put("maxdb.generatedkey.retrieve","before") 982 .put("maxdb.generatedkey.statement","SELECT SEQ_<TABLE>_<KEY>.nextval FROM DUAL") 983 /*-- MYSQL (without jdbc 3.0 driver) ------*/ 984 .put("mysql.generatedkey.retrieve","after") 985 .put("mysql.generatedkey.statement","SELECT last_insert_id()") 986 /*-- SQL SERVER ------*/ 987 .put("sqlserver.generatedkey.retrieve","after") 988 .put("sqlserver.generatedkey.statement","SELECT @@IDENTITY") 989 /*-- SYBASE ------*/ 990 .put("sybase.generatedkey.retrieve","after") 991 .put("sybase.generatedkey.statement","SELECT @@IDENTITY") 992 /*-- POSTGRESQL ------*/ 993 .put("postgresql.generatedkey.retrieve","before") 994 .put("postgresql.generatedkey.statement","SELECT nextval('<TABLE>_<KEY>_SEQ')") 995 /*-- INFORMIX ------*/ 996 .put("informix.generatedkey.retrieve","after") 997 .put("informix.generatedkey.statement","SELECT dbinfo('sqlca.sqlerrd1') FROM systables WHERE tabid=1") 998 /*-- FIREBIRD ------ 999 - need to create "sequence" number table first "create generator SEQ_<TABLE>" 1000 - the 1 is how much to increment the sequence*/ 1001 .put("firebird.generatedkey.retrieve","before") 1002 .put("firebird.generatedkey.statement","SELECT GEN_ID(SEQ_<TABLE>, 1) FROM RDB$DATABASE") 1003 /*-- DERBY ------*/ 1004 .put("derby.generatedkey.retrieve","after") 1005 .put("derby.generatedkey.statement","VALUES IDENTITY_VAL_LOCAL()") 1006 .build(); 1007 1008 private static final String getMechanismPropertyWithSuffix(String engine,String suffix) { 1009 return checkNotNull(AUTOINC_MECHANISM.get(engine + "." + suffix), "NOT FOUND %s property for %s database",suffix,engine); 1010 } 1011 1012 private synchronized void autokeyMechanismInit(Connection connection) throws SQLException{ 1013 DatabaseMetaData meta = checkNotNull(connection,"connection is null").getMetaData(); 1014 if(meta.supportsGetGeneratedKeys()){ 1015 generatedkeyRetrieve = "auto"; 1016 generatedkeyStatement = null; 1017 }else{ 1018 String engine = new StringTokenizer(meta.getDatabaseProductName()).nextToken().toLowerCase(); 1019 generatedkeyRetrieve = getMechanismPropertyWithSuffix(engine,RETRIEVE_SUFFIX).toLowerCase(); 1020 generatedkeyStatement = getMechanismPropertyWithSuffix(engine,RETRIEVE_SUFFIX); 1021 } 1022 } 1023 1024 String getGeneratedkeyRetrieve(Connection c){ 1025 // double check 1026 if(generatedkeyRetrieve == null){ 1027 synchronized (this) { 1028 if(generatedkeyRetrieve == null){ 1029 try { 1030 autokeyMechanismInit(c); 1031 } catch (SQLException e) { 1032 throw new RuntimeException(e); 1033 } 1034 } 1035 } 1036 } 1037 return generatedkeyRetrieve; 1038 } 1039 1040 AutoKeyRetrieveType getGeneratedkeyRetrieveType(Connection c){ 1041 if(retrieveType == null){ 1042 synchronized (this) { 1043 if(retrieveType == null){ 1044 retrieveType = AutoKeyRetrieveType.valueOf(getGeneratedkeyRetrieve(c)); 1045 } 1046 } 1047 } 1048 return retrieveType; 1049 } 1050 String getGeneratedkeyStatement(Connection c){ 1051 // double check 1052 if(generatedkeyStatement == null && !AutoKeyRetrieveType.auto.equals(retrieveType)){ 1053 synchronized (this) { 1054 if(generatedkeyStatement == null && !AutoKeyRetrieveType.auto.equals(retrieveType)){ 1055 try { 1056 autokeyMechanismInit(c); 1057 } catch (SQLException e) { 1058 throw new RuntimeException(e); 1059 } 1060 } 1061 } 1062 } 1063 return generatedkeyStatement; 1064 } 1065 1066 public void setDebug(boolean isDebug) { 1067 this.isDebug = isDebug; 1068 } 1069 1070}