監控Hibernate執行SQL
幾乎 80% - 85% 的資料庫效能問題是由於應用資料庫的設計或者應用程式本身的程式碼所引起的。因此良好的事務處理能力需要在設計應用程式的時候,在設計資料庫的時候就考慮到效能和伸縮性。
在我們使用Java EE開發企業級應用程式的過程中,總會涉及到系統的效能問題,並且都會與資料庫進行打交道。當我們碰到資料庫效能優化時,最有效的就是直接跟蹤SQL 語句的執行情況,對SQL 語句寫法進行優化、對索引進行優化,效果往往非常顯著。
<property name="hibernate.show_sql" value="true"/>
但是即便如此,對於引數化SQL,依然沒有辦法全部顯示出來,下述的情況想必大家都遇到過吧:
Hibernate:
select
*
from
dictgroup0_.group_id as group1_0_,
dictgroup0_.delete_flag as delete2_0_,
dictgroup0_.group_code as group3_0_,
dictgroup0_.group_desc as group4_0_,
dictgroup0_.group_name as group5_0_,
dictgroup0_.test_mode as test6_0_
from
dict_table_group dictgroup0_
where
dictgroup0_.delete_flag=?
and dictgroup0_.test_mode=? )
where
rownum <= ?
因為“?”處沒有具體數值,其實這樣的語句對開發,調整SQL語句及程式的幫助是非常有限的,拷貝到類似於PLSQL Developer或者TOAD中無法看到SQL執行的結果。
本文通過在Java IDE工具中整合SQL語句攔截工具和SQL語句顯示工具完成監控引數化SQL。
1.1 預期讀者
1、開發人員:使用Java進行應用系統服務層、資料訪問層開發。能夠使用Java IDE工具,如Eclipse、IntelliJ IDEA等。
2 工具介紹
P6Spy:本文使用P6Spy作為攔截SQL的工具,是一個可以用來在應用程式中攔截和修改資料操作語句的開源框架,相當於一個 SQL 語句的記錄器,P6Spy 用 Log4J 來記錄 JDBC 呼叫的日記資訊。本文只使用P6Log,不使用P6Outage。該軟體工作原理如下:
P6Spy 就是一個代理,它只做了一層對 JDBC 驅動的攔截,然後轉發出去,與實際的應用程式沒有任何的耦合性,除了在配置中將驅動程式改成 P6Spy 的攔截驅動外,程式其他地方並不需要做任何的改變。這層攔截器可能會給系統帶來略微的效能下降,但對程式其他方面沒有任何的影響。相對於這一點點的效能下降,相比它所帶來的好處,在開發環境中對於開發人員來說是完全可以忽略不計的。
Sqlprofiler:這個小工具可以實時地顯示資料庫查詢的情況,並能夠和P6Spy關聯。其它的資訊也會進行收集和顯示,比如:單個數據庫請求的時間、一類請求的時間以及所有請求的時間。此外,該工具能夠建立統計分析,並生成索引指令碼,不過這個特性不在本文的討論範疇
3 安裝與設定
準備工作如下:
1、 下載P6Spy,下載地址:http://sourceforge.net/projects/p6spy/
2、 下載Sqlprofiler,下載地址:http://sourceforge.net/projects/sqlprofiler/
3、 一個能夠執行Hibernate程式的Java工程。
本文使用的P6Spy版本為1.3,sqlprofiler版本為0.3。
安裝P6Spy:
1、 開啟p6spy-install.jar把p6spy.jar放到/WEB-INF/lib/ 目錄下,或IDE能夠識別jar檔案的位置
2、 把spy.properties放到Java工程的src目錄下,或者是IDE編譯後能夠拷貝到classess/目錄下的位置。
3、 將應用系統中的原來資料庫驅動名稱改為為 P6Spy 的驅動程式名稱 com.p6spy.engine.spy.P6SpyDriver ,其它的全部使用預設值。
4、 開啟配置檔案 spy.properties,找到 realdriver,把它的值改為你的應用系統的真正的資料庫驅動名稱。
# 提前載入驅動
deregisterdrivers=true
標準控制檯輸出:
appender=com.p6spy.engine.logging.appender.StdoutLogger
log4j.logger.p6spy=INFO,STDOUT
在整合Tomcat環境下Spring框架下可以忽略第3步,按以下範例修改:
<bean id="myDataSourceTarget" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@10.128.129.88:1521:invest"></property>
<property name="username" value="請填寫"></property>
<property name="password" value="請填寫"></property>
</bean>
<bean id="dataSource" class="com.p6spy.engine.spy.P6DataSource" >
<constructor-arg>
<ref local="myDataSourceTarget" />
</constructor-arg>
</bean>
執行你的應用程式或 Web 應用程式,可以在 spy.log 裡看到 P6Spy 監測到的 SQL 詳細的執行與操作的記錄資訊了,包含有完整的 SQL 執行引數,只不過所有資訊都在一行上,看著比較困難。
4 擴充套件
4.1 減少無用資料的輸出
P6Spy將其能夠攔截到的SQL資料都攔截下來,並輸出到控制檯,造成我們獲取和程式關聯資訊的困難。在除錯時主要關注,替換繫結變數後真實SQL,來檢驗業務的正確性,對其他的SQL,並不是太在意。我們通過修改配置檔案和提供擴充套件程式碼完成只輸出有用的資訊,當需要展示全部資訊的時候,也可以通過修改配置進行調整。
開啟配置檔案 spy.properties,修改如下資訊:
module.log= com.p6spy.engine.logging.P6LogFactory
改為自定義的Factory(在這裡體現了P6Spy的在設計上的靈活性),繼承自P6LogFactory,並重載:
public ResultSet getResultSet(ResultSet real, P6Statement statement, String preparedQuery, String query) throws SQLException方法
返回定製的基於P6LogResultSet的ResultSet。
在定製的ResultSet中過載:
public boolean next() throws SQLException方法,程式碼從P6LogResultSet拷貝對應程式碼,修改如下:
if (currRow > -1) {
long startTime = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer();
String comma = "";
for (Iterator itr = resultMap.keySet().iterator(); itr.hasNext();) {
String index = (String) itr.next();
buffer.append(comma);
buffer.append(index);
buffer.append(" = ");
buffer.append((String) resultMap.get(index));
comma = ", ";
}
P6Connection p6connection = (P6Connection) this.statement
.getConnection();
P6LogQuery.logElapsed(p6connection.getId(), startTime, "resultset",
preparedQuery, query);
resultMap.clear();
}
currRow++;
return passthru.next();
這時候打在控制檯的就是清理後實際執行的SQL語句了。
4.2 輸出到Sqlprofiler看格式化結果
Sqlprofiler以監聽的模式獲取P6Spy的SQL輸出,並根據P6Spy的輸出格式將SQL拆解顯示,一般來說我們並不需要關注P6Spy的SQL格式,只需要按照以下步驟進行配置:
1、 把Log4j配置為Server模式
appender=com.p6spy.engine.logging.appender.Log4jLogger
log4j.appender.SQLPROFILER_CLIENT=org.apache.log4j.net.SocketAppender
log4j.appender.SQLPROFILER_CLIENT.RemoteHost=localhost
log4j.appender.SQLPROFILER_CLIENT.Port=4445
log4j.appender.SQLPROFILER_CLIENT.LocationInfo=true
log4j.logger.p6spy=INFO,SQLPROFILER_CLIENT
注:請注意Sqlprofiler對應的埠號為4445
2、 開啟Sqlprofiler,接收SQL指令碼
下圖為Sqlprofiler正常工作下的截圖:
按照本文所述內容已經可以滿足相關擴充套件開發,若還需要原文對應程式碼,請向[email protected]訂閱。