Pages

2013年8月16日 星期五

Java Connection.setAutoCommit使用注意事項

setAutoCommit總的來說就是保持資料的完整性,一個系統的更新操作可能要涉及多張表,需多個SQL語句進行操作
迴圈裡連續的進行插入操作,如果你在開始時設置了:conn.setAutoCommit(false);
最後才進行conn.commit(),這樣你即使插入的時候報錯,修改的內容也不會提交到資料庫,
而如果你沒有手動的進行setAutoCommit(false);
出錯時就會造成,前幾條插入,後幾條沒有
會形成髒資料~~

setAutoCommit(false)的誤用
(設定setAutoCommit(false)沒有在catch中進行Connection的rollBack操作,操作的表就會被鎖住,造成資料庫鎖死):
誤用Connection.setAutoCommit導致的資料庫鎖死問題。
系統在發佈到使用者的的伺服器了,運行一段時間偶爾出現某些功能不能正常運行,甚至不能自動恢復,嚴重導致伺服器當機,表現為伺服器不回應使用者的請求,資料庫有大量的鎖。在伺服器重啟後才能恢復正常。今天通遍的查看了一下代碼,初步分析了原因,記錄了下來,跟大家交流交流。
先看下面一段有問題的代碼:
Connection con = null;  
try{  
 con = getConnection();  
 con.setAutoCommit(false);  
 /* 
 * update USER set name=’winson’ where id=’000001’; 
 */  
 con.commit();  
}
finally{  
 if(con!=null){  
  try {  
   con.close();  
  } catch (SQLException e) {  
   e.printStackTrace();  
  }  
 }  
} 

分析:問題就出現在第4行,寫代碼的人把資料庫連接con 設置成非自動提交,但沒有在執行出現異常的時候進行回滾。如果在執行第5行的時候出現異常,con既沒有提交也沒有回滾,表USER就會被鎖住(如果oracle資料庫就是行鎖),而這個鎖卻沒有機會釋放。有人會質疑,在執行con.close()的時候不會釋放鎖嗎?因為如果應用伺服器使用了資料庫連接池,連接不會被斷開。
附:在oracle上查看鎖的方法:select * from v$lock_object或者select * from v$lock.
JDBC的api文檔是這樣對setAutoCommit方法解釋的:
Sets the connection's auto-commit mode to enableAutoCommit.
Newly created Connection objects are in auto-commit mode by default, which means that individual SQL statements are committed automatically when the statement is completed. To be able to group SQL statements into transactions and commit them or roll them back as a unit, auto-commit must be disabled by calling the method setAutoCommit with false as its argument. When auto-commit is disabled, the user must call either the commit or rollback method explicitly to end a transaction.(一定不能大意哦,如果設置成非自動提交,在最後一定要調用commit或者rollback方法)
The commit occurs when the statement completes or the next execute occurs, whichever comes first. In the case of statements returning a ResultSet object, the statement completes when the last row of the result set has been retrieved or the ResultSet object has been closed. In advanced cases, a single statement may return multiple results as well as output parameter values. In this case, the commit may occur when all results and output parameter values have been retrieved, or the commit may occur after each result is retrieved.


參考正確的寫法應該是:
Connection con = null;  
try{  
    con = getConnection();  
    con.setAutoCommit(false);  
    /* 
     * do what you want here. 
     */  
    con.commit();  
}
catch(Throwable e){  
    if(con!=null){  
  try {
   con.rollback();  
  }
  catch (SQLException e1) {  
   e1.printStackTrace();  
  }  
 }  
 throw new RuntimeException(e);  
}
finally{  
    if(con!=null){  
  try {  
   con.close();  
  } catch (SQLException e) {  
   e.printStackTrace();  
  }  
    }  
}  

這種疏忽很容易出現,但又導致非常嚴重的運行問題。所以在這裡作個提醒,以後在處理外部資源的時候一定要格外小心。今天還發現代碼中一些地方濫用synchronized關鍵字,導致系統性能受到很大的影響,處理不當跟前面提到問題一起出現,那系統就是時候over了。
另外,如果不是自己來處理事務,可能在用hibernate或者ejb等,都一定要記住在處理完之後要提交或者回滾哦。
import java.sql.DriverManager;  
import java.sql.PreparedStatement;  
import java.sql.Connection;  
import java.sql.ResultSet;  
import java.sql.SQLException;  
import java.util.List;  
  
/** 
 * 用於JDBC操作資料庫的公共類 
 */  
public class CommonSql {  
    /** 資料庫連線物件 */  
 private Connection conn;  
 /** 資料庫操作物件 */  
 private PreparedStatement ps;  
 /** 返回的資料結果集物件 */  
 private ResultSet rs;  
  
