今日做了個小網站,數據量不大,但當發布到虛擬主機上之后,接連不斷的遇到各種問題。
被折磨了數日后,在網上查了大量的相關資料,現總結如下。
一.項目在上傳到遠程服務器的過程中,有可能丟失文件,或文件內容發生改變。雖然幾率是很小的,但程序容不得一丁點錯誤,你懂得。。。
方法一般為:將程序打成war包上傳,或將編譯好的項目打個壓縮包(如tomcat根目錄下的項目文件)上傳。項目完成后,一定要在本地測試確保無誤,把本地測試過的傳上去。否則,不知不覺中就會出錯,而且不知道哪出錯,沒法調試。
二.配置tomcat虛擬主機連接池。
- 在tomcat配置文件server.xml下,找到
- <Context path="" docBase="" reloadable="true" />
這一行,並改為
<Context path="" docBase="" reloadable="true">
<Resource name="jdbc/sxdDS" auth="Container"
type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/數據庫名?characterEncoding=GBK&useUnicode=true"
username="用戶" password="密碼" maxActive="100" maxIdle="10"
maxWait="-1" removeAbandoned="true" removeAbandonedTimeout="20" logAbandoned="true"/>
</Context> - 相關參數說明:
- dataSource: 要連接的 datasource (通常我們不會定義在 server.xml)
defaultAutoCommit: 對於事務是否 autoCommit, 默認值為 true
defaultReadOnly: 對於數據庫是否只能讀取, 默認值為 false
driverClassName:連接數據庫所用的 JDBC Driver Class,
maxActive: 可以從對象池中取出的對象最大個數,為0則表示沒有限制,默認為8
maxIdle: 最大等待連接中的數量,設 0 為沒有限制 (對象池中對象最大個數)
minIdle:對象池中對象最小個數
maxWait: 最大等待秒數, 單位為 ms, 超過時間會丟出錯誤信息,-1為無限制
password: 登陸數據庫所用的密碼
url: 連接數據庫的 URL
username: 登陸數據庫所用的帳號
validationQuery: 驗證連接是否成功, SQL SELECT 指令至少要返回一行
removeAbandoned: 是否自我中斷, 默認是 false
removeAbandonedTimeout: 幾秒后會自我中斷, removeAbandoned 必須為 true
logAbandoned: 是否記錄中斷事件, 默認為 false
minEvictableIdleTimeMillis:大於0 ,進行連接空閑時間判斷,或為0,對空閑的連接不進行驗證;默認30分鍾
timeBetweenEvictionRunsMillis:失效檢查線程運行時間間隔,如果小於等於0,不會啟動檢查線程,默認-1
testOnBorrow:取得對象時是否進行驗證,檢查對象是否有效,默認為false
testOnReturn:返回對象時是否進行驗證,檢查對象是否有效,默認為false
testWhileIdle:空閑時是否進行驗證,檢查對象是否有效,默認為false
Ø 在使用DBCP的時候,如果使用默認值,則數據庫連接因為某種原因斷掉后,再從連接池中取得連接又不進行驗證,這時取得的連接實際上就會是無效的數據庫連接。因此為了防止獲得的數據庫連接失效,在使用的時候最好保證:
username: 登陸數據庫所用的帳號
validationQuery:SELECT COUNT(*) FROM DUAL
testOnBorrow、testOnReturn、testWhileIdle:最好都設為true
minEvictableIdleTimeMillis:大於0 ,進行連接空閑時間判斷,或為0,對空閑的連接不進行驗證
timeBetweenEvictionRunsMillis:失效檢查線程運行時間間隔,如果小於等於0,不會啟動檢查線程
Ø PS:在構造GenericObjectPool [BasicDataSource在其createDataSource () 方法中也會使用GenericObjectPool]時,會生成一個內嵌類Evictor,實現自Runnable接口。如果 timeBetweenEvictionRunsMillis大於0,每過timeBetweenEvictionRunsMillis毫秒 Evictor會調用evict()方法,檢查對象的閑置時間是否大於minEvictableIdleTimeMillis毫秒(_minEvictableIdleTimeMillis小於等於0時則忽略,默認為30分鍾),是則銷毀此對象,否則就激活並校驗對象,然后調用 ensureMinIdle方法檢查確保池中對象個數不小於_minIdle。在調用returnObject方法把對象放回對象池,首先檢查該對象是否有效,然后調用PoolableObjectFactory的passivateObject方法使對象處於非活動狀態。再檢查對象池中對象個數是否小於 maxIdle,是則可以把此對象放回對象池,否則銷毀此對象
Ø 上述特性的可設置性已在代碼中驗證,具體性能是否能實現有待實際驗證
在Tomcat的Server.xml,我們可以看看下面的這個例子:
<Resource name="lda/raw"
type="javax.sql.DataSource"
password="lda_master"
driverClassName="oracle.jdbc.driver.OracleDriver"
maxIdle="30" minIdle="2" maxWait="60000" maxActive="1000"
testOnBorrow="true" testWhileIdle="true" validationQuery="select 1 from dual"
username="lda_master" url="jdbc:oracle:thin:@192.160.100.107:15537:lcststd"/>
這樣的話,就可以避免產生Connection Reset的錯誤了.
這樣一來,就能夠解決Connect Reset的問題了。剛才說了,其實很多App Server都會有相應的配置地方,只是大型的服務器正好提供了Admin Console,上面可以顯式的配置Connection Pool,也有明顯的屬性選擇。 - 值得一提的是,
- removeAbandoned: 是否自我中斷, 默認是 false
removeAbandonedTimeout: 幾秒后會自我中斷, removeAbandoned 必須為 true
logAbandoned: 是否記錄中斷事件, 默認為 false
這三個屬性。
removeAbandoned removeAbandonedTimeout 兩個屬性配合使用可以將指定時間內沒有關閉的connection回收關閉。
設置了這幾個屬性后,連接池連接數的狀態是這樣的,如果存在沒有關閉的連接,連接池會每隔removeAbandonedTimeout設置的時間,檢測一下連接池的連接數和連接狀態,如果當連接池中活動的連接數大於maxActive設置的最大連接數時,將會啟動連接回收,這個連接回收是不會將連接回收到連接池重復利用,而是直接銷毀這些連接。在這個時候連接池的連接數會突然下降至當前需要的連接,而這些連接是連接池重新產生的,不是回收利用的。
如果持續發現這種情況,而且這些連接在數據庫總的狀態一直是sleep狀態,我個人覺得,就應該是程序中沒有正常關閉連接了。我們就需要在程序中找到這個連接。恰恰連接池提供了logAbandoned屬性,如果將其設置為true,那么在出現上述情況時,關閉的連接信息打印到日志,類似的打印日志如下:
DBCP object created 2011-04-14 11:22:18 by the following code was never closed:
java.lang.Exception
at org.apache.tomcat.dbcp.dbcp.AbandonedTrace.setStackTrace(AbandonedTrace.java:160)
at org.apache.tomcat.dbcp.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:86)
at org.apache.tomcat.dbcp.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:96)
at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:880)
at org.yeeda.costexpress.util.ConnectionPool.getConnection(Unknown Source)
at org.yeeda.costexpress.service.member.impl.MyMaterialServiceImpl.exportExcel(Unknown Source)
at org.yeeda.costexpress.servlet.member.MyMaterialServlet.toExcel(Unknown Source)
at org.yeeda.costexpress.servlet.member.MyMaterialServlet.doPost(Unknown Source)
這個相當於異常處理,程序不會報錯。但是我們可以通過異常信息找到到底是哪里的connection沒有關閉,能夠找到代碼的位置,然后仔細分析代碼,看從連接池的拿到的連接到底執行了close()方法沒有。如果出現上述那樣的情況,一般是沒有關閉的。
三、在應用程序的web.xml文件中,</web-app>標簽前,添加如下代碼:
<resource-ref>
<description>sxd Datasource example</description>
<res-ref-name>jdbc/sxdDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
四、程序中的java代碼,DBConnection.java。
public class DBConnetion {
private Connection conn = null;
private PreparedStatement psta = null;
public Connection getConnection() {
try {
Context cxt = new InitialContext();
DataSource ds=(DataSource) cxt.lookup("java:comp/env/jdbc/sxdDS");
conn=ds.getConnection();
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
public PreparedStatement getPsta(String sql) {
conn = this.getConnection();// 得到連接
try {
psta = conn.prepareStatement(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return psta;
}
/**
* 關閉Connetion連接
* @param conn
* @throws SQLException
* @throws SQLException
*/
public void closeConnetion(Connection conn){
if (conn != null) {
try {
conn.close();//使用連接池后,colse()被重寫,此時並沒關閉,而是放回了連接池中
//System.out.println("連接是否關閉: "+conn.isClosed());
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(conn!=null){
try {
conn.close() ;
System.out.println("連接是否關閉: "+conn.isClosed());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 關閉所有連接
* @param conn
* @param psta
* @param rs
*/
public void closeAll(Connection conn, PreparedStatement psta, ResultSet rs) {
try {
if (rs != null) {
rs.close();
rs = null;
}
if (psta != null) {
psta.close();
psta = null;
}
if (conn != null) {
this.closeConnetion(conn);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}