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 public 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 public 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 * @see #beginTransaction() 310 * @see #endTransaction(boolean) 311 */ 312 <T>T runAsTransaction(Callable<T> fun,TransactionListener transactionListener) throws RuntimeDaoException{ 313 checkArgument(fun != null, "fun is null"); 314 checkArgument(null != transactionListener,"transactionListener is null"); 315 try { 316 beginTransaction(); 317 transactionListener.onBegin(); 318 boolean commit = false; 319 try { 320 T result = fun.call(); 321 commit = true; 322 transactionListener.onCommit(); 323 return result; 324 } catch (SQLException e) { 325 throw e; 326 } catch (RuntimeException e) { 327 throw e; 328 }catch (Exception e) { 329 throw new RuntimeException(e); 330 }finally { 331 endTransaction(commit); 332 transactionListener.onEnd(); 333 } 334 } catch (SQLException e) { 335 throw new RuntimeDaoException(e); 336 } 337 } 338 339 /** 340 * Run {@code Runnable} as a transaction.no return 341 * @param fun 342 * @param transactionListener listener for transaction status 343 * @throws RuntimeDaoException 344 * @see #runAsTransaction(Callable,TableListener.TransactionListener) 345 */ 346 void runAsTransaction(final Runnable fun,TransactionListener transactionListener) throws RuntimeDaoException{ 347 checkArgument(fun != null, "fun is null"); 348 runAsTransaction(new Callable<Object>(){ 349 350 @Override 351 public Object call() throws Exception { 352 fun.run(); 353 return null; 354 }},transactionListener); 355 } 356 /** 357 * @param fun 358 * @return 359 * @throws RuntimeDaoException 360 */ 361 @Override 362 public <T>T runAsTransaction(Callable<T> fun) throws RuntimeDaoException{ 363 return runAsTransaction(fun, ListenerContainer.TRANSACTION_LISTENER); 364 } 365 /** 366 * Run {@code Runnable} as a transaction.no return 367 * @param fun 368 * @throws DaoException 369 */ 370 @Override 371 public void runAsTransaction(final Runnable fun) throws RuntimeDaoException{ 372 runAsTransaction(fun,ListenerContainer.TRANSACTION_LISTENER); 373 } 374 375//////////////////////////////////////////////////// 376// cleaning method 377//////////////////////////////////////////////////// 378 379 /** 380 * Closes the passed Statement. 381 */ 382 void close(Statement s) 383 { 384 try 385 { 386 if (s != null) { 387 s.close(); 388 } 389 } 390 catch (SQLException x) 391 { 392 log("Could not close statement!: " + x.toString()); 393 } 394 } 395 396 /** 397 * Closes the passed ResultSet. 398 */ 399 void close(ResultSet rs) 400 { 401 try 402 { 403 if (rs != null) { 404 rs.close(); 405 } 406 } 407 catch (SQLException x) 408 { 409 log("Could not close result set!: " + x.toString()); 410 } 411 } 412 413 /** 414 * Closes the passed Statement and ResultSet. 415 */ 416 void close(Statement s, ResultSet rs) 417 { 418 close(rs); 419 close(s); 420 } 421 422 //////////////////////////////////////////////////// 423 // Helper methods for fetching numbers using IDs or names 424 //////////////////////////////////////////////////// 425 426 /** 427 * return all bytes in buffer (position~limit),no change status of buffer 428 * @param buffer 429 * @return 430 */ 431 private static final byte[] getBytesInBuffer(ByteBuffer buffer){ 432 if(null == buffer){ 433 return null; 434 } 435 int pos = buffer.position(); 436 try{ 437 byte[] bytes = new byte[buffer.remaining()]; 438 buffer.get(bytes); 439 return bytes; 440 }finally{ 441 buffer.position(pos); 442 } 443 } 444 /** 445 * Set a byte array to the passed prepared statement as a ByteBuffer or as null. 446 */ 447 private static void setBytes(int sqlType,PreparedStatement ps, int pos, ByteBuffer bytes) throws SQLException 448 { 449 setBytes(sqlType,ps,pos,getBytesInBuffer(bytes)); 450 } 451 /** 452 * Set a byte array to the passed prepared statement as a ByteBuffer or as null. 453 */ 454 private static void setBytes(int sqlType,PreparedStatement ps, int pos, byte[] bytes) throws SQLException 455 { 456 if (null == bytes){ 457 ps.setNull(pos, sqlType); 458 }else{ 459 ps.setBytes(pos, bytes); 460 } 461 } 462 /** 463 * Retrieves a date value from the passed result set as a Calendar object. 464 */ 465 static Calendar getCalendar(ResultSet rs, int pos) throws SQLException 466 { 467 Timestamp date = rs.getTimestamp(pos); 468 if (rs.wasNull()) { 469 return null; 470 } 471 Calendar calendar = Calendar.getInstance(); 472 calendar.setTime(date); 473 return calendar; 474 } 475 476 /** 477 * Retrieves a date value from the passed result set as a Calendar object. 478 */ 479 static Calendar getCalendar(ResultSet rs, String column) throws SQLException 480 { 481 Timestamp date = rs.getTimestamp(column); 482 if (null == date) { 483 return null; 484 } 485 Calendar calendar = Calendar.getInstance(); 486 calendar.setTime(date); 487 return calendar; 488 } 489 490 /** 491 * Set a Calendar object to the passed prepared statement as a date or as null. 492 */ 493 static void setCalendar(PreparedStatement ps, int pos, Calendar calendar) throws SQLException 494 { 495 if (calendar == null) 496 { 497 ps.setNull(pos, Types.TIMESTAMP); 498 } 499 else 500 { 501 ps.setTimestamp(pos, new java.sql.Timestamp(calendar.getTimeInMillis())); 502 } 503 } 504 505 /** 506 * 507 * For compatibility with old version {@link ResultSet} without getObject(int,Class) method 508 * @param rs 509 * @param columnIndex 510 * @param type 511 * @return 512 * @throws SQLException 513 */ 514 @SuppressWarnings("unchecked") 515 public static <T> T getObject(ResultSet rs,int columnIndex, Class<T> type) throws SQLException { 516 if (type == null) { 517 throw new SQLException("Type parameter can not be null"); 518 } 519 Object t = null; 520 if (type.equals(String.class)) { 521 t = rs.getString(columnIndex); 522 } else if (type.equals(BigDecimal.class)) { 523 t = rs.getBigDecimal(columnIndex); 524 } else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) { 525 t = Boolean.valueOf(rs.getBoolean(columnIndex)); 526 } else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) { 527 t = Integer.valueOf(rs.getInt(columnIndex)); 528 } else if (type.equals(Long.class) || type.equals(Long.TYPE)) { 529 t = Long.valueOf(rs.getLong(columnIndex)); 530 } else if (type.equals(Float.class) || type.equals(Float.TYPE)) { 531 t = Float.valueOf(rs.getFloat(columnIndex)); 532 } else if (type.equals(Double.class) || type.equals(Double.TYPE)) { 533 t = Double.valueOf(rs.getDouble(columnIndex)); 534 } else if (type.equals(byte[].class)) { 535 t = rs.getBytes(columnIndex); 536 } else if (type.equals(ByteBuffer.class)) { 537 byte[] array = rs.getBytes(columnIndex); 538 t = (array == null ? null : ByteBuffer.wrap(array)); 539 } else if (type.equals(java.sql.Date.class)) { 540 t = rs.getDate(columnIndex); 541 } else if (type.equals(Time.class)) { 542 t = rs.getTime(columnIndex); 543 } else if (type.equals(Timestamp.class)) { 544 t = rs.getTimestamp(columnIndex); 545 } else if (type.equals(Calendar.class)) { 546 java.util.Date date = (java.util.Date) rs.getObject(columnIndex); 547 if (date == null) { 548 return null; 549 } 550 Calendar calendar = Calendar.getInstance(); 551 calendar.setTime(date); 552 t = calendar; 553 } else if (type.equals(Clob.class)) { 554 t = rs.getClob(columnIndex); 555 } else if (type.equals(Blob.class)) { 556 t = rs.getBlob(columnIndex); 557 } else if (type.equals(Array.class)) { 558 t = rs.getArray(columnIndex); 559 } else if (type.equals(Ref.class)) { 560 t = rs.getRef(columnIndex); 561 } else if (type.equals(URL.class)) { 562 t = rs.getURL(columnIndex); 563 } else { 564 try { 565 t = type.cast(rs.getObject(columnIndex)); 566 } catch (ClassCastException e) { 567 throw new SQLException("Conversion not supported for type " + type.getName(), e); 568 } 569 } 570 return rs.wasNull() ? null : (T)t; 571 } 572 573//////////////////////////////////////////////////// 574// Date helper methods 575//////////////////////////////////////////////////// 576 577 /** 578 * pattern for received date processing. 579 */ 580 private static final String[] PATTERNS = new String[] 581 { 582 "EEE, dd MMM yyyy HH:mm:ss '-'S '('z')'", 583 "EEE, dd MMM yyyy HH:mm:ss '+'S '('z')'", 584 "EEE, dd MMM yyyy HH:mm:ss '-'S", 585 "EEE, dd MMM yyyy HH:mm:ss '+'S", 586 "EEE, dd MMM yyyy HH:mm:ss z", 587 "EEE, dd MMM yyyy HH:mm:ss Z", 588 "EEE, dd MMM yyyy HH:mm:ss", 589 "EEE, d MMM yyyy HH:mm:ss '-'S '('z')'", 590 "EEE, d MMM yyyy HH:mm:ss '+'S '('z')'", 591 "EEE, d MMM yyyy HH:mm:ss '-'S", 592 "EEE, d MMM yyyy HH:mm:ss '+'S", 593 "EEE, d MMM yyyy HH:mm:ss z", 594 "EEE, d MMM yyyy HH:mm:ss Z", 595 "EEE, d MMM yyyy HH:mm:ss", 596 597 "EEE, dd MMM yy HH:mm:ss '-'S '('z')'", 598 "EEE, dd MMM yy HH:mm:ss '+'S '('z')'", 599 "EEE, dd MMM yy HH:mm:ss '-'S", 600 "EEE, dd MMM yy HH:mm:ss '+'S", 601 "EEE, dd MMM yy HH:mm:ss z", 602 "EEE, dd MMM yy HH:mm:ss Z", 603 "EEE, dd MMM yy HH:mm:ss", 604 "EEE, d MMM yy HH:mm:ss '-'S '('z')'", 605 "EEE, d MMM yy HH:mm:ss '+'S '('z')'", 606 "EEE, d MMM yy HH:mm:ss '-'S", 607 "EEE, d MMM yy HH:mm:ss '+'S", 608 "EEE, d MMM yy HH:mm:ss z", 609 "EEE, d MMM yy HH:mm:ss Z", 610 "EEE, d MMM yy HH:mm:ss", 611 612 "dd MMM yyyy HH:mm:ss '-'S", 613 "dd MMM yyyy HH:mm:ss '+'S", 614 "dd MMM yyyy HH:mm:ss '-'S '('z')'", 615 "dd MMM yyyy HH:mm:ss '+'S '('z')'", 616 "dd MMM yyyy HH:mm:ss z", 617 "dd MMM yyyy HH:mm:ss Z", 618 "dd MMM yyyy HH:mm:ss", 619 620 "dd MMM yyy HH:mm:ss '-'S", 621 "dd MMM yyy HH:mm:ss '+'S", 622 "dd MMM yyy HH:mm:ss '-'S '('z')'", 623 "dd MMM yyy HH:mm:ss '+'S '('z')'", 624 "dd MMM yyy HH:mm:ss z", 625 "dd MMM yyy HH:mm:ss Z", 626 "dd MMM yyy HH:mm:ss", 627 628 "yyyy.MM.dd HH:mm:ss z", 629 "yyyy.MM.dd HH:mm:ss Z", 630 "yyyy.MM.d HH:mm:ss z", 631 "yyyy.MM.d HH:mm:ss Z", 632 "yyyy.MM.dd HH:mm:ss", 633 "yyyy.MM.d HH:mm:ss", 634 635 "yy.MM.dd HH:mm:ss z", 636 "yy.MM.dd HH:mm:ss Z", 637 "yy.MM.d HH:mm:ss z", 638 "yy.MM.d HH:mm:ss Z", 639 "yy.MM.dd HH:mm:ss", 640 "yy.MM.d HH:mm:ss", 641 642 "yyyy MM dd HH:mm:ss", 643 "yyyy MM d HH:mm:ss", 644 "yyyy MM dd HH:mm:ss z", 645 "yyyy MM dd HH:mm:ss Z", 646 "yyyy MM d HH:mm:ss z", 647 "yyyy MM d HH:mm:ss Z", 648 649 "yy MM dd HH:mm:ss", 650 "yy MM d HH:mm:ss", 651 "yy MM dd HH:mm:ss z", 652 "yy MM dd HH:mm:ss Z", 653 "yy MM d HH:mm:ss z", 654 "yy MM d HH:mm:ss Z", 655 656 "yyyy-MM-dd HH:mm:ss z", 657 "yyyy-MM-dd HH:mm:ss Z", 658 "yyyy-MM-d HH:mm:ss z", 659 "yyyy-MM-d HH:mm:ss Z", 660 "yyyy-MM-dd HH:mm:ss", 661 "yyyy-MM-d HH:mm:ss", 662 663 "yy-MM-dd HH:mm:ss z", 664 "yy-MM-dd HH:mm:ss Z", 665 "yy-MM-d HH:mm:ss z", 666 "yy-MM-d HH:mm:ss Z", 667 "yy-MM-dd HH:mm:ss", 668 "yy-MM-d HH:mm:ss", 669 670 "dd MMM yyyy", 671 "d MMM yyyy", 672 673 "dd.MMM.yyyy", 674 "d.MMM.yyyy", 675 676 "dd-MMM-yyyy", 677 "d-MMM-yyyy", 678 679 "dd MM yyyy", 680 "d MM yyyy", 681 682 "dd.MM.yyyy", 683 "d.MM.yyyy", 684 685 "dd-MM-yyyy", 686 "d-MM-yyyy", 687 688 "yyyy MM dd", 689 "yyyy MM d", 690 691 "yyyy.MM.dd", 692 "yyyy.MM.d", 693 694 "yyyy-MM-dd", 695 "yyyy-MM-d", 696 697 "dd MMM yy", 698 "d MMM yy", 699 700 "dd.MMM.yy", 701 "d.MMM.yy", 702 703 "dd-MMM-yy", 704 "d-MMM-yy", 705 706 "dd MM yy", 707 "d MM yy", 708 709 "dd.MM.yy", 710 "d.MM.yy", 711 712 "dd-MM-yy", 713 "d-MM-yy", 714 715 "yy MMM dd", 716 "yy MMM d", 717 718 "yy.MMM.dd", 719 "yy.MMM.d", 720 721 "yy-MMM-dd", 722 "yy-MMM-d", 723 724 "yy MMM dd", 725 "yy MMM d", 726 727 "yy.MMM.dd", 728 "yy.MMM.d", 729 730 "yy-MMM-dd", 731 "yy-MMM-d", 732 // ex: Wed 19, Feb 2003 733 "EEE dd, MMM yyyy", 734 // ex: Wed 19, Feb 03 735 "EEE dd, MMM yy" 736 }; 737 738 739 /** 740 * get a date from a date string representation in one of the registered formats 741 * @param strDate the date as string. 742 * @return Date object ,otherwise null If (null or empty) or correct pattern was not found 743 */ 744 static java.util.Date getDateFromString(String strDate) 745 { 746 java.util.Date dReceivedDate = null; 747 if (strDate == null) { 748 return dReceivedDate; 749 } else { 750 strDate = strDate.trim(); 751 } 752 753 SimpleDateFormat pSimpleDateFormat = new SimpleDateFormat(""); 754 if (!strDate.isEmpty()) 755 { 756 for (int i=0; i<PATTERNS.length; i++) 757 { 758 try 759 { 760 pSimpleDateFormat.applyPattern(PATTERNS[i]); 761 dReceivedDate = pSimpleDateFormat.parse(strDate); 762 if (dReceivedDate == null) 763 { 764 continue; 765 } 766 return dReceivedDate; 767 } 768 catch (ParseException pe) 769 { 770 ; // ignore this format try the next one 771 } 772 } 773 } 774 return dReceivedDate; 775 } 776 777 /** 778 * Verify that the string represantes the date with one of the registered formats 779 * @param strDate the date as string. 780 * @return boolean "true" if the string represantes the date in one of the registed formats. 781 */ 782 static boolean isDate(String strDate) 783 { 784 return null != getDateFromString(strDate); 785 } 786 787 static String buildProcedureCall(String packageName, String procedureName, int paramCount) { 788 return buildProcedureCall(packageName + "." + procedureName, paramCount); 789 } 790 791 static String buildProcedureCall(String procedureName, int paramCount) { 792 StringBuilder sb = new StringBuilder("{call ").append(procedureName).append("("); 793 for (int n = 1; n <= paramCount; n++) { 794 sb.append("?,"); 795 } 796 if (paramCount > 0) { 797 sb.setLength(sb.length()-1); 798 } 799 return sb.append(")}").toString(); 800 } 801 List<BaseBean> runPreparedStatementAsList(Map<String, Class<?>> targetTypes, 802 PreparedStatement ps){ 803 checkArgument(null !=ps,"ps is null"); 804 ResultSet rs = null; 805 try { 806 ps.setFetchSize(100); 807 rs = ps.executeQuery(); 808 UnnameRowMetaData metaData = new UnnameRowMetaData(rs.getMetaData(), targetTypes); 809 List<BaseBean> list = Lists.newLinkedList(); 810 while(rs.next()){ 811 BaseBean row = new UnnameRow(metaData); 812 for(int i=0; i<metaData.defaultColumnIdList.length; ++i){ 813 row.setValue(i, rs.getObject(i+1)); 814 } 815 list.add(row); 816 } 817 return list; 818 } catch (DaoException e) { 819 throw new RuntimeDaoException(e); 820 }catch (SQLException e) { 821 throw new RuntimeDaoException(new DataAccessException(e)); 822 } finally { 823 this.close(rs); 824 } 825 } 826 List<BaseBean> runSqlAsList(Map<String, Class<?>> targetTypes, String sql, Object... argList){ 827 checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty"); 828 PreparedStatement ps = null; 829 Connection connection = null; 830 try { 831 connection = this.getConnection(); 832 if(isDebug){ 833 log("runSqlAsList:" + sql); 834 } 835 ps = connection.prepareStatement(sql, 836 ResultSet.TYPE_FORWARD_ONLY, 837 ResultSet.CONCUR_READ_ONLY); 838 fillPrepareStatement(ps, argList); 839 return runPreparedStatementAsList(targetTypes,ps); 840 } catch (DaoException e) { 841 throw new RuntimeDaoException(e); 842 }catch (SQLException e) { 843 throw new RuntimeDaoException(new DataAccessException(e)); 844 } finally { 845 this.close(ps); 846 this.releaseConnection(connection); 847 } 848 } 849 850 private static Function<BaseBean,Map<String,Object>> VMAP_FUN = new Function<BaseBean,Map<String,Object>>(){ 851 852 @Override 853 public Map<String, Object> apply(BaseBean input) { 854 return input.asNameValueMap(); 855 }}; 856 857 @Override 858 public List<Map<String, Object>> runSqlForMap(Map<String,Class<?>> targetType, String sql,Object... argList) throws RuntimeDaoException{ 859 List<BaseBean> list = runSqlAsList(targetType, sql, argList); 860 if(list.isEmpty()){ 861 return Collections.emptyList(); 862 } 863 return Lists.transform(list, VMAP_FUN); 864 } 865 866 private <T> List<T> fetchOnlyOneColumn(List<BaseBean> list,final Class<T> targetType){ 867 if(list.isEmpty()){ 868 return Collections.emptyList(); 869 } 870 BaseBean row = list.get(0); 871 Object[] values = row.asValueArray(); 872 873 checkArgument(values.length == 1,"more than 1 column returned"); 874 return Lists.transform(list, new Function<BaseBean, T>() { 875 @Override 876 public T apply(BaseBean input) { 877 if(null == targetType){ 878 return input.getValue(0); 879 } 880 return targetType.cast(input.getValue(0)); 881 } 882 }); 883 } 884 <T> List<T> runPreparedStatementAsList(Class<T> targetType, PreparedStatement ps) throws RuntimeDaoException{ 885 List<BaseBean> list = runPreparedStatementAsList((Map<String,Class<?>>)null, ps); 886 return fetchOnlyOneColumn(list,targetType); 887 } 888 889 @Override 890 public List<BaseBean> runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{ 891 return runSqlAsList(Collections.<String,Class<?>>emptyMap(), sql, argList); 892 } 893 @Override 894 public <T> List<T> runSqlAsList(Class<T> targetType, String sql, Object... argList) throws RuntimeDaoException{ 895 List<BaseBean> list = runSqlAsList((Map<String,Class<?>>)null, sql, argList); 896 return fetchOnlyOneColumn(list,targetType); 897 } 898 899 @Override 900 public <T> T runSqlForValue(Class<T> targetType,String sql, Object... argList) throws RuntimeDaoException{ 901 List<T> list = runSqlAsList(targetType,sql,argList); 902 checkArgument(list.size() == 1,"more than 1 row returned for SQL: %s",sql); 903 return list.get(0); 904 } 905 <T> T runPreparedStatementForValue(Class<T> targetType,PreparedStatement ps) throws RuntimeDaoException{ 906 List<T> list = runPreparedStatementAsList(targetType,ps); 907 checkArgument(list.size() == 1,"more than 1 row returned"); 908 return list.get(0); 909 } 910 /** 911 * 填充PreparedStatement中的参数对象 912 * 913 * @param ps 914 * @param argList 915 * @throws SQLException 916 */ 917 protected static void fillPrepareStatement(PreparedStatement ps, Object[] argList) throws DaoException{ 918 try { 919 if (!(argList == null || ps == null)) { 920 for (int i = 0; i < argList.length; i++) { 921 if (argList[i] instanceof ByteBuffer) { 922 ps.setBytes(i + 1, getBytesInBuffer((ByteBuffer) argList[i])); 923 } else { 924 ps.setObject(i + 1, argList[i]); 925 } 926 } 927 } 928 } catch (SQLException e) { 929 throw new DaoException(e); 930 } 931 } 932 protected static void setPreparedStatement(PreparedStatement ps,int pos,Object value,int sqlType) 933 throws SQLException { 934 if(value instanceof ByteBuffer){ 935 setBytes(sqlType, ps, pos, (ByteBuffer)value); 936 }else{ 937 ps.setObject(pos,value,sqlType); 938 } 939 } 940 941 942 /** 943 * For those who do not want to read below, please simply pick up the 944 * configuration associated with your database. 945 * 946 * More explanation: 947 * When you save a bean whose primary key is numeric and has no value set, 948 * we assume that you want sql2java to retrieve a key's value generated 949 * on the database side. 950 * 951 * generatedkey.retrieve can take 3 values: 952 * 953 * auto - the standard approach when you have a JDBC 3.0 driver. 954 * 955 * before - the key's value is retrieved before inserting the record. 956 * 957 * after - the key's value is retrieved after inserting the record 958 * @author guyadong 959 * 960 */ 961 enum AutoKeyRetrieveType{ 962 auto,after,before 963 } 964 /** 965 * 966 * For those who do not want to read below, please simply pick up the 967 * configuration associated with your database. 968 * 969 * More explanation: 970 * When you save a bean whose primary key is numeric and has no value set, 971 * we assume that you want sql2java to retrieve a key's value generated 972 * on the database side. 973 * 974 * If you set it to before or after you also need to configure the 975 * autogeneratedkey.statement properties. 976 * <TABLE> is replaced at code generation time by the table name. 977 * <KEY> is replaced at code generation time by the column name. 978 * You may adjust this properties to fit your own naming convention. 979 * 980 * PICK THE CONFIGURATION ASSOCIATED WITH YOUR DATABASE 981 * (or create one, but in that case let us know so we can add it here... :-) 982 */ 983 private static final ImmutableMap<String, String> AUTOINC_MECHANISM = ImmutableMap.<String, String>builder() 984 /*-- HSQL ------*/ 985 .put("hsql.generatedkey.retrieve","after") 986 .put("hsql.generatedkey.statement","CALL IDENTITY()") 987 /*-- ORACLE 8i ------*/ 988 .put("oracle.generatedkey.retrieve","before") 989 .put("oracle.generatedkey.statement","SELECT SEQ_<TABLE>.nextval FROM DUAL") 990 /*-- MaxDB (SapDB) ------*/ 991 .put("maxdb.generatedkey.retrieve","before") 992 .put("maxdb.generatedkey.statement","SELECT SEQ_<TABLE>_<KEY>.nextval FROM DUAL") 993 /*-- MYSQL (without jdbc 3.0 driver) ------*/ 994 .put("mysql.generatedkey.retrieve","after") 995 .put("mysql.generatedkey.statement","SELECT last_insert_id()") 996 /*-- SQL SERVER ------*/ 997 .put("sqlserver.generatedkey.retrieve","after") 998 .put("sqlserver.generatedkey.statement","SELECT @@IDENTITY") 999 /*-- SYBASE ------*/ 1000 .put("sybase.generatedkey.retrieve","after") 1001 .put("sybase.generatedkey.statement","SELECT @@IDENTITY") 1002 /*-- POSTGRESQL ------*/ 1003 .put("postgresql.generatedkey.retrieve","before") 1004 .put("postgresql.generatedkey.statement","SELECT nextval('<TABLE>_<KEY>_SEQ')") 1005 /*-- INFORMIX ------*/ 1006 .put("informix.generatedkey.retrieve","after") 1007 .put("informix.generatedkey.statement","SELECT dbinfo('sqlca.sqlerrd1') FROM systables WHERE tabid=1") 1008 /*-- FIREBIRD ------ 1009 - need to create "sequence" number table first "create generator SEQ_<TABLE>" 1010 - the 1 is how much to increment the sequence*/ 1011 .put("firebird.generatedkey.retrieve","before") 1012 .put("firebird.generatedkey.statement","SELECT GEN_ID(SEQ_<TABLE>, 1) FROM RDB$DATABASE") 1013 /*-- DERBY ------*/ 1014 .put("derby.generatedkey.retrieve","after") 1015 .put("derby.generatedkey.statement","VALUES IDENTITY_VAL_LOCAL()") 1016 .build(); 1017 1018 private static final String getMechanismPropertyWithSuffix(String engine,String suffix) { 1019 return checkNotNull(AUTOINC_MECHANISM.get(engine + "." + suffix), "NOT FOUND %s property for %s database",suffix,engine); 1020 } 1021 1022 private synchronized void autokeyMechanismInit(Connection connection) throws SQLException{ 1023 DatabaseMetaData meta = checkNotNull(connection,"connection is null").getMetaData(); 1024 if(meta.supportsGetGeneratedKeys()){ 1025 generatedkeyRetrieve = "auto"; 1026 generatedkeyStatement = null; 1027 }else{ 1028 String engine = new StringTokenizer(meta.getDatabaseProductName()).nextToken().toLowerCase(); 1029 generatedkeyRetrieve = getMechanismPropertyWithSuffix(engine,RETRIEVE_SUFFIX).toLowerCase(); 1030 generatedkeyStatement = getMechanismPropertyWithSuffix(engine,RETRIEVE_SUFFIX); 1031 } 1032 } 1033 1034 String getGeneratedkeyRetrieve(Connection c){ 1035 // double check 1036 if(generatedkeyRetrieve == null){ 1037 synchronized (this) { 1038 if(generatedkeyRetrieve == null){ 1039 try { 1040 autokeyMechanismInit(c); 1041 } catch (SQLException e) { 1042 throw new RuntimeException(e); 1043 } 1044 } 1045 } 1046 } 1047 return generatedkeyRetrieve; 1048 } 1049 1050 AutoKeyRetrieveType getGeneratedkeyRetrieveType(Connection c){ 1051 if(retrieveType == null){ 1052 synchronized (this) { 1053 if(retrieveType == null){ 1054 retrieveType = AutoKeyRetrieveType.valueOf(getGeneratedkeyRetrieve(c)); 1055 } 1056 } 1057 } 1058 return retrieveType; 1059 } 1060 String getGeneratedkeyStatement(Connection c){ 1061 // double check 1062 if(generatedkeyStatement == null && !AutoKeyRetrieveType.auto.equals(retrieveType)){ 1063 synchronized (this) { 1064 if(generatedkeyStatement == null && !AutoKeyRetrieveType.auto.equals(retrieveType)){ 1065 try { 1066 autokeyMechanismInit(c); 1067 } catch (SQLException e) { 1068 throw new RuntimeException(e); 1069 } 1070 } 1071 } 1072 } 1073 return generatedkeyStatement; 1074 } 1075 1076 public void setDebug(boolean isDebug) { 1077 this.isDebug = isDebug; 1078 } 1079 1080}