 /** 
  * 測試資料庫連接是否成功 
  * @param args 
  */  
 /** 
  * 打開資料庫連接並創建資料庫連線物件 
  * @return boolean true:連接成功,false:連接失敗 
  */  
 public boolean openConn() {  
  Boolean isPassed = false;  
  String driver = RWProperties.getProperty("driver", "com/test/config/db.properties");  
  String url = RWProperties.getProperty("url", "com/test/config/db.properties");  
  String user = RWProperties.getProperty("user", "com/test/config/db.properties");  
  String pwd = RWProperties.getProperty("pwd", "com/test/config/db.properties");  
  
  try {  
   Class.forName(driver);  
   conn = DriverManager.getConnection(url, user, pwd);  
   isPassed = true;  
  } catch (ClassNotFoundException e) {  
   closeAll();  
   e.printStackTrace();  
   System.out.println("資料庫連接失敗!");  
  } catch (Exception e) {  
   closeAll();  
   e.printStackTrace();  
   System.out.println("資料庫連接失敗!");  
  }  
  
  return isPassed;  
 }  
 /** 
  * 執行資料庫的新增和修改語句,只操作一張表 
  * @param sql 要執行的SQL語句 
  * @return boolean true:執行成功,false:執行失敗 
  */  
 /** 
     * 執行資料庫的新增和修改語句,同時操作多張表 
     * @param sql 要執行的SQL語句的字串陣列 
     * @return boolean true:執行成功,false:執行失敗 
     */  
 public boolean execUpdate(String[] sql) {  
  boolean isPassed = false;  
  // 判斷連接資料庫是否成功  
  if (openConn()) {  
   try {  
    conn.setAutoCommit(false);  
    for (int i = 0; i < sql.length; i++) {  
     ps = conn.prepareStatement(sql[i]);  
     ps.executeUpdate();  
    }  
    conn.commit();  
    isPassed = true;  
   } catch (SQLException e) {  
    try {  
     conn.rollback();  
    } catch (SQLException e1) {  
     e1.printStackTrace();  
    }  
    for (int i = 0; i < sql.length; i++) {  
     System.out.println("SQL:"+sql[i]);  
    }  
    e.printStackTrace();  
   } finally {  
    closeAll();  
   }  
  } else {  
   closeAll();  
   for (int i = 0; i < sql.length; i++) {  
    System.out.println(sql[i]);  
   }  
   System.out.println("資料庫連接失敗!");  
  }  
  
  return isPassed;  
 }  
    /** 
     * 執行資料庫的新增和修改語句,同時操作多張表 
     * @param sql 要執行的SQL語句的集合 
     * @return boolean true:執行成功,false:執行失敗 
     */  
 public boolean execUpdate(List<string> sql) {  
  boolean isPassed = false;  
  // 判斷連接資料庫是否成功  
  if (openConn()) {  
   try {  
    conn.setAutoCommit(false);  
    for (int i = 0; i < sql.size(); i++) {  
     ps = conn.prepareStatement(sql.get(i));  
     ps.executeUpdate();  
    }  
    conn.commit();  
    isPassed = true;  
   } catch (SQLException e) {  
    try {  
     conn.rollback();  
    } catch (SQLException e1) {  
     e1.printStackTrace();  
    }  
    for (int i = 0; i < sql.size(); i++) {  
     System.out.println("SQL:"+sql.get(i));  
    }  
    e.printStackTrace();  
   } finally {  
    closeAll();  
   }  
  } else {  
   closeAll();  
   for (int i = 0; i < sql.size(); i++) {  
    System.out.println(sql.get(i));  
   }  
   System.out.println("資料庫連接失敗!");  
  }  
  
  return isPassed;  
 }  
    /** 
     * 執行資料庫查詢操作 
     * @param sql 要執行的SQL語句 
     * @return ResultSet 返回查詢的結果集對象 
     */  
 public ResultSet execQuery(String sql) {  
     rs = null;  
     // 判斷連接資料庫是否成功  
  if (openConn()) {  
   try {  
    ps = conn.prepareStatement(sql);  
    rs = ps.executeQuery();  
   } catch (SQLException e) {  
    closeAll();  
    System.out.println("SQL:"+sql);  
    e.printStackTrace();  
   }  
  } else {  
   closeAll();  
   System.out.println("SQL:"+sql);  
   System.out.println("資料庫連接失敗!");  
  }  
  
  return rs;  
 }  
 /** 
  * 關閉所有資料庫連線物件 
  */  
 public void closeAll() {  
  if (conn != null) {  
   try {  
    conn.close();  
    conn = null;  
   } catch (SQLException e) {  
    e.printStackTrace();  
   }  
  }  
  if (ps != null) {  
   try {  
    ps.close();  
    ps = null;  
   } catch (SQLException e) {  
    e.printStackTrace();  
   }  
  }  
  if (rs != null) {  
   try {  
    rs.close();  
    rs = null;  
   } catch (SQLException e) {  
    e.printStackTrace();  
   }  
  }  
 }  
}  

0 意見: