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