DAO设计模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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设计模式的主要内容,如果未能解决你的问题,请参考以下文章

DAO模式

jsp DAO设计模式

DAO模式

java中的dao模式

Java01-接口(DAO模式代码阅读及应用)

JavaWeb技术:DAO设计模式