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.DataAccessException; 044import gu.sql2java.exception.RuntimeDaoException; 045 046 047/** 048 * The Manager provides connections and manages transactions transparently. 049 * <br> 050 * It is a singleton, you get its instance with the getInstance() method. 051 * All of the XxxxManager classes use the Manager to get database connections. 052 * Before doing any operation, you must pass either a 053 * datasource or a jdbc driver/url/username/password. 054 * @author guyadong 055 */ 056public final class Manager implements Constant,SqlRunner 057{ 058 private static class Singleton{ 059 private static final Manager INSTANCE = new Manager(); 060 } 061 private static InheritableThreadLocal<Connection> transactionConnection = new InheritableThreadLocal<Connection>(); 062 /** JDBC properties from properties file */ 063 private static final Properties databaseProperties; 064 065 static{ 066 String envVar="config_folder"; 067 String propFile="database.properties"; 068 String confFolder="conf"; 069 databaseProperties = ConfigUtils.loadAllProperties(propFile, confFolder, envVar, Manager.class, false); 070 } 071 /** 072 * inject properties to {@link #databaseProperties}<br> 073 * be effected only while called before initializing singleton instance 074 * @param properties 075 * @see JdbcProperty 076 */ 077 public static final void injectProperties(Map<String,String> properties){ 078 if(null != properties){ 079 EnumMap<JdbcProperty, String> enumMap = new EnumMap<JdbcProperty,String>(JdbcProperty.class); 080 JdbcProperty property; 081 for(Entry<String, String> entry:properties.entrySet()){ 082 if(null != (property = JdbcProperty.fromKey(entry.getKey()))){ 083 enumMap.put(property, entry.getValue()); 084 } 085 } 086 injectProperties(enumMap); 087 } 088 } 089 /** 090 * inject properties to {@link #databaseProperties}<br> 091 * be effected only while called before initializing singleton instance 092 * @param properties 093 */ 094 public static final void injectProperties(EnumMap<JdbcProperty,String> properties){ 095 if(null != properties){ 096 Boolean isDebug = false; 097 if(properties.containsKey(JdbcProperty.DEBUG)){ 098 isDebug = "true".equalsIgnoreCase(properties.get(JdbcProperty.DEBUG)); 099 databaseProperties.setProperty(JdbcProperty.DEBUG.key, isDebug.toString()); 100 } 101 String prefix=isDebug?"debug.":"work."; 102 String value; 103 JdbcProperty key; 104 for(Entry<JdbcProperty, String> entry : properties.entrySet()){ 105 value = entry.getValue(); 106 key = entry.getKey(); 107 if( !JdbcProperty.DEBUG.equals(key) && null !=value && !value.isEmpty()){ 108 databaseProperties.setProperty(key.withPrefix(prefix), value); 109 } 110 } 111 } 112 } 113 private volatile DataSource dataSource = null; 114 private String jdbcDriver = null; 115 private String jdbcUrl = null; 116 private String jdbcUsername = null; 117 private String jdbcPassword = null; 118 private volatile String generatedkeyRetrieve = null; 119 private volatile String generatedkeyStatement = null; 120 private volatile AutoKeyRetrieveType retrieveType; 121 private String maxIdleTime; 122 private String idleConnectionTestPeriod; 123 private String maxPoolSize; 124 private String minPoolSize; 125 private boolean isDebug = false; 126 127 /** 128 * Returns the manager singleton instance. 129 */ 130 private Manager() 131 { 132 loadProperties(databaseProperties); 133 } 134 135 /** 136 * Returns the manager singleton instance. 137 */ 138 public static Manager getInstance() 139 { 140 return Singleton.INSTANCE; 141 } 142 143 /** dispose pool */ 144 public void disposePool(){ 145 try{ 146 DataSources.destroy(dataSource); 147 }catch (Exception e) { 148 log("dispose pool wrong ..." + e); 149 } 150 } 151 152 /** 153 * configure with the parameters given in the properties object 154 * @param properties the properties object to be used 155 */ 156 public void loadProperties(Properties properties){ 157 isDebug = Boolean.valueOf(properties.getProperty("isDebug")); 158 String prefix=isDebug ? "debug." : "work."; 159 jdbcDriver = properties.getProperty(JdbcProperty.JDBC_DRIVER.withPrefix(prefix)); 160 jdbcUrl = properties.getProperty(JdbcProperty.JDBC_URL.withPrefix(prefix)); 161 jdbcUsername = properties.getProperty(JdbcProperty.JDBC_USERNAME.withPrefix(prefix)); 162 jdbcPassword = properties.getProperty(JdbcProperty.JDBC_PASSWORD.withPrefix(prefix)); 163 164 generatedkeyRetrieve = properties.getProperty(JdbcProperty.GENERATEDKEY_RETRIEVE.name()); 165 generatedkeyStatement = properties.getProperty(JdbcProperty.GENERATEDKEY_STATEMENT.name()); 166 167 maxPoolSize = properties.getProperty(JdbcProperty.C3P0_MAXPOOLSIZE.withPrefix(prefix)); 168 minPoolSize = properties.getProperty(JdbcProperty.C3P0_MINPOOLSIZE.withPrefix(prefix)); 169 maxIdleTime = properties.getProperty(JdbcProperty.C3P0_MAXIDLETIME.withPrefix(prefix)); 170 idleConnectionTestPeriod = properties.getProperty(JdbcProperty.C3P0_IDLECONNECTIONTESTPERIOD.withPrefix(prefix)); 171 } 172 private void logDatabaseParameters(){ 173 if(isDebug){ 174 log("database using debug environment parameter: "); 175 log("jdbcUrl = " + jdbcUrl); 176 log("jdbcUsername = " + jdbcUsername); 177 log("jdbcPassword = " + jdbcPassword); 178 log("maxPoolSize = " + maxPoolSize); 179 log("minPoolSize = " + minPoolSize); 180 log("maxIdleTime = " + maxIdleTime); 181 log("idleConnectionTestPeriod = " + idleConnectionTestPeriod); 182 } 183 } 184 /** 185 * use key synchronized to be sure the ds created once 186 * @return 187 */ 188 private DataSource getDataSource(){ 189 // double check 190 if (dataSource == null){ 191 synchronized (this) { 192 if (dataSource == null){ 193 try{ 194 logDatabaseParameters(); 195 //set C3P0 properties 196 ComboPooledDataSource cpds = new ComboPooledDataSource(); 197 cpds.setDriverClass(jdbcDriver); 198 cpds.setUser(jdbcUsername); 199 cpds.setPassword(jdbcPassword); 200 cpds.setJdbcUrl(jdbcUrl); 201 cpds.setMaxPoolSize(Integer.parseInt(maxPoolSize)); 202 cpds.setMinPoolSize(Integer.parseInt(minPoolSize)); 203 cpds.setMaxIdleTime(Integer.parseInt(maxIdleTime)); 204 cpds.setIdleConnectionTestPeriod(Integer.parseInt(idleConnectionTestPeriod)); 205 dataSource = cpds; 206 }catch (Exception e){ 207 throw new IllegalArgumentException(String.format("can't get connection by argument...driver/url/username/password[%s/%s/%s/%s]",jdbcDriver,jdbcUrl,jdbcUsername,jdbcPassword),e); 208 } 209 } 210 } 211 } 212 return dataSource; 213 } 214 215 /** 216 * Gets an auto commit connection. 217 * <br> 218 * Normally you do not need this method that much ;-) 219 * 220 * @return an auto commit connection 221 */ 222 public Connection getConnection() throws SQLException 223 { 224 Connection tc = transactionConnection.get(); 225 if (tc != null) { 226 return tc; 227 } 228 return getDataSource().getConnection(); 229 } 230 231 /** 232 * Releases the database connection. 233 * <br> 234 * Normally you should not need this method ;-) 235 */ 236 public void releaseConnection(Connection c) 237 { 238 Connection tc = transactionConnection.get(); 239 if (tc != null){ 240 return; 241 } 242 try{ 243 if (c != null){ 244 c.close(); 245 } 246 }catch (SQLException x){ 247 log("Could not release the connection: "+x.toString()); 248 } 249 } 250 251 /** 252 * Initiates a database transaction. 253 * <br> 254 * When working within a transaction, you should invoke this method first. 255 * The connection is returned just in case you need to set the isolation level. 256 * 257 * @return a non-auto commit connection with the default transaction isolation level 258 */ 259 private Connection beginTransaction() throws SQLException 260 { 261 Connection c = this.getConnection(); 262 c.setAutoCommit(false); 263 transactionConnection.set(c); 264 return c; 265 } 266 267 /** 268 * Releases connection used for the transaction and performs a commit or rollback. 269 * 270 * @param commit tells whether this connection should be committed 271 * true for commit(), false for rollback() 272 */ 273 private void endTransaction(boolean commit) throws SQLException 274 { 275 Connection c = transactionConnection.get(); 276 if (c == null) 277 { 278 return; 279 } 280 281 try 282 { 283 if (commit) 284 { 285 c.commit(); 286 } 287 else 288 { 289 c.rollback(); 290 } 291 } 292 finally 293 { 294 c.setAutoCommit(true); 295 transactionConnection.set(null); 296 releaseConnection(c); 297 } 298 } 299 /** 300 * Run {@code Callable<T>} as a transaction.<br> 301 * all exceptions but {@code SQLException} threw by {@code Callable<T>} is wrapped into {@code RuntimeException}<br> 302 * throw {@code NullPointerException} if {@code fun} be {@code null}<br> 303 * @param <T> type of return result 304 * @param fun 305 * @param transactionListener listener for transaction status 306 * @return 307 * @throws RuntimeDaoException 308 */ 309 <T>T runAsTransaction(Callable<T> fun,TransactionListener transactionListener) throws RuntimeDaoException{ 310 checkArgument(fun != null, "fun is null"); 311 checkArgument(null != transactionListener,"transactionListener is null"); 312 try { 313 beginTransaction(); 314 transactionListener.onBegin(); 315 boolean commit = false; 316 try { 317 T result = fun.call(); 318 commit = true; 319 transactionListener.onCommit(); 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 transactionListener.onEnd(); 330 } 331 } catch (SQLException e) { 332 throw new RuntimeDaoException(e); 333 } 334 } 335 336 /** 337 * Run {@code Runnable} as a transaction.no return 338 * @param fun 339 * @param transactionListener listener for transaction status 340 * @throws RuntimeDaoException 341 * @see #runAsTransaction(Callable,TableListener.TransactionListener) 342 */ 343 void runAsTransaction(final Runnable fun,TransactionListener transactionListener) throws RuntimeDaoException{ 344 checkArgument(fun != null, "fun is null"); 345 runAsTransaction(new Callable<Object>(){ 346 347 @Override 348 public Object call() throws Exception { 349 fun.run(); 350 return null; 351 }},transactionListener); 352 } 353 354 @Override 355 public <T>T runAsTransaction(Callable<T> fun) throws RuntimeDaoException{ 356 return runAsTransaction(fun, ListenerContainer.TRANSACTION_LISTENER); 357 } 358 359 @Override 360 public void runAsTransaction(Runnable fun) throws RuntimeDaoException{ 361 runAsTransaction(fun,ListenerContainer.TRANSACTION_LISTENER); 362 } 363 364//////////////////////////////////////////////////// 365// cleaning method 366//////////////////////////////////////////////////// 367 368 /** 369 * Closes the passed Statement. 370 */ 371 void close(Statement s) 372 { 373 try 374 { 375 if (s != null) { 376 s.close(); 377 } 378 } 379 catch (SQLException x) 380 { 381 log("Could not close statement!: " + x.toString()); 382 } 383 } 384 385 /** 386 * Closes the passed ResultSet. 387 */ 388 void close(ResultSet rs) 389 { 390 try 391 { 392 if (rs != null) { 393 rs.close(); 394 } 395 } 396 catch (SQLException x) 397 { 398 log("Could not close result set!: " + x.toString()); 399 } 400 } 401 402 /** 403 * Closes the passed Statement and ResultSet. 404 */ 405 void close(Statement s, ResultSet rs) 406 { 407 close(rs); 408 close(s); 409 } 410 411 //////////////////////////////////////////////////// 412 // Helper methods for fetching numbers using IDs or names 413 //////////////////////////////////////////////////// 414 415 /** 416 * return all bytes in buffer (position~limit),no change status of buffer 417 * @param buffer 418 * @return 419 */ 420 private static final byte[] getBytesInBuffer(ByteBuffer buffer){ 421 if(null == buffer){ 422 return null; 423 } 424 int pos = buffer.position(); 425 try{ 426 byte[] bytes = new byte[buffer.remaining()]; 427 buffer.get(bytes); 428 return bytes; 429 }finally{ 430 buffer.position(pos); 431 } 432 } 433 /** 434 * Set a byte array to the passed prepared statement as a ByteBuffer or as null. 435 */ 436 private static void setBytes(int sqlType,PreparedStatement ps, int pos, ByteBuffer bytes) throws SQLException 437 { 438 setBytes(sqlType,ps,pos,getBytesInBuffer(bytes)); 439 } 440 /** 441 * Set a byte array to the passed prepared statement as a ByteBuffer or as null. 442 */ 443 private static void setBytes(int sqlType,PreparedStatement ps, int pos, byte[] bytes) throws SQLException 444 { 445 if (null == bytes){ 446 ps.setNull(pos, sqlType); 447 }else{ 448 ps.setBytes(pos, bytes); 449 } 450 } 451 /** 452 * Retrieves a date value from the passed result set as a Calendar object. 453 */ 454 static Calendar getCalendar(ResultSet rs, int pos) throws SQLException 455 { 456 Timestamp date = rs.getTimestamp(pos); 457 if (rs.wasNull()) { 458 return null; 459 } 460 Calendar calendar = Calendar.getInstance(); 461 calendar.setTime(date); 462 return calendar; 463 } 464 465 /** 466 * Retrieves a date value from the passed result set as a Calendar object. 467 */ 468 static Calendar getCalendar(ResultSet rs, String column) throws SQLException 469 { 470 Timestamp date = rs.getTimestamp(column); 471 if (null == date) { 472 return null; 473 } 474 Calendar calendar = Calendar.getInstance(); 475 calendar.setTime(date); 476 return calendar; 477 } 478 479 /** 480 * Set a Calendar object to the passed prepared statement as a date or as null. 481 */ 482 static void setCalendar(PreparedStatement ps, int pos, Calendar calendar) throws SQLException 483 { 484 if (calendar == null) 485 { 486 ps.setNull(pos, Types.TIMESTAMP); 487 } 488 else 489 { 490 ps.setTimestamp(pos, new java.sql.Timestamp(calendar.getTimeInMillis())); 491 } 492 } 493 494 /** 495 * 496 * For compatibility with old version {@link ResultSet} without getObject(int,Class) method 497 * @param rs 498 * @param columnIndex 499 * @param type 500 * @return 501 * @throws SQLException 502 */ 503 @SuppressWarnings("unchecked") 504 public static <T> T getObject(ResultSet rs,int columnIndex, Class<T> type) throws SQLException { 505 if (type == null) { 506 throw new SQLException("Type parameter can not be null"); 507 } 508 Object t = null; 509 if (type.equals(String.class)) { 510 t = rs.getString(columnIndex); 511 } else if (type.equals(BigDecimal.class)) { 512 t = rs.getBigDecimal(columnIndex); 513 } else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) { 514 t = Boolean.valueOf(rs.getBoolean(columnIndex)); 515 } else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) { 516 t = Integer.valueOf(rs.getInt(columnIndex)); 517 } else if (type.equals(Long.class) || type.equals(Long.TYPE)) { 518 t = Long.valueOf(rs.getLong(columnIndex)); 519 } else if (type.equals(Float.class) || type.equals(Float.TYPE)) { 520 t = Float.valueOf(rs.getFloat(columnIndex)); 521 } else if (type.equals(Double.class) || type.equals(Double.TYPE)) { 522 t = Double.valueOf(rs.getDouble(columnIndex)); 523 } else if (type.equals(byte[].class)) { 524 t = rs.getBytes(columnIndex); 525 } else if (type.equals(ByteBuffer.class)) { 526 byte[] array = rs.getBytes(columnIndex); 527 t = (array == null ? null : ByteBuffer.wrap(array)); 528 } else if (type.equals(java.sql.Date.class)) { 529 t = rs.getDate(columnIndex); 530 } else if (type.equals(Time.class)) { 531 t = rs.getTime(columnIndex); 532 } else if (type.equals(Timestamp.class)) { 533 t = rs.getTimestamp(columnIndex); 534 } else if (type.equals(Calendar.class)) { 535 java.util.Date date = (java.util.Date) rs.getObject(columnIndex); 536 if (date == null) { 537 return null; 538 } 539 Calendar calendar = Calendar.getInstance(); 540 calendar.setTime(date); 541 t = calendar; 542 } else if (type.equals(Clob.class)) { 543 t = rs.getClob(columnIndex); 544 } else if (type.equals(Blob.class)) { 545 t = rs.getBlob(columnIndex); 546 } else if (type.equals(Array.class)) { 547 t = rs.getArray(columnIndex); 548 } else if (type.equals(Ref.class)) { 549 t = rs.getRef(columnIndex); 550 } else if (type.equals(URL.class)) { 551 t = rs.getURL(columnIndex); 552 } else { 553 try { 554 t = type.cast(rs.getObject(columnIndex)); 555 } catch (ClassCastException e) { 556 throw new SQLException("Conversion not supported for type " + type.getName(), e); 557 } 558 } 559 return rs.wasNull() ? null : (T)t; 560 } 561 562//////////////////////////////////////////////////// 563// Date helper methods 564//////////////////////////////////////////////////// 565 566 /** 567 * pattern for received date processing. 568 */ 569 private static final String[] PATTERNS = new String[] 570 { 571 "EEE, dd MMM yyyy HH:mm:ss '-'S '('z')'", 572 "EEE, dd MMM yyyy HH:mm:ss '+'S '('z')'", 573 "EEE, dd MMM yyyy HH:mm:ss '-'S", 574 "EEE, dd MMM yyyy HH:mm:ss '+'S", 575 "EEE, dd MMM yyyy HH:mm:ss z", 576 "EEE, dd MMM yyyy HH:mm:ss Z", 577 "EEE, dd MMM yyyy HH:mm:ss", 578 "EEE, d MMM yyyy HH:mm:ss '-'S '('z')'", 579 "EEE, d MMM yyyy HH:mm:ss '+'S '('z')'", 580 "EEE, d MMM yyyy HH:mm:ss '-'S", 581 "EEE, d MMM yyyy HH:mm:ss '+'S", 582 "EEE, d MMM yyyy HH:mm:ss z", 583 "EEE, d MMM yyyy HH:mm:ss Z", 584 "EEE, d MMM yyyy HH:mm:ss", 585 586 "EEE, dd MMM yy HH:mm:ss '-'S '('z')'", 587 "EEE, dd MMM yy HH:mm:ss '+'S '('z')'", 588 "EEE, dd MMM yy HH:mm:ss '-'S", 589 "EEE, dd MMM yy HH:mm:ss '+'S", 590 "EEE, dd MMM yy HH:mm:ss z", 591 "EEE, dd MMM yy HH:mm:ss Z", 592 "EEE, dd MMM yy HH:mm:ss", 593 "EEE, d MMM yy HH:mm:ss '-'S '('z')'", 594 "EEE, d MMM yy HH:mm:ss '+'S '('z')'", 595 "EEE, d MMM yy HH:mm:ss '-'S", 596 "EEE, d MMM yy HH:mm:ss '+'S", 597 "EEE, d MMM yy HH:mm:ss z", 598 "EEE, d MMM yy HH:mm:ss Z", 599 "EEE, d MMM yy HH:mm:ss", 600 601 "dd MMM yyyy HH:mm:ss '-'S", 602 "dd MMM yyyy HH:mm:ss '+'S", 603 "dd MMM yyyy HH:mm:ss '-'S '('z')'", 604 "dd MMM yyyy HH:mm:ss '+'S '('z')'", 605 "dd MMM yyyy HH:mm:ss z", 606 "dd MMM yyyy HH:mm:ss Z", 607 "dd MMM yyyy HH:mm:ss", 608 609 "dd MMM yyy HH:mm:ss '-'S", 610 "dd MMM yyy HH:mm:ss '+'S", 611 "dd MMM yyy HH:mm:ss '-'S '('z')'", 612 "dd MMM yyy HH:mm:ss '+'S '('z')'", 613 "dd MMM yyy HH:mm:ss z", 614 "dd MMM yyy HH:mm:ss Z", 615 "dd MMM yyy HH:mm:ss", 616 617 "yyyy.MM.dd HH:mm:ss z", 618 "yyyy.MM.dd HH:mm:ss Z", 619 "yyyy.MM.d HH:mm:ss z", 620 "yyyy.MM.d HH:mm:ss Z", 621 "yyyy.MM.dd HH:mm:ss", 622 "yyyy.MM.d HH:mm:ss", 623 624 "yy.MM.dd HH:mm:ss z", 625 "yy.MM.dd HH:mm:ss Z", 626 "yy.MM.d HH:mm:ss z", 627 "yy.MM.d HH:mm:ss Z", 628 "yy.MM.dd HH:mm:ss", 629 "yy.MM.d HH:mm:ss", 630 631 "yyyy MM dd HH:mm:ss", 632 "yyyy MM d HH:mm:ss", 633 "yyyy MM dd HH:mm:ss z", 634 "yyyy MM dd HH:mm:ss Z", 635 "yyyy MM d HH:mm:ss z", 636 "yyyy MM d HH:mm:ss Z", 637 638 "yy MM dd HH:mm:ss", 639 "yy MM d HH:mm:ss", 640 "yy MM dd HH:mm:ss z", 641 "yy MM dd HH:mm:ss Z", 642 "yy MM d HH:mm:ss z", 643 "yy MM d HH:mm:ss Z", 644 645 "yyyy-MM-dd HH:mm:ss z", 646 "yyyy-MM-dd HH:mm:ss Z", 647 "yyyy-MM-d HH:mm:ss z", 648 "yyyy-MM-d HH:mm:ss Z", 649 "yyyy-MM-dd HH:mm:ss", 650 "yyyy-MM-d HH:mm:ss", 651 652 "yy-MM-dd HH:mm:ss z", 653 "yy-MM-dd HH:mm:ss Z", 654 "yy-MM-d HH:mm:ss z", 655 "yy-MM-d HH:mm:ss Z", 656 "yy-MM-dd HH:mm:ss", 657 "yy-MM-d HH:mm:ss", 658 659 "dd MMM yyyy", 660 "d MMM yyyy", 661 662 "dd.MMM.yyyy", 663 "d.MMM.yyyy", 664 665 "dd-MMM-yyyy", 666 "d-MMM-yyyy", 667 668 "dd MM yyyy", 669 "d MM yyyy", 670 671 "dd.MM.yyyy", 672 "d.MM.yyyy", 673 674 "dd-MM-yyyy", 675 "d-MM-yyyy", 676 677 "yyyy MM dd", 678 "yyyy MM d", 679 680 "yyyy.MM.dd", 681 "yyyy.MM.d", 682 683 "yyyy-MM-dd", 684 "yyyy-MM-d", 685 686 "dd MMM yy", 687 "d MMM yy", 688 689 "dd.MMM.yy", 690 "d.MMM.yy", 691 692 "dd-MMM-yy", 693 "d-MMM-yy", 694 695 "dd MM yy", 696 "d MM yy", 697 698 "dd.MM.yy", 699 "d.MM.yy", 700 701 "dd-MM-yy", 702 "d-MM-yy", 703 704 "yy MMM dd", 705 "yy MMM d", 706 707 "yy.MMM.dd", 708 "yy.MMM.d", 709 710 "yy-MMM-dd", 711 "yy-MMM-d", 712 713 "yy MMM dd", 714 "yy MMM d", 715 716 "yy.MMM.dd", 717 "yy.MMM.d", 718 719 "yy-MMM-dd", 720 "yy-MMM-d", 721 // ex: Wed 19, Feb 2003 722 "EEE dd, MMM yyyy", 723 // ex: Wed 19, Feb 03 724 "EEE dd, MMM yy" 725 }; 726 727 728 /** 729 * get a date from a date string representation in one of the registered formats 730 * @param strDate the date as string. 731 * @return Date object ,otherwise null If (null or empty) or correct pattern was not found 732 */ 733 static java.util.Date getDateFromString(String strDate) 734 { 735 java.util.Date dReceivedDate = null; 736 if (strDate == null) { 737 return dReceivedDate; 738 } else { 739 strDate = strDate.trim(); 740 } 741 742 SimpleDateFormat pSimpleDateFormat = new SimpleDateFormat(""); 743 if (!strDate.isEmpty()) 744 { 745 for (int i=0; i<PATTERNS.length; i++) 746 { 747 try 748 { 749 pSimpleDateFormat.applyPattern(PATTERNS[i]); 750 dReceivedDate = pSimpleDateFormat.parse(strDate); 751 if (dReceivedDate == null) 752 { 753 continue; 754 } 755 return dReceivedDate; 756 } 757 catch (ParseException pe) 758 { 759 ; // ignore this format try the next one 760 } 761 } 762 } 763 return dReceivedDate; 764 } 765 766 /** 767 * Verify that the string represantes the date with one of the registered formats 768 * @param strDate the date as string. 769 * @return boolean "true" if the string represantes the date in one of the registed formats. 770 */ 771 static boolean isDate(String strDate) 772 { 773 return null != getDateFromString(strDate); 774 } 775 776 static String buildProcedureCall(String packageName, String procedureName, int paramCount) { 777 return buildProcedureCall(packageName + "." + procedureName, paramCount); 778 } 779 780 static String buildProcedureCall(String procedureName, int paramCount) { 781 StringBuilder sb = new StringBuilder("{call ").append(procedureName).append("("); 782 for (int n = 1; n <= paramCount; n++) { 783 sb.append("?,"); 784 } 785 if (paramCount > 0) { 786 sb.setLength(sb.length()-1); 787 } 788 return sb.append(")}").toString(); 789 } 790 List<BaseBean> runPreparedStatementAsList(Map<String, Class<?>> targetTypes, 791 PreparedStatement ps){ 792 checkArgument(null !=ps,"ps is null"); 793 ResultSet rs = null; 794 try { 795 ps.setFetchSize(100); 796 rs = ps.executeQuery(); 797 UnnameRowMetaData metaData = new UnnameRowMetaData(rs.getMetaData(), targetTypes); 798 List<BaseBean> list = Lists.newLinkedList(); 799 while(rs.next()){ 800 BaseBean row = new UnnameRow(metaData); 801 for(int i=0; i<metaData.defaultColumnIdList.length; ++i){ 802 row.setValue(i, rs.getObject(i+1)); 803 } 804 list.add(row); 805 } 806 return list; 807 } catch (SQLException e) { 808 throw new RuntimeDaoException(new DataAccessException(e)); 809 } finally { 810 this.close(rs); 811 } 812 } 813 List<BaseBean> runSqlAsList(Map<String, Class<?>> targetTypes, String sql, Object... argList){ 814 checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty"); 815 PreparedStatement ps = null; 816 Connection connection = null; 817 try { 818 connection = this.getConnection(); 819 if(isDebug){ 820 log("runSqlAsList:" + sql); 821 } 822 ps = connection.prepareStatement(sql, 823 ResultSet.TYPE_FORWARD_ONLY, 824 ResultSet.CONCUR_READ_ONLY); 825 fillPrepareStatement(ps, argList); 826 return runPreparedStatementAsList(targetTypes,ps); 827 } catch (SQLException e) { 828 throw new RuntimeDaoException(new DataAccessException(e)); 829 } finally { 830 this.close(ps); 831 this.releaseConnection(connection); 832 } 833 } 834 835 private static Function<BaseBean,Map<String,Object>> VMAP_FUN = new Function<BaseBean,Map<String,Object>>(){ 836 837 @Override 838 public Map<String, Object> apply(BaseBean input) { 839 return input.asNameValueMap(); 840 }}; 841 842 @Override 843 public List<Map<String, Object>> runSqlForMap(Map<String,Class<?>> targetType, String sql,Object... argList) throws RuntimeDaoException{ 844 List<BaseBean> list = runSqlAsList(targetType, sql, argList); 845 if(list.isEmpty()){ 846 return Collections.emptyList(); 847 } 848 return Lists.transform(list, VMAP_FUN); 849 } 850 851 private <T> List<T> fetchOnlyOneColumn(List<BaseBean> list,final Class<T> targetType){ 852 if(list.isEmpty()){ 853 return Collections.emptyList(); 854 } 855 BaseBean row = list.get(0); 856 Object[] values = row.asValueArray(); 857 858 checkArgument(values.length == 1,"more than 1 column returned"); 859 return Lists.transform(list, new Function<BaseBean, T>() { 860 @Override 861 public T apply(BaseBean input) { 862 if(null == targetType){ 863 return input.getValue(0); 864 } 865 return targetType.cast(input.getValue(0)); 866 } 867 }); 868 } 869 <T> List<T> runPreparedStatementAsList(Class<T> targetType, PreparedStatement ps) throws RuntimeDaoException{ 870 List<BaseBean> list = runPreparedStatementAsList((Map<String,Class<?>>)null, ps); 871 return fetchOnlyOneColumn(list,targetType); 872 } 873 874 @Override 875 public List<BaseBean> runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{ 876 return runSqlAsList(Collections.<String,Class<?>>emptyMap(), sql, argList); 877 } 878 @Override 879 public <T> List<T> runSqlAsList(Class<T> targetType, String sql, Object... argList) throws RuntimeDaoException{ 880 List<BaseBean> list = runSqlAsList((Map<String,Class<?>>)null, sql, argList); 881 return fetchOnlyOneColumn(list,targetType); 882 } 883 884 @Override 885 public <T> T runSqlForValue(Class<T> targetType,String sql, Object... argList) throws RuntimeDaoException{ 886 List<T> list = runSqlAsList(targetType,sql,argList); 887 checkArgument(list.size() == 1,"more than 1 row returned for SQL: %s",sql); 888 return list.get(0); 889 } 890 <T> T runPreparedStatementForValue(Class<T> targetType,PreparedStatement ps) throws RuntimeDaoException{ 891 List<T> list = runPreparedStatementAsList(targetType,ps); 892 checkArgument(list.size() == 1,"more than 1 row returned"); 893 return list.get(0); 894 } 895 /** 896 * 填充PreparedStatement中的参数对象 897 * 898 * @param ps 899 * @param argList 900 * @throws SQLException 901 */ 902 protected static void fillPrepareStatement(PreparedStatement ps, Object[] argList) throws SQLException{ 903 if (!(argList == null || ps == null)) { 904 for (int i = 0; i < argList.length; i++) { 905 if (argList[i] instanceof ByteBuffer) { 906 ps.setBytes(i + 1, getBytesInBuffer((ByteBuffer) argList[i])); 907 } else { 908 ps.setObject(i + 1, argList[i]); 909 } 910 } 911 } 912 913 } 914 /** 915 * 填充PreparedStatement中的参数对象 916 * @param ps 917 * @param pos parameter Index, the first parameter is 1, the second is 2, ... 918 * @param value parameter value 919 * @param sqlType the SQL type code defined in java.sql.Types 920 * @throws SQLException 921 */ 922 protected static void fillPreparedStatement(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 /** 1067 * set debug flag that determine if output log message,default : false 1068 * @param isDebug flag for debug message output 1069 */ 1070 public void setDebug(boolean isDebug) { 1071 this.isDebug = isDebug; 1072 } 1073 1074}