閱讀132 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Mysql Java驅動代碼閱讀筆記及JDBC規範筆記

一前言:

以前剛開始用java連接mysql時,都是連猜帶蒙的。比如:

一個Statement,Connection何時關閉?

Connection能不能先於Statement關閉?

ResultSet是怎樣存放數據的?怎樣才能高效操縱ResultSet?

PrepareStatement到底是怎樣回事?

連接池是怎樣工作的?

二、從JDBC driver代碼分析:

在性能要求高的地方,應當使用ResultSet.get**(int)係列函數

如ResultSet.getBytes(String columnName),

則會先會調用findColumn(columnName)去查找到columnName對應的index是什麼,而在findColumn(columnName)函數中,

會檢查索引有沒有構建好了,如果還沒有則要構建columnName對應的索引。

所以,如果對性能要求,則應該使用ResultSet.getBytes(int column)函數。


PreparedStatement的緩存及重用

對於PreparedStatement,會有一個LRUCache來存放,會先到裏麵去取,拿不到再創建一個新的。

這個LRUCache的默認大小是25(太小了吧。。)。對於sql長度,如果大於256的,貌似則不緩存這個PreparedStatement。

LRUCache很簡單,代碼:

    public class LRUCache extends LinkedHashMap {
    protected int maxElements;
    public LRUCache(int maxSize) {
        super(maxSize);
        this.maxElements = maxSize;
    }
    protected boolean removeEldestEntry(Entry eldest) {
        return (size() > this.maxElements);
    }
}

LinkedHashMap在每次put和putAll後,會調用removeEldestEntry來判斷是否要移除最老的Entry。

LinkedHashMap的實現也比較簡單,裏麵用雙向鏈表把所有的Entry串在一起,當調用get時,把get所在的key移到鏈表的最前麵。

PreparedStatement是如何實現重用的:

    pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);
                                                                                                                                                                                                                                                                                                                                                                                                                                                     
if (pStmt != null) {
    ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);
    pStmt.clearParameters();
}

可見隻是設置未關閉,再清除Parameters。所以從代碼上來說,我們得到一個PreparedStatement在使用後,可以調用clearParameters,再接著使用。但是最好不要這樣做。

如果是想要執行多次,可以用addBatch和executeBatch函數來多次執行。


關於CallableStatement和ServerPreparedStatement

CallableStatement,ServerPreparedStatement繼承自PreparedStatement,實際上prepareStatement(String sql)函數返回的就是ServerPreparedStatement,LRUCache中放的也是。

CallableStatement也有一個LRUcache。

實際上當PreparedStatement調用close時,並沒有真正釋放掉資源,


Statement、Connection、ResultSet何時close

查看Statement的close函數代碼,可以發現當close時,這個Statement中所有的ResultSet也會被close。

查看Connection的close函數,當close時,這個Connection的所有Statement都會被close。

但是據JDBC4的規範,有可能當Statement關閉時,ResultSet中的資源未被完全釋放,當GC再次運行時才會回收。

所以最好就是順序關閉ResultSet,Statement,Connection。


異常處理

SQLException是可以迭代的,應該用以下的代碼來處理所有的異常:

    catch(SQLException ex) {
   while(ex != null) {
      System.out.println("SQLState:" + ex.getSQLState());
      System.out.println("Error Code:" + ex.getErrorCode());
      System.out.println("Message:" + ex.getMessage());
      Throwable t = ex.getCause();
      while(t != null) {
         System.out.println("Cause:" + t);
         t = t.getCause();
      }
      ex = ex.getNextException();
   }
}
//或者
catch(SQLException ex) {
   for(Throwable e : ex ) {
      System.out.println("Error encountered: " + e);
   }
}


在代碼Connection類的很多地方,比如void closeAllOpenStatements()函數,可以看到這樣的代碼:

    for (int i = 0; i < numStmts; i++) {
                Statement stmt = (Statement) currentlyOpenStatements.get(i);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                try {
                    stmt.realClose(false, true);
                } catch (SQLException sqlEx) {
                    postponedException = sqlEx; // throw it later, cleanup all
                    // statements first
                }
            }

感覺這個是有問題的,因為把一些異常信息給丟掉了,實際上是可以迭代的,應該調用setNextException函數把異常都加到一起。


