垃圾回收器(garbage collection,簡稱GC)回收不再被使用的物件的記憶體空間
這點是自 C\C++ 以來,高階程式語言很大的改變之一
使用 C\C++ 語言撰寫時,memory leak 是最常發生的嚴重錯誤之一
如果使用完的物件沒有被清除,記憶體空間的資源也無法被釋放
Java的垃圾回收器能回收不再被使用的記憶體空間
但是有些重點必須要注意,垃圾回收器只能回收經由new產生的物件
被占用的特殊記憶體,例如從外部的其他語言獲得的物件,清除方式就不能透過GC進行
針對這類的物件,Java提供了 filanlize() method來因應
class 檔中可以定義 finalize() method
當垃圾回收器要開始釋放被物件占用的記憶體時,會先呼叫 finalize()函式
接著在下一次的垃圾回收動作發生時才會回收該記憶體空間
所以一般常見 finalize() 的定義是在垃圾回收時先執行某些重要的清理工作
有個可能比較容易引起誤會的地方是 finalize() 並不是C++的 destructor
差異點在於C++物件一定會被摧毀,所以destrucor絕對會被喚起
但是Java物件卻不一定會被GC回收
GC的執行條件是記憶體接近用完
假如直到程式執行結束都沒有啟動GC,記憶體空間會在程式終止時一次歸還
GC的執行會給程式帶來額外的負擔,能不執行的話自然是最好
因此 finalize() 並不一定會被呼叫
如果有一定要執行的清除動作,必須自行撰寫函式處理
通常影像處理類的任務比較有這類需求
finalize()真正適合使用的時機就只有要清除 non-Java 的物件
2010年9月28日 星期二
2010年9月26日 星期日
JDBC 簡介與基礎使用
JDBC(Java Database Connectivity)是Java規範資料庫存取的API
資料庫廠商依據Java提供的介面進行實作,開發者則依照JDBC的標準進行操作
如此設計的因素是每家廠商提供用來操作的 API 並不相同
開發者透過JDBC的介面操作的好處是,若有更換資料庫的要求,可以避免大量修改程式碼
只需要替換實作廠商的資料庫驅動
也就是寫一個Java程式就能應付所有的資料庫
當然實作時,有時為了使用資料庫的特定功能,仍有修改程式碼的必要
廠商實作JDBC驅動程式的方式有四種類型
1.JDBC-ODBC Bridge Driver
ODBC(Open DataBase Connectivity)是微軟所主導的資料庫連結標準
所以也很常在 Microsoft 的系統上使用
這類的驅動程式將JDBC的呼叫轉換成對ODBC的呼叫
但因為兩者並非一對一的對應,所以在功能及存取速度上都有所限制
2.Native API Driver
載入由廠商提供的 C\C++撰寫成的原生函式庫
直接將JDBC的呼叫轉成資料庫中的相關 API 呼叫
因為直接呼叫 API 的關係,操作速度比第一種快(但比不上後兩種)
但因為驅動程式作用的對象只限制在原生資料庫
沒有做到JDBC的跨資料庫的目標
3.JDBC-Net Driver
這類型的驅動程式提供了用戶端網路API,JDBC驅動透過Socket呼叫伺服器上的中介元件
由該元件將JDBC的呼叫轉換成 API 呼叫
客戶端的驅動程式與中介元件間的協定是固定的,所以不需要載入廠商的函式庫
也因此在更換資料庫時,只需修改中介元件即可
此種方式也是相當有彈性的架構
4.Native Protocol Driver
這類的驅動程式使用Socket,直接在用戶端和資料庫間通訊
也因為這是最直接的實作方式,通常具有最快的存取速度
但針對不同的資料庫需使用不同的驅動程式
為不需要JDBC-Net Driver那般彈性時的選擇,也是最常見的驅動程式類型
接下來的操作範例以第四種為主,並以MySQL為操作範例
MySQL提供的JDBC驅動程式是 mysql-connector-java ,可在其官網中找到
首先來個範例:
SQLController.java=======
package DB;
import java.sql.*;
public class SQLController {
private Connection con = null; //Database objects
private Statement stat = null; //SQL code to execute
private ResultSet res = null; //result
private PreparedStatement pst = null;
public SQLController(){
}
public SQLController(String SQLType, String SQLDriver, String daatabase, String account, String password){
String conInfo = "jdbc:" + SQLType + "://localhost:3306/" + daatabase + "?useUnicode=true&characterEncoding=UTF-8";
try {
//register driver
Class.forName(SQLDriver);
con = DriverManager.getConnection(conInfo, account, password);
}
catch(ClassNotFoundException e){
System.out.println("DriverClassNotFound :"+e.toString());
}
//prevent SQL code error
catch(SQLException x) {
System.out.println("Exception :"+x.toString());
}
}
//clear objects in order to close database
private void Close()
{
try{
if(res!=null){ res.close();}
if(stat!=null){ stat.close();}
if(pst!=null){ pst.close(); }
if(con!=null){ con.close(); }
}
catch(SQLException e){
System.out.println("Close Exception :" + e.toString());
}
}
}
網路上可以找到寫成JavaBean格式的寫法,搭配EL也能正常地使用資料庫
範例將資料庫連線寫成Java物件,寫法依個人喜好而定
連結資料庫前必須先載入JDBC驅動程式
透過Class.forName(),範例程式動態載入 com.mysql.jdbc.Driver 類別至 DriverManager
類別會自動向 DriverManager 做註冊
生成連線時,DriverManager就會使用該驅動建立Connection實例
JDBC URL定義連線資料庫的協定、子協定、資料來源識別
型態是 協定:子協定:識別資料來源
使用MySQL的JDBC URL就會是 jdbc:mysql://localhost:3306/資料庫名稱?參數=值&...
識別資料裡的 localhost:3306 連結 MySQL的連結阜
使用中文時需附加參數 useUnicode=true&characterEncoding=UTF8,指定用UTF8編碼
建立Connection實例時我們必須提供JDBC URL
DriverManager.getConnection() method有兩種引入參數的版本
單一 參數的版本必須將資料庫的帳密資料附加在JDBC URL裡,再引入URL
另一個版本就是本例使用的 DriverManager.getConnection(conInfo, account, password);
將帳號、密碼當作呼叫時的參數傳入
SQLException是資料庫處理過程發生異常時會被丟出的例外
資料庫的使用過程中都必須做這個例外處理的準備
Connection是連接資料庫的代表物件
執行SQL還需要取得 java.sql.Statement 物件
下面的範例示範實際執行SQL碼的撰寫方式,並搭配DCBP與JNDI來進行:
DatabaseBean.java======
package tw.vencs;
import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class DatabaseBean {
private DataSource dataSource = null;
public DatabaseBean(){
}
public void setConSource(DataSource dataSource){
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
}
class SignInBean extends DatabaseBean{
public boolean isSignIn(String account, String password){
Connection con = null;
Statement sta = null;
ResultSet res = null;
boolean checkOK = false;
try{
con = getConnection();
sta = con.createStatement();
res = sta.executeQuery("SELECT * FROM `member` WHERE account = '"+ account
+"' AND password = '"+ password +"' ");
//如果有找到與輸入的帳密相同的資料,允許會員登入
while(res.next()){
checkOK = true;
}
}catch(SQLException ex){
Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}finally{
try{
if(res != null){ res.close();}
if(sta != null){ sta.close();}
if(con != null){ con.close();}
}catch(SQLException ex){
Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}
}
return checkOK;
}
}
SignInConfirmer.java======
package tw.vencs;
import java.io.IOException;
import javax.servlet.http.*;
import javax.sql.DataSource;
import com.colid.DatabaseBean;
public class SignInConfirmer extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
String account = request.getParameter("account");
String password = request.getParameter("password");
String cookieMaker = request.getParameter("cookieMaker");
//get DBCP connection resource from InitialListener
DataSource dataSource = (DataSource)getServletContext().getAttribute("conData");
SignInBean confirmer = new SignInBean();
confirmer.setConSource(dataSource);
if(confirmer.isSignIn(account, password)){
HttpSession session = request.getSession();
session.setAttribute("account", account);
if(cookieMaker.equals("yes")){
int life = 30*24*60*60; //set Life of Cookie to 30 days
Cookie cookie = new Cookie("account", account);
cookie.setMaxAge(life);
response.addCookie(cookie);
}
//encode URL if user's COOKIE function is closed
response.sendRedirect(response.encodeURL("index.jsp"));
}
else{
getServletContext().setAttribute("warning", true);
response.sendRedirect(response.encodeURL("login.jsp"));
}
}
}
範例是一隻處理登入狀態(會話管理)的 Servlet 程式
DBCP 與 JNDI 的部分可以去找那篇的筆記來看
執行SQL碼的 Statement 物件從連線物件中取得,程式碼如: con.createStatement()
Statement 物件常用的方法有 executeUpdate()、executeQuery()和execute()
executeUpdate()主要用來執行 CREATE、INSERT、 DROP等改變資料庫內容的SQL碼
執行完畢後回傳 int 數值,表示資料變動的筆數
executeQuery() 則如字面所示,用來得到 SELECT等SQL碼查詢到的結果
執行完畢後回傳 java.sql.ResultSet 物件
ResultSet 的next() method 會回傳 boolean值來表示是否有下一筆資料
如果確實有資料的話可以使用 getXXX()的方式取得該筆資料,XXX為資料型別
查詢結果會從1開始作為欄位的索引值
例如: int id = res.getINT(1)
execute() 這個method則用在無法事先得知要執行的SQL碼的場合
假使回傳的結果是 true,則可以用getResultSet() 取得 ResultSet 物件
回傳 false 則可以用 getUpdateCount() 得知更新資料筆數
最後提到上面的範例沒有展示到的 PreparedStatement
假使有動態資料的情況下
像上述範例以 + 運算子結合字串組成SQL碼會比較麻煩
字串每使用一次 + ,其實也是重新製作一個String物件
如此的字串使用也造成效能上的負擔
PreparedStatement 的使用與 Statement 很相似,如改寫範例後為:
PreparedStatement pre = con.prepareStatement("SELECT * FROM `member` WHERE
account =? AND password =?");
pre.setString(1, account); //setString() 輸入的參數可以避免 Injection Attack
pre.setString(2, password);
pre.executeUpdate();
pre.clearParameters();
將SQL碼中要動態輸入的部分以 ? 運算子代替,然後設置所要輸入的變數
con.prepareStatement() 會建立一個預先編譯的SQL實例
它也同樣可以使用executeUpdate()、executeQuery() method
當查詢完畢之後,clearParameters() 清除所輸入的參數
留下來的實例卻可以填上別的參數繼續使用,而不用重新再建立SQL實例
會被頻繁使用的SQL碼實例適合用這種方式建立
setString() 輸入的參數會被視為純粹的String物件,最後在SQL碼內的形式是包覆在引號內
若是需要填入SQL碼內的是數字,則可以用 setInt() 以免出現SQL碼錯誤的情形
PreparedStatement 會是效能與安全性考量的好選擇
資料庫廠商依據Java提供的介面進行實作,開發者則依照JDBC的標準進行操作
如此設計的因素是每家廠商提供用來操作的 API 並不相同
開發者透過JDBC的介面操作的好處是,若有更換資料庫的要求,可以避免大量修改程式碼
只需要替換實作廠商的資料庫驅動
也就是寫一個Java程式就能應付所有的資料庫
當然實作時,有時為了使用資料庫的特定功能,仍有修改程式碼的必要
廠商實作JDBC驅動程式的方式有四種類型
1.JDBC-ODBC Bridge Driver
ODBC(Open DataBase Connectivity)是微軟所主導的資料庫連結標準
所以也很常在 Microsoft 的系統上使用
這類的驅動程式將JDBC的呼叫轉換成對ODBC的呼叫
但因為兩者並非一對一的對應,所以在功能及存取速度上都有所限制
2.Native API Driver
載入由廠商提供的 C\C++撰寫成的原生函式庫
直接將JDBC的呼叫轉成資料庫中的相關 API 呼叫
因為直接呼叫 API 的關係,操作速度比第一種快(但比不上後兩種)
但因為驅動程式作用的對象只限制在原生資料庫
沒有做到JDBC的跨資料庫的目標
3.JDBC-Net Driver
這類型的驅動程式提供了用戶端網路API,JDBC驅動透過Socket呼叫伺服器上的中介元件
由該元件將JDBC的呼叫轉換成 API 呼叫
客戶端的驅動程式與中介元件間的協定是固定的,所以不需要載入廠商的函式庫
也因此在更換資料庫時,只需修改中介元件即可
此種方式也是相當有彈性的架構
4.Native Protocol Driver
這類的驅動程式使用Socket,直接在用戶端和資料庫間通訊
也因為這是最直接的實作方式,通常具有最快的存取速度
但針對不同的資料庫需使用不同的驅動程式
為不需要JDBC-Net Driver那般彈性時的選擇,也是最常見的驅動程式類型
接下來的操作範例以第四種為主,並以MySQL為操作範例
MySQL提供的JDBC驅動程式是 mysql-connector-java ,可在其官網中找到
首先來個範例:
SQLController.java=======
package DB;
import java.sql.*;
public class SQLController {
private Connection con = null; //Database objects
private Statement stat = null; //SQL code to execute
private ResultSet res = null; //result
private PreparedStatement pst = null;
public SQLController(){
}
public SQLController(String SQLType, String SQLDriver, String daatabase, String account, String password){
/* 範例裡傳入的參數
SQLType = "mysql"
SQLDriver = "com.mysql.jdbc.Driver"
其餘的則按資料庫的設定傳入*/
String conInfo = "jdbc:" + SQLType + "://localhost:3306/" + daatabase + "?useUnicode=true&characterEncoding=UTF-8";
try {
//register driver
Class.forName(SQLDriver);
con = DriverManager.getConnection(conInfo, account, password);
}
catch(ClassNotFoundException e){
System.out.println("DriverClassNotFound :"+e.toString());
}
//prevent SQL code error
catch(SQLException x) {
System.out.println("Exception :"+x.toString());
}
}
//clear objects in order to close database
private void Close()
{
try{
if(res!=null){ res.close();}
if(stat!=null){ stat.close();}
if(pst!=null){ pst.close(); }
if(con!=null){ con.close(); }
}
catch(SQLException e){
System.out.println("Close Exception :" + e.toString());
}
}
}
網路上可以找到寫成JavaBean格式的寫法,搭配EL也能正常地使用資料庫
範例將資料庫連線寫成Java物件,寫法依個人喜好而定
連結資料庫前必須先載入JDBC驅動程式
透過Class.forName(),範例程式動態載入 com.mysql.jdbc.Driver 類別至 DriverManager
類別會自動向 DriverManager 做註冊
生成連線時,DriverManager就會使用該驅動建立Connection實例
JDBC URL定義連線資料庫的協定、子協定、資料來源識別
型態是 協定:子協定:識別資料來源
使用MySQL的JDBC URL就會是 jdbc:mysql://localhost:3306/資料庫名稱?參數=值&...
識別資料裡的 localhost:3306 連結 MySQL的連結阜
使用中文時需附加參數 useUnicode=true&characterEncoding=UTF8,指定用UTF8編碼
建立Connection實例時我們必須提供JDBC URL
DriverManager.getConnection() method有兩種引入參數的版本
單一 參數的版本必須將資料庫的帳密資料附加在JDBC URL裡,再引入URL
另一個版本就是本例使用的 DriverManager.getConnection(conInfo, account, password);
將帳號、密碼當作呼叫時的參數傳入
SQLException是資料庫處理過程發生異常時會被丟出的例外
資料庫的使用過程中都必須做這個例外處理的準備
Connection是連接資料庫的代表物件
執行SQL還需要取得 java.sql.Statement 物件
下面的範例示範實際執行SQL碼的撰寫方式,並搭配DCBP與JNDI來進行:
DatabaseBean.java======
package tw.vencs;
import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class DatabaseBean {
private DataSource dataSource = null;
public DatabaseBean(){
}
public void setConSource(DataSource dataSource){
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
}
class SignInBean extends DatabaseBean{
public boolean isSignIn(String account, String password){
Connection con = null;
Statement sta = null;
ResultSet res = null;
boolean checkOK = false;
try{
con = getConnection();
sta = con.createStatement();
res = sta.executeQuery("SELECT * FROM `member` WHERE account = '"+ account
+"' AND password = '"+ password +"' ");
//如果有找到與輸入的帳密相同的資料,允許會員登入
while(res.next()){
checkOK = true;
}
}catch(SQLException ex){
Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}finally{
try{
if(res != null){ res.close();}
if(sta != null){ sta.close();}
if(con != null){ con.close();}
}catch(SQLException ex){
Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}
}
return checkOK;
}
}
SignInConfirmer.java======
package tw.vencs;
import java.io.IOException;
import javax.servlet.http.*;
import javax.sql.DataSource;
import com.colid.DatabaseBean;
public class SignInConfirmer extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
String account = request.getParameter("account");
String password = request.getParameter("password");
String cookieMaker = request.getParameter("cookieMaker");
//get DBCP connection resource from InitialListener
DataSource dataSource = (DataSource)getServletContext().getAttribute("conData");
SignInBean confirmer = new SignInBean();
confirmer.setConSource(dataSource);
if(confirmer.isSignIn(account, password)){
HttpSession session = request.getSession();
session.setAttribute("account", account);
if(cookieMaker.equals("yes")){
int life = 30*24*60*60; //set Life of Cookie to 30 days
Cookie cookie = new Cookie("account", account);
cookie.setMaxAge(life);
response.addCookie(cookie);
}
//encode URL if user's COOKIE function is closed
response.sendRedirect(response.encodeURL("index.jsp"));
}
else{
getServletContext().setAttribute("warning", true);
response.sendRedirect(response.encodeURL("login.jsp"));
}
}
}
範例是一隻處理登入狀態(會話管理)的 Servlet 程式
DBCP 與 JNDI 的部分可以去找那篇的筆記來看
執行SQL碼的 Statement 物件從連線物件中取得,程式碼如: con.createStatement()
Statement 物件常用的方法有 executeUpdate()、executeQuery()和execute()
executeUpdate()主要用來執行 CREATE、INSERT、 DROP等改變資料庫內容的SQL碼
執行完畢後回傳 int 數值,表示資料變動的筆數
executeQuery() 則如字面所示,用來得到 SELECT等SQL碼查詢到的結果
執行完畢後回傳 java.sql.ResultSet 物件
ResultSet 的next() method 會回傳 boolean值來表示是否有下一筆資料
如果確實有資料的話可以使用 getXXX()的方式取得該筆資料,XXX為資料型別
查詢結果會從1開始作為欄位的索引值
例如: int id = res.getINT(1)
execute() 這個method則用在無法事先得知要執行的SQL碼的場合
假使回傳的結果是 true,則可以用getResultSet() 取得 ResultSet 物件
回傳 false 則可以用 getUpdateCount() 得知更新資料筆數
最後提到上面的範例沒有展示到的 PreparedStatement
假使有動態資料的情況下
像上述範例以 + 運算子結合字串組成SQL碼會比較麻煩
字串每使用一次 + ,其實也是重新製作一個String物件
如此的字串使用也造成效能上的負擔
PreparedStatement 的使用與 Statement 很相似,如改寫範例後為:
PreparedStatement pre = con.prepareStatement("SELECT * FROM `member` WHERE
account =? AND password =?");
pre.setString(1, account); //setString() 輸入的參數可以避免 Injection Attack
pre.setString(2, password);
pre.executeUpdate();
pre.clearParameters();
將SQL碼中要動態輸入的部分以 ? 運算子代替,然後設置所要輸入的變數
con.prepareStatement() 會建立一個預先編譯的SQL實例
它也同樣可以使用executeUpdate()、executeQuery() method
當查詢完畢之後,clearParameters() 清除所輸入的參數
留下來的實例卻可以填上別的參數繼續使用,而不用重新再建立SQL實例
會被頻繁使用的SQL碼實例適合用這種方式建立
setString() 輸入的參數會被視為純粹的String物件,最後在SQL碼內的形式是包覆在引號內
若是需要填入SQL碼內的是數字,則可以用 setInt() 以免出現SQL碼錯誤的情形
PreparedStatement 會是效能與安全性考量的好選擇
2010年9月14日 星期二
Tomcat 6.0 環境配置 DBCP 與 JNDI 使用
取得資料庫連線的過程必須經過網路連線、協定交換等多個步驟
過程中的耗費在單機使用者上也許感覺不太出來
但放大到使用者眾多的網路應用程式,開開關關的連線就是不可小看的浪費
改善連線效能的方法之一就是建立儲存了連線實例的連接池
盡量使用已經開啟的連線,重複使用以減低資源上的浪費
Tomcat的DBCP連接池機制提供了連接池建立的功能
JNDI 處理 Java EE 平台上資源的分散式運算的協調與資源上查詢
JNDI 更詳細的介紹可以在 SUN 的文件裡看到
接著搭配兩者來示範建立連接池
Java EE將資料庫處理的相關行為規範在 javax.sql.DataSource 介面
取得Connecion實例等的處理會由實作介面的物件來處理,我們只需取得DataSource實例即可
範例以建立一個能被封裝散佈的WAR檔的Web Application為例:
context.xml=========
<?xml version="1.0" encoding="UTF-8"?>
<Resource name="jdbc/sql" auth="Container" type="javax.sql.DataSource"
maxActive="60" maxIdle="20" maxWait="10000"
username="***" password="***" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/***?useUnicode=true&characterEncoding=UTF8" />
</Context>
web.xml===========
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
.......
<resource-ref>
<res-ref-name>jdbc/sql</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
....
</web-app>
DatabaseBean.java====
package tw.vencs;
import java.sql.*;
import java.util.logging.*;
import javax.naming.*;
import javax.sql.DataSource;
public class DatabaseBean {
private DataSource dataSource;
public DatabaseBean(){
try{
Context initContext = new InitialContext();
//java:comp/env 表示應用程式環境項目
Context envContext = (Context)initContext.lookup("java:/comp/env");
//查找 jdbc/sql 對應的 DataSource 物件
dataSource = (DataSource)envContext.lookup("jdbc/sql");
}catch(NamingException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}
}
public boolean isConnectedOK(){
boolean ok = false;
Connection conn = null;
try{
// 透過 DataSource 物件取得連線
conn = dataSource.getConnection();
if(!conn.isClosed()){
ok = true;
}
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}finally{
if(conn != null){
try{
conn.close();
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return ok;
}
}
conn.jsp========
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="db" class="tw.vencs.DatabaseBean" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>db Test</title>
</head>
<body>
<c:choose>
<c:when test="${db.connectedOK }">連線成功</c:when>
<c:otherwise>失敗</c:otherwise>
</c:choose>
</body>
</html>
context.xml需要放置在Web應用程式根目錄的META-INF目錄裡
<Context>裡的 path 填上相對於容器網路根目錄的應用程式目錄位置
<Resource>標籤裡有許多與連線設定相關的屬性
name 存取辨識用的 JNDI 名稱
auth 認證方式,一般為Container
type 數據集的型別,使用標準的javax.sql.DataSource
maxActive 連接池中最多的Connection實例數量,0為不限制
maxIdle 最大空閒連接數,也就是一個連接池裡最少要有的Connection實例數量
maxWait 等待可用的Connection實例,最大的等待時間,-1為不限制
username 資料庫使用者名稱
password 資料庫密碼
driverClassName JDBC Driver 類別名稱
url JDBC URL,注意因資訊寫在xml裡,"&"需改成"&"才符合XML規範
web.xml裡的設定提供 JNDI 查找時需要的環境資訊
減少物件建立所需要設置的參數的麻煩
DatabaseBean 物件裡不會看見資料庫連結的實體位置、連接阜等資訊
那些資訊由context.xml設定,只須給資料庫管理人員管理
程式部署後,Tomcat根據放在 META-INF 裡的context.xml設定,尋找指定的JDBC Driver
所以驅動程式也必須放在程式的Build path裡(如Tomcat的lib資料夾)
conn.jsp只提供了連線的訊息,資料庫的操作還須依照JDBC的規範來
過程中的耗費在單機使用者上也許感覺不太出來
但放大到使用者眾多的網路應用程式,開開關關的連線就是不可小看的浪費
改善連線效能的方法之一就是建立儲存了連線實例的連接池
盡量使用已經開啟的連線,重複使用以減低資源上的浪費
Tomcat的DBCP連接池機制提供了連接池建立的功能
JNDI 處理 Java EE 平台上資源的分散式運算的協調與資源上查詢
JNDI 更詳細的介紹可以在 SUN 的文件裡看到
接著搭配兩者來示範建立連接池
Java EE將資料庫處理的相關行為規範在 javax.sql.DataSource 介面
取得Connecion實例等的處理會由實作介面的物件來處理,我們只需取得DataSource實例即可
範例以建立一個能被封裝散佈的WAR檔的Web Application為例:
context.xml=========
<?xml version="1.0" encoding="UTF-8"?>
<!-- 應用程式目錄是Demo -->
<Context antiJARLocking="true" path="/Demo"><Resource name="jdbc/sql" auth="Container" type="javax.sql.DataSource"
maxActive="60" maxIdle="20" maxWait="10000"
username="***" password="***" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/***?useUnicode=true&characterEncoding=UTF8" />
</Context>
web.xml===========
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
.......
<resource-ref>
<res-ref-name>jdbc/sql</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
....
</web-app>
DatabaseBean.java====
package tw.vencs;
import java.sql.*;
import java.util.logging.*;
import javax.naming.*;
import javax.sql.DataSource;
public class DatabaseBean {
private DataSource dataSource;
public DatabaseBean(){
try{
Context initContext = new InitialContext();
//java:comp/env 表示應用程式環境項目
Context envContext = (Context)initContext.lookup("java:/comp/env");
//查找 jdbc/sql 對應的 DataSource 物件
dataSource = (DataSource)envContext.lookup("jdbc/sql");
}catch(NamingException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}
}
public boolean isConnectedOK(){
boolean ok = false;
Connection conn = null;
try{
// 透過 DataSource 物件取得連線
conn = dataSource.getConnection();
if(!conn.isClosed()){
ok = true;
}
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}finally{
if(conn != null){
try{
conn.close();
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return ok;
}
}
conn.jsp========
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="db" class="tw.vencs.DatabaseBean" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>db Test</title>
</head>
<body>
<c:choose>
<c:when test="${db.connectedOK }">連線成功</c:when>
<c:otherwise>失敗</c:otherwise>
</c:choose>
</body>
</html>
context.xml需要放置在Web應用程式根目錄的META-INF目錄裡
<Context>裡的 path 填上相對於容器網路根目錄的應用程式目錄位置
<Resource>標籤裡有許多與連線設定相關的屬性
name 存取辨識用的 JNDI 名稱
auth 認證方式,一般為Container
type 數據集的型別,使用標準的javax.sql.DataSource
maxActive 連接池中最多的Connection實例數量,0為不限制
maxIdle 最大空閒連接數,也就是一個連接池裡最少要有的Connection實例數量
maxWait 等待可用的Connection實例,最大的等待時間,-1為不限制
username 資料庫使用者名稱
password 資料庫密碼
driverClassName JDBC Driver 類別名稱
url JDBC URL,注意因資訊寫在xml裡,"&"需改成"&"才符合XML規範
web.xml裡的設定提供 JNDI 查找時需要的環境資訊
減少物件建立所需要設置的參數的麻煩
DatabaseBean 物件裡不會看見資料庫連結的實體位置、連接阜等資訊
那些資訊由context.xml設定,只須給資料庫管理人員管理
程式部署後,Tomcat根據放在 META-INF 裡的context.xml設定,尋找指定的JDBC Driver
所以驅動程式也必須放在程式的Build path裡(如Tomcat的lib資料夾)
conn.jsp只提供了連線的訊息,資料庫的操作還須依照JDBC的規範來
2010年9月8日 星期三
Eclipse 環境下Tomcat 發生did not find a matching property 錯誤解決方法
環境配置:
eclipse-jee-helios-win32-x86_64.zip
apache-tomcat-6.0.29
錯誤訊息大致如下:
[SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.j2ee.server:demo' did not find a matching property.
解決方法:
1.對 Servers 欄位右鍵取得 properties-> General
點擊Switch Location 讓 Location = /Servers/Tomcat v6.0 Server at localhost.server
2.Project Explorer -> Servers 打開此目錄下的 Tomcat v6.0 Server at localhost.server
3.Server Options -> Check "Publish module contexts to separate XML files"
eclipse-jee-helios-win32-x86_64.zip
apache-tomcat-6.0.29
錯誤訊息大致如下:
[SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.j2ee.server:demo' did not find a matching property.
解決方法:
1.對 Servers 欄位右鍵取得 properties-> General
點擊Switch Location 讓 Location = /Servers/Tomcat v6.0 Server at localhost.server
2.Project Explorer -> Servers 打開此目錄下的 Tomcat v6.0 Server at localhost.server
3.Server Options -> Check "Publish module contexts to separate XML files"
APR Tomcat Native library 擴充 Tomcat 性能
最近遇到 eclipse 跳出了個警告訊息出來的情況:
查了一下解決的方法,發現這個問題發生的原因是Tomcat
Tomcat從 5.5版本後增加了APR技術(Apache Portable Runtime)
這是個用C語言寫成的文件包,目的在於提高Tomcat的服務性能
使Tomcat將不僅僅擔任一個容器的功能,而能成為一個一般的web服務器
預設的Tomcat檔案並沒有這個文件包
解決的方法就是到官網下載這個文件包,在官網中的Download區裡選擇 Tomcat Native
至 Tomcat Native Connector 選擇 You may download them from HERE 下載適合的版本
將下載到的 tcnative-1.dll 放置在 JAVA_HOME 環境變數所設定的目錄底下
例如 C:\Program Files\Java\jdk1.7.0_09\bin
這樣問題就解決了
The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found ....
查了一下解決的方法,發現這個問題發生的原因是Tomcat
Tomcat從 5.5版本後增加了APR技術(Apache Portable Runtime)
這是個用C語言寫成的文件包,目的在於提高Tomcat的服務性能
使Tomcat將不僅僅擔任一個容器的功能,而能成為一個一般的web服務器
預設的Tomcat檔案並沒有這個文件包
解決的方法就是到官網下載這個文件包,在官網中的Download區裡選擇 Tomcat Native
至 Tomcat Native Connector 選擇 You may download them from HERE 下載適合的版本
將下載到的 tcnative-1.dll 放置在 JAVA_HOME 環境變數所設定的目錄底下
例如 C:\Program Files\Java\jdk1.7.0_09\bin
這樣問題就解決了
2010年9月3日 星期五
程式物件與記憶體的配置方式
物件在配置時能被儲存在五個地方:
1.暫存器(Registers) 2.堆疊(Stack) 3.堆積(Heap)
4.常數暫存空間(Constant storage) 5.Non-RAM儲存空間
以下對這五種方式做說明
1.暫存器(Registers):
暫存器內建於CPU內,所以是速度最快的儲存位置
但因為數量有限,所以這裡的物件配置都由編譯器來決定
自由度比較高的C或C++也僅能向編譯器建議暫存器的配置
2.堆疊(Stack):
堆疊的位置位於一般的RAM裡,處理器經由其指標(stack pointer)提供直接支援來運作
當程式配置一塊記憶體時,stack指標會往後移動;釋放記憶體則會讓指標往前移回
堆疊很快也很有效率,速度僅次於暫存器
因為Java的編譯器必須自行建立移動stack pointer的程式碼
所以必須要完全掌握存在stack裡的資料的實際大小與存活時間
因為這個限制,儘管物件的reference可以儲存於其上,但一般的Java物件並不能如此
3.堆積(Heap):
Heap是通用性質的記憶體儲存空間,也位在RAM裡
與stack不同的是,它不需要事先知道必須配置於其上的物件大小與存活時間
所以它有了相當的彈性
Heap也是Java放置一般Java物件的地方
每當在程式中執行 new 指令時,就會在heap上配置
當然有彈性的另一面是配置的時間較stack來的長
4.常數暫存空間(Constant storage):
因為常數值不會改變,所以有時候會被放在ROM(read-only memory)或內嵌式系統中
內嵌式系統的例子舉Java的string pool,它是特殊的儲存空間
5.Non-RAM儲存空間:
如果資料可以獨立於程式外,這類物件通常被儲存於磁碟裡
這一類儲存空間的特色是,可以將物件轉換為可儲存於其他媒介的形式
必要時也可以還原成儲存於RAM中的一般物件
我們稱呼在撰寫程式過程中常用到的類別為基本型別(primitive types)
要特別的處理它是因為透過 new 來配置這種極小、簡單的變數於heap上,效率不佳
Java採取C/C++的方式來處理,也就是不用 new 來配置其空間
這產生了"automatic"變數(不再使用reference型態)
這類變數直接存放資料值,並配置在stack上以取得較佳的效率
1.暫存器(Registers) 2.堆疊(Stack) 3.堆積(Heap)
4.常數暫存空間(Constant storage) 5.Non-RAM儲存空間
以下對這五種方式做說明
1.暫存器(Registers):
暫存器內建於CPU內,所以是速度最快的儲存位置
但因為數量有限,所以這裡的物件配置都由編譯器來決定
自由度比較高的C或C++也僅能向編譯器建議暫存器的配置
2.堆疊(Stack):
堆疊的位置位於一般的RAM裡,處理器經由其指標(stack pointer)提供直接支援來運作
當程式配置一塊記憶體時,stack指標會往後移動;釋放記憶體則會讓指標往前移回
堆疊很快也很有效率,速度僅次於暫存器
因為Java的編譯器必須自行建立移動stack pointer的程式碼
所以必須要完全掌握存在stack裡的資料的實際大小與存活時間
因為這個限制,儘管物件的reference可以儲存於其上,但一般的Java物件並不能如此
3.堆積(Heap):
Heap是通用性質的記憶體儲存空間,也位在RAM裡
與stack不同的是,它不需要事先知道必須配置於其上的物件大小與存活時間
所以它有了相當的彈性
Heap也是Java放置一般Java物件的地方
每當在程式中執行 new 指令時,就會在heap上配置
當然有彈性的另一面是配置的時間較stack來的長
4.常數暫存空間(Constant storage):
因為常數值不會改變,所以有時候會被放在ROM(read-only memory)或內嵌式系統中
內嵌式系統的例子舉Java的string pool,它是特殊的儲存空間
5.Non-RAM儲存空間:
如果資料可以獨立於程式外,這類物件通常被儲存於磁碟裡
這一類儲存空間的特色是,可以將物件轉換為可儲存於其他媒介的形式
必要時也可以還原成儲存於RAM中的一般物件
我們稱呼在撰寫程式過程中常用到的類別為基本型別(primitive types)
要特別的處理它是因為透過 new 來配置這種極小、簡單的變數於heap上,效率不佳
Java採取C/C++的方式來處理,也就是不用 new 來配置其空間
這產生了"automatic"變數(不再使用reference型態)
這類變數直接存放資料值,並配置在stack上以取得較佳的效率
訂閱:
文章 (Atom)