DAO設計模式
1 DAO設計模式簡介
DAO(數據訪問對象)的主要功能是數據操作;
在程序開發的結構中屬於數據層的操作;
程序開發的標準架構如下:
客戶層、顯示層、業務層和數據層分別介紹如下:
客戶層:現在都采用B/S開發架構,一般用戶都使用瀏覽器進行訪問,當然也可以采用其他程序進行訪問;
顯示層:使用JSP/Servlet進行頁面效果的顯示;
業務層(Business Object,業務對象):會將多個原子性的DAO操作進行組合,組合城一個完整的業務邏輯;
數據層(DAO):提供多個原子性的DAO操作,如增加、修改、刪除等,都屬於原子性操作;
DAO層就是編寫一些具體的操作代碼;
對於業務關聯較多的系統,BO才會發揮作用;
如果業務操作相對簡單,可以不使用BO,而完全通過DAO完成操作;
整個DAO中,實際上是以接口為操作標準,即客戶端依靠DAO實現的接口進行操作,而服務端要將接口進行具體的實現;
DAO由一下幾個部分組成:
DatabaseConnection : 專門負責數據的打開與關閉操作的類;
VO:主要由屬性、setter、getter方法組成,VO類中屬性與表中的字段對應,每一個VO類的對象都表示表中的每一條記錄;
DAO:主要定義操作的接口,定義一系列數據庫的原子性操作標準,如增加、修改、刪除、按ID查詢等;
Impl:DAO接口的真實實現類,完成具體的數據庫操作,但是不負責數據庫的打開和關閉;
Proxy:代理實現類,主要完成數據庫的打開和關閉,並且調用真實的實現類對象的操作;
Factory:工廠類,通過工廠取得一個DAO的實例化操作;
包的命名:
數據庫連接:xxx.dbc.DatabaseConnection;
DAO接口:xxx.dao.IXxxDAO;
DAO接口真實實現類:xxx.dao.impl.XxxDAOImpl;
DAO接口代理實現類:xxx.dao.proxy.XxxDAOProxy;
VO類:xxx.vo.Xxx,VO的命名要與表的命名一致;
工廠類:xxx.factory.DAOFactory;
2 DAO開發
新建emp表
數據庫穿件腳本:
CREATE TABLE emp( empno int(4) primary key, ename varchar(10), job varchar(9), hiredate date, sal float(7,2), );
首先定義VO類,VO類的名稱與表的名稱一致;
Emp.java :定義對應的VO類
package cn.com.bug.vo ; import java.util.Date ; public class Emp { private int empno ; private String ename ; private String job ; private Date hiredate ; private float sal ; public void setEmpno(int empno){ this.empno = empno ; } public void setEname(String ename){ this.ename = ename ; } public void setJob(String job){ this.job = job ; } public void setHiredate(Date hiredate){ this.hiredate = hiredate ; } public void setSal(float sal){ this.sal = sal ; } public int getEmpno(){ return this.empno ; } public String getEname(){ return this.ename ; } public String getJob(){ return this.job ; } public Date getHiredate(){ return this.hiredate ; } public float getSal(){ return this.sal ; } }
以上定義了一個簡單的VO類,包含了屬性,setter、getter方法;其中表示日期時使用java.util.Date類;
定義一個DatabaseConnection.java類,此類主要完成數據庫的打開和關閉操作;
package cn.com.bug.dbc ; import java.sql.Connection ; import java.sql.DriverManager ; public class DatabaseConnection { private static final String DBDRIVER = "org.gjt.mm.mysql.Driver" ; private static final String DBURL = "jdbc:mysql://localhost:3306/bugshi" ; private static final String DBUSER = "root" ; private static final String DBPASSWORD = "123456" ; private Connection conn ; public DatabaseConnection() throws Exception { //在構造方法中進行數據庫的連接
try{ Class.forName(DBDRIVER) ;//加載數據庫驅動 this.conn = DriverManager.getConnection(DBURL,DBUSER,DBPASSWORD) ;//連接數據庫
}catch(Exception e){
throw e;
} } public Connection getConnection(){ //取得數據庫的連接操作 return this.conn ; } public void close() throws Exception { //數據庫的關閉操作 if(this.conn != null){ //避免出現空指針異常 try{ this.conn.close() ; }catch(Exception e){ throw e ; } } } }
執行數據庫連接和關閉的操作中,由於可能出現意外導致無法操作成功時,所有異常將統一交給被調用出處理;
-----------------------------------------------------------------------------------------------------------------------------------------------------
在DAO操作中,由於要適應不同的數據庫 ,所以要將所有可能變化的地方都通過接口進行實現;
如果一個DAO操作既可以在Oracle下使用,也可以在MySQL中使用,往往會將DatabaseConnection定義為一個接口;
eg :DatabaseConnection.java : 定義數據庫連接和關閉的操作接口
package cn.com.bug.dbc; import java,sql.Connection; public interface DatabaseConnection{ public Connection getConnection; public void close(); }
根據不同的數據庫定義不同的子類
以下是MySQL數據庫連接的部分代碼:
MySQLDatabaseConnection.java : 定義DatabaseConnection的子類
package cn.com.bug.dbv.ipml; import java.sql.Connection; import cn.com.bug.dbc.DatabaseConnection; public class MySQlDatabaseConnection implements DatabaseConnection{ public Connection getConnection(){ //編寫針對數據庫連接的代碼 return null; } public void close(){ //編寫具體代碼 } }
此時,如果要取得一個DatabaseConnection接口的連接對象,還需要一個工廠類完成;
DatabaseConnectionFactory.java
package cn.com.bug.factory; import cn.com.bug.dbc.DatabaseConnection; import cn.com.bug.dbc.impl.MySQLDatabaseConnection; public class MySQlDatabaseConnection implements DatabaseConnection{ //取得DatabaseConnection接口實例 public static DatabaseConnection getDatabaseConnection(){ return new MySQLDatabaseConnection(); } }
以上代碼減少了類之間的耦合度;
-----------------------------------------------------------------------------------------------------------------------------------------------------
DAO設計模式中,最重要的就是定義DAO接口;
在定義DAO接口之前必須對業務進行詳細的分析,要清楚地知道一張表在整個系統中的應該具備何種功能;
本程序只完成數據哭的增加、查詢、按雇員編號查詢的功能;
IEmepDAO.java : 定義DAO操作標準
package cn.com.bug.dao ; import java.util.* ; import cn.com.bug.vo.* ; public interface IEmpDAO { /** 數據的增加操作,一般以doXxx命名 @param emp要增加的數據對象 @return 是否增加返回的操作 @throws Exception 有異常交給被調用處處理 */ public boolean doCreate(Emp emp) throws Exception ; /** 查詢全部的數據,一般以findXxx命名 @param keyWord 查詢的關鍵字 @return 返回全部的查詢結果,每一個Emp對象表示表的每一行記錄 @throws Exception 有異常交給被調用處處理 */ public List<Emp> findAll(String keyWord) throws Exception ; /** 根據雇員編號查詢雇員信息 @param empno 雇員編號 @return 雇員的vo對象 @throws Exception 有異常交給被調用處處理 */ public Emp findById(int empno) throws Exception ; }
DAO接口定義完成後需要做具體的實現類,實現類有兩種;
一種是真實主題實現類,另一種是代理操作類;
真實主題類主要是負責具體的數據庫操作,在操作時為了性能及安全講使用PreparedStatement接口完成;
EmpDAOImpl.java : 真實主題實現類
package cn.com.bug.dao.impl ; import java.util.* ; import java.sql.* ; import cn.com.bug.dao.* ; import cn.com.bug.vo.* ; public class EmpDAOImpl implements IEmpDAO { private Connection conn = null ; //數據庫連接對象 private PreparedStatement pstmt = null ; public EmpDAOImpl(Connection conn){ //通過構造方法取得數據庫的連接 this.conn = conn ; } public boolean doCreate(Emp emp) throws Exception{ boolean flag = false ; //定義標誌位 String sql = "INSERT INTO emp(empno,ename,job,hiredate,sal) VALUES (?,?,?,?,?)" ; this.pstmt = this.conn.prepareStatement(sql) ;//實例化PreparedStatement對象 this.pstmt.setInt(1,emp.getEmpno()) ; this.pstmt.setString(2,emp.getEname()) ; this.pstmt.setString(3,emp.getJob()) ; this.pstmt.setDate(4,new java.sql.Date(emp.getHiredate().getTime())) ; this.pstmt.setFloat(5,emp.getSal()) ; if(this.pstmt.executeUpdate() > 0){ //更新記錄的的行數大於0 flag = true ; } this.pstmt.close() ;//關閉PreparedStatent操作 return flag ; } public List<Emp> findAll(String keyWord) throws Exception{ List<Emp> all = new ArrayList<Emp>() ;//定義集合接收全部的數據 String sql = "SELECT empno,ename,job,hiredate,sal FROM emp WHERE ename LIKE ? OR job LIKE ?" ; this.pstmt = this.conn.prepareStatement(sql) ; this.pstmt.setString(1,"%"+keyWord+"%") ; this.pstmt.setString(2,"%"+keyWord+"%") ; ResultSet rs = this.pstmt.executeQuery() ; Emp emp = null ; while(rs.next()){ emp = new Emp() ; emp.setEmpno(rs.getInt(1)) ; emp.setEname(rs.getString(2)) ; emp.setJob(rs.getString(3)) ; emp.setHiredate(rs.getDate(4)) ; emp.setSal(rs.getFloat(5)) ; all.add(emp) ; } this.pstmt.close() ; return all ; //返回全部的結果 } public Emp findById(int empno) throws Exception{ Emp emp = null ; String sql = "SELECT empno,ename,job,hiredate,sal FROM emp WHERE empno=?" ; this.pstmt = this.conn.prepareStatement(sql) ; this.pstmt.setInt(1,empno) ; ResultSet rs = this.pstmt.executeQuery() ; if(rs.next()){ emp = new Emp() ; emp.setEmpno(rs.getInt(1)) ; emp.setEname(rs.getString(2)) ; emp.setJob(rs.getString(3)) ; emp.setHiredate(rs.getDate(4)) ; emp.setSal(rs.getFloat(5)) ; } this.pstmt.close() ; return emp ; //如果查詢不到結果則返回null,默認值為null } }
在DAO的實現類中定義了Connection和PreparedStatement兩個接口對象,並在構造方法中接收外部傳遞來的Connection的實例化對象;
進行數據增加操作時,首先實例化PreparedStatement接口,然後將Emp對象中內容依次設置到PreparedStatement操作中;如果更新記錄大於0,則表示插入成功,修改標誌位;
執行查詢全部數據時,首先實例化List接口的對象;
定義sql語句時,將雇員的姓名和職位定義成了模糊查詢的字段,然後將查詢關鍵字設置到PreparedStatement對象中;
由於查詢出來的多條記錄,所以每一條記錄都重新實例化了一個Emp對象;
同時,會將內容設置到每個Emp對象的對應屬性當中,並將這些對象全部加到List集合中;
在真實主題的實現類中,沒有處理數據庫的打開和連接操作;
只是通過構造方法取得了數據庫的連接,而真正負責打開和關閉的操作將由代理類完成;
EmpDAOProxy.java : 代理主題實現類
package cn.com.bug.dao.proxy ; import java.util.* ; import java.sql.* ; import cn.com.bug.dao.* ; import cn.com.bug.dbc.* ; import cn.com.bug.impl.* ; import cn.com.bug.vo.* ; public class EmpDAOProxy implements IEmpDAO { private DatabaseConnection dbc = null ; //定義數據庫的連接類 private IEmpDAO dao = null ; //聲明DAO對象 public EmpDAOProxy() throws Exception { //在構造方法中實例化連接,同時實例化dao對象 this.dbc = new DatabaseConnection() ;//連接數據庫 this.dao = new EmpDAOImpl(this.dbc.getConnection()) ;//實例化真實主題類 } public boolean doCreate(Emp emp) throws Exception{ boolean flag = false ; try{ if(this.dao.findById(emp.getEmpno()) == null){//如果要插入的雇員編號不存在 flag = this.dao.doCreate(emp) ;//調用真實主題操作 } }catch(Exception e){ throw e ; }finally{ this.dbc.close() ;//關閉數據庫連接 } return flag ; } public List<Emp> findAll(String keyWord) throws Exception{ List<Emp> all = null ; //定義返回的集合 try{ all = this.dao.findAll(keyWord) ; //調用真實主題 }catch(Exception e){ throw e ; }finally{ this.dbc.close() ; } return all ; } public Emp findById(int empno) throws Exception{ Emp emp = null ; try{ emp = this.dao.findById(empno) ; }catch(Exception e){ throw e ; }finally{ this.dbc.close() ;//關閉數據庫連接 } return emp ; } }
在代理類的構造方法中實例化了數據庫連接類的對象以及真實主題實現,
而在代理中的各個方法也只是調用了真實主題實現類中的相應方法;
DAO的真實實現類和代理實現類編寫完成後就需要編寫工廠類,以降低代碼間的耦合度;
DAOFactory.java : DAO工廠類
package cn.com.bug.factory ; import cn.com.bug.dao.IEmpDAO ; import cn.com.bug.dao.proxy.EmpDAOProxy ; public class DAOFactory { public static IEmpDAO getIEmpDAOInstance() throws Exception{//取得DAO接口的實例 return new EmpDAOProxy() ;//取得代理類的實例 } }
該類中的功能就是直接返回DAO接口的實例化對象,以後的客戶端直接通過工廠類取得DAO接口的實例化對象;
TestDAOInsert.java :測試DAO插入功能
package cn.com.bug.dao.test ; importcn.com.bug.factory.DAOFactory ; import cn.com.bug.vo.* ; public class TestDAOInsert{ public static void main(String args[]) throws Exception{ Emp emp = null ; for(int x=0;x<5;x++){ emp = new Emp() ; emp.setEmpno(1000 + x) ; emp.setEname("bug- " + x) ; emp.setJob("設計師- " + x) ; emp.setHiredate(new java.util.Date()) ; emp.setSal(500 * x) ; DAOFactory.getIEmpDAOInstance().doCreate(emp) ; } } }
TestDAOSelect.java 測試查詢操作
package cn.com.bug.dao.test ; import java.util.* ; import cn.com.bug.factory.DAOFactory ; import cn.com.bug.vo.* ; public class TestDAOSelect{ public static void main(String args[]) throws Exception{ List<Emp> all = DAOFactory.getIEmpDAOInstance().findAll("") ; Iterator<Emp> iter = all.iterator() ; while(iter.hasNext()){ Emp emp = iter.next() ; System.out.println(emp.getEmpno() + "、" + emp.getEname() + " --> " + emp.getJob()) ; } } }
3 JSP調用DAO
編寫完一個DAO程序後,即可使用jsp進行前臺功能的實現;
以下內容將在jsp中應用上面寫好的DAO完成雇員的增加、查詢操作;
emp_insert.jsp : 增加雇員
<%@ page contentType="text/html" pageEncoding="GBK"%> <html> <head><title>增加雇員</title></head> <body> <form action="emp_insert_do.jsp" method="post"> 雇員編號:<input type="text" name="empno"><br> 雇員姓名:<input type="text" name="ename"><br> 雇員職位:<input type="text" name="job"><br> 雇傭日期:<input type="text" name="hiredate"><br> 基本工資:<input type="text" name="sal"><br> <input type="submit" value="註冊"> <input type="reset" value="重置"> </form> </body> </html>
由於沒有引入js的驗證,所以要求輸入的雇員編號必須是數字;
雇傭日期的格式必須是yyyy-MM-dd,以方便SimpleDateFormat類完成String到Date類的轉換;
emp_insert_do.jsp : 完成增加雇員的操作
<%@ page contentType="text/html" pageEncoding="GBK"%> <%@ page import="cn.com.bug.factory.*,cn.com.bug.vo.*"%> <%@ page import="java.text.*"%> <html> <head><title>完成增加雇員的操作</title></head> <% request.setCharacterEncoding("GBK"); %> <body> <% Emp emp = new Emp() ; emp.setEmpno(Integer.parseInt(request.getParameter("empno"))) ; emp.setEname(request.getParameter("ename")) ; emp.setJob(request.getParameter("job")) ; emp.setHiredate(new SimpleDateFormat("yyyy-MM-dd").parse(request.getParameter("hiredate"))) ; emp.setSal(Float.parseFloat(request.getParameter("sal"))) ; try{ if(DAOFactory.getIEmpDAOInstance().doCreate(emp)){ %> <h3>雇員信息添加成功!</h3> <% } else { %> <h3>雇員信息添加失敗!</h3> <% } %> <% }catch(Exception e){ e.printStackTrace() ; } %> </body> </html>
以上代碼首先定義了一個Emp對象,然後將表單提交過來的參數依次設置到Emp對象中;
並通過DAO完成數據的插入操作;
在jsp中,如果調用的方法上存在throws關鍵字,則在jsp中也可以不處理異常,而一旦發生異常,將由web容器處理,但還是建議使用try...catch進行處理;
編寫jsp代碼時,所有的異常處理語句絕對不可以使用out.println()進行頁面輸出,而要將全部的異常交給後臺輸出;這樣做是為了避免安全隱患;
emp_list.jsp :數據查詢
<%@ page contentType="text/html" pageEncoding="GBK"%> <%@ page import="cn.com.bug.factory.*,cn.com.bug.vo.*"%> <%@ page import="java.util.*"%> <html> <head><title>數據查詢</title></head> <% request.setCharacterEncoding("GBK") ; %> <body> <% String keyWord = request.getParameter("kw") ; if(keyWord == null){ keyWord = "" ; // 如果沒有查詢關鍵字,則查詢全部 } List<Emp> all = DAOFactory.getIEmpDAOInstance().findAll(keyWord) ;//取全部記錄 Iterator<Emp> iter = all.iterator() ;//實例化Iterator %> <center> <form action="emp_list.jsp" method="post"> 請輸入查詢關鍵字:<input type="text" name="kw"> <input type="submit" value="查詢"> </form> <table border="1" width="80%"> <tr> <td>雇員編號</td> <td>雇員姓名</td> <td>雇員工作</td> <td>雇傭日期</td> <td>基本工資</td> </tr> <% while(iter.hasNext()){ Emp emp = iter.next() ;//取出每一個Emp對象 %> <tr> <td><%=emp.getEmpno()%></td> <td><%=emp.getEname()%></td> <td><%=emp.getJob()%></td> <td><%=emp.getHiredate()%></td> <td><%=emp.getSal()%></td> </tr> <% } %> </table> </center> </body> </html>
以上的代買中,首先根據DAO定義的findAll()方法取得全部查詢街而過,然後采用叠代的方式輸出全部數據;
使用JSP+DAO開發模式可以發現,jsp中的代碼減少了很多,而jsp頁面的功能就是簡單的將DAO 返回的結果進行輸出;
DAO設計模式