統計數量count:

    stmt.execute(sql);
ResultSet resultSet = stmt.getResultSet();
resultSet.next();
int count = resultSet.getInt("count(*)");//or  getInt(1);


代碼中其它一些有意思的地方:

在代碼中有大量的StringBuffer,而沒有用StringBuilder,可能是要兼容JDK5的原因

配置都用一個ConnectionProperty類來表示,從這裏派生出子類,IntegerConnectionProperty,BooleanConnectionProperty什麼的。

每一個配置有都默認值,描述信息,版本等。貌似大部分在ConnectionProperties類中實現。

發現了代碼中的一個bug,沒有找到找交的地方,不是很重要就算了:)

com.mysql.jdbc.ConnectionProperties類initializeFrom(String extractedValue)函數中:

    if (extractedValue.endsWith("k")
        || extractedValue.endsWith("K")
        || extractedValue.endsWith("kb")
        || extractedValue.endsWith("Kb")
        || extractedValue.endsWith("kB")) {
    multiplier = 1024;
    int indexOfK = StringUtils.indexOfIgnoreCase(
            extractedValue, "k");
    extractedValue = extractedValue.substring(0, indexOfK);
} else if (extractedValue.endsWith("m")
        || extractedValue.endsWith("M")
        || extractedValue.endsWith("G")  //這行明顯是多出來的
        || extractedValue.endsWith("mb")
        || extractedValue.endsWith("Mb")
        || extractedValue.endsWith("mB")) {
    multiplier = 1024 * 1024;


三、從JDBC規範來看:

類型對照表:

Java類型

SQL類型

boolean

BIT

byte

TINYINT

short

SMALLINT

int

INTEGER

long

BIGINT

float

FLOAT

double

DOUBLE

byte[]

BINARYVARBINARYLONGBINARY

java.lang.String

CHARVARCHARLONGVARCHAR

java.math.BigDecimal

NUMERICDECIMAL

java.sql.Date

DATE

java.sql.Time

TIME

java.sql.Timestamp

TIMESTAMP

注意:

在JDBC中要表示日期,是使用java.sql.Date,其日期格式是"年、月、日";

要表示時間的話則是使用java.sql.Time,其時間格式為"時、分、秒";

如果要表示"時、分、秒、微秒"的格式,則是使用java.sql.Timestamp


連接池可能會自動關閉之前的connection!

要注意使用連接池時,據JDBC規範:

A single physical PooledConnection object may generate many logical

Connection objects during its lifetime. For a given PooledConnection object,

only the most recently produced logical Connection object will be valid. Any

previously existing Connection object is automatically closed when the associated

PooledConnection.getConnection method is called. Listeners (connection

pool managers) are not notified in this case.

This gives the application server a way to take a connection away from a client.

This is an unlikely scenario but may be useful if the application server is trying

to force an orderly shutdown.

所以之前得到的Connection有可能會失效!!

但是實際上我估計沒人會按這個方案來實現,因為太不友好,怎麼能別人用著你就把它悄悄地關閉掉了。

測試了proxool,當設置最大Connection數為1時,在獲取第二個Connection時,會拋出個異常。


物理連接和邏輯連接

連接池中分為物理連接和邏輯連接,對應PooledConnection類和Connection類。

PooledConnection不對用戶暴露,當PooledConnection調用close時才關閉物理連接。

Connection調用close時,並不真正關閉物理連接,隻是把它放入池中。


Statement

Statement也分兩種logical statement和physical statement。

可以實現PreparedStatement池,當Connection調用close時,並不把PreparedStatement關閉,有可能是放入到池中。

A pool of statements is associated with a PooledConnection object.

所以說一個物理連接都有一個Statement池。至於池的大小,可以通過ConnectionPoolDataSource的Properties來設置。


ConnectionPoolDataSource的屬性

ConnectionPoolDataSource 有以下的標準屬性:

maxStatements,initialPoolSize,minPoolSize,maxPoolSize,maxIdleTime,propertyCycle。



JDBC連接池的架構圖和PreparedStatement池的架構圖:








最後更新:2017-04-02 16:47:50

  上一篇:go ios 開發file&#39;s owner以及outlet與連線的理解
  下一篇:go 《洛克菲勒留給兒子的38封信》 第二封:運氣靠策劃