1. 程式人生 > >DAO設計模式講解—四層模型,分層設計模式

DAO設計模式講解—四層模型,分層設計模式

1.程式的分層

如果對程式進行劃分,那麼最常見的劃分方式:顯示層+控制層+業務鞥+資料層,其中,顯示層和控制層一般稱為前臺,業務層和資料層成為後臺業務層。

在整個專案之中,後臺業務是最核心的部分,因為移動應用的火爆問題,前臺所以對於前臺層不再簡單侷限於WEB層,可能是Android,IOS,而且隨著技術的發展。前臺可能使用Python或者Node.js進行包裝。

業務層是整個程式提供的操作功能,資料層是資料庫的原子操作。業務層就是一個操作。而一個業務層的操作要想完成多個數據層的操作共同完成。整個過程中,發現數據層完成的只是一個個原子性的資料庫開發。而在實際開發過程中,每一個操作的業務需要牽扯多個原子性的操作,也就是說所有原子性的操作最終實在業務層中完成。

在實際開發中,業務層的設計師非常複雜。往往需要幾個總業務層,之下還有若干子業務層。

2.例項分析。

現在要求使用結果emp表(empno,ename,job,hiredarte,sal,comm)實現如下操作(客戶提出的所有的需求就是業務層,也就是一個功能)

  • 【業務層】實現僱員資料的新增,但是保證被新增的僱員編號不會重複
  • |--【資料層】判斷要增加的僱員編號是否存在
  • |--【資料層】如果僱員編號不存在則進行資料的儲存操作
  • 【業務層】實現僱員資料的修改操作
  • |--【資料層】執行資料的修改操作
  • 【業務層】實現多個僱員資料的刪除操作
  • |--【資料層】執行僱員的限定刪除操作
  • 【業務層】可以根據僱員編號查到一個僱員的資訊
  • |--【資料層】根據僱員編號查詢指定的僱員資料
  • 【業務層】可以查詢所有僱員的資訊
  • |--【資料層】查詢全部僱員資料
  • 【業務層】可以實現資料的分頁顯示,同時又可以返回所有僱員的數量
  • |--【資料層】僱員資料的分頁查詢
  • |--【資料層】使用count()函式統計出所有的僱員數量。

結論:使用者所提出的所有的需求都應該劃分為業務層,因為他指的是功能,而開發人員必須根據業務層進行資料層的設計。

3.專案準備

設定專案名稱DAOProject,專案使用Mysql,需要為其配置好資料庫的驅動程式。

為了方便的進行統一的管理,所有的專案的父包名稱統一設定為:cn.mldn。而子包要根據不同的功能模組進行劃分。

3.1資料庫連線類

所有的的資料庫連線操作都是固定的步驟,可以單獨定義一個DatabaseConnection類,這個類主要負責資料庫連線物件的取得和關閉。既然是一個專門用於資料庫的連線操作,那麼可以將其儲存在dbc子包中。

package cn.mldn.dbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
 * 本類方法專門負責資料庫的連線與關閉操作,在例項化本類物件時就意味著要進行資料庫的開發
 * 所以在本類的構造方法裡要進行資料庫驅動的載入與資料庫的連線取得
 * @author Leeffy
 *
 */
public class DatabaseConnection {
    private static final String DBDRIVER="com.mysql.jdbc.driver";
    private static final String DBURL="jdbc:mysql://localhost/student?useUnicode=true&characterEncoding=UTF-8";
    private static final String DBUSER="root";
    private static final String PASSWORD="";
    Connection conn=null;
    /**
     * 在構造方法裡面為conn物件進行例項化,可以直接取得資料庫的連線物件<br>
     * 由於所有的操作都是基於資料庫完成的,如果資料庫取得不了連線,那麼也就意味著所有的操作都可以停止了
     */
    public DatabaseConnection() {
        try {
            Class.forName(DBDRIVER);
            this.conn = DriverManager.getConnection(DBURL,DBUSER,PASSWORD);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * 取得一個數據庫的連線物件
     * @return Connection例項化物件
     */
    public Connection getConnection() {
        return this.conn;
    }
    /**
     * 負責資料庫的關閉
     */
    public void close() {
        if(this.conn!=null) {//現在有連線物件
            try {
                this.conn.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

整個操作過程之中,DatabaseConnection只是無條件的提供有資料庫連線,而至於說有多少個連線物件,他都不關心。

額外話題:從最早的DAO設計模式來講實際上還會考慮一個問題,多資料庫間的移植問題。此時就需要設定一個專門的表示連線標準的介面。

3.2、開發ValueObject(VO)

現在的程式嚴格來講已經給出了4個層次。不同的層次之間一定要進行資料的傳遞,但是既然操作的是指定的資料表,所以資料的結構必須要與表的結構一一對應,那麼自然可以想到簡單Java類。

在實際的工作之中,針對簡單java類的開發給出如下要求

  • 考慮到日後程式有可能出現的分散式應用問題,所以簡單Java類必須實現java.io.Serialiable介面。
  • 簡單Java類的名稱必須與表名一致。例:student_info表的類名,StudentInfo;
  • 類中的屬性不允許使用基本資料型別,都必須使用基本資料型別的包裝類:基本資料型別的數值型預設值是0,而如果是包裝類預設值就是null;
  • 類中可以定義有多個構造方法,但是必須要保留一個無參構造方法。
  • 類中的屬性必須使用private封裝,封裝後的屬性必須提供有getter、setter方法
  • 【可選要求】複寫equals()、toString()、hashCode()。

將所有的簡單Java類儲存在vo包中。

範例:定義Emp.java

package cn.mldn.vo;

import java.io.Serializable;
import java.util.Date;

@SuppressWarnings("serial")
public class Emp implements Serializable {
    private Integer empno;
    private String ename;
    private String job;
    private Date hiredate;
    private Double sal;
    private Double comm;
}

不管有多少張表,只要是實體表,那麼一定要寫簡單Java類,而且不要試圖想著一次性將所有的表都轉換到位。所有的類都要儲存在vo包中。

3.3開發資料層

資料層最終是交給業務層進行呼叫的,所以業務層必須知道資料層的執行標準,即,業務層需要明確的知道資料層的操作方法,但是不需要知道他的具體實現。

3.3.1開發資料層操作標準

不同層之間如果要進行訪問,那麼必須要提供有介面,以定義操作標準,那麼對於資料層也是一樣的,因為資料層的最終要交給業務層執行,所以需要先定義資料層的藉口。

對於資料層的介面給出如下的開發要求:

  • 資料層既然進行資料操作的,那麼就將其儲存在dao包下;
  • 既然不同的資料表的操作有可能使用不同的資料層開發,那麼針對於資料表進行命名:
  • |- emp表,那麼資料層的標準就應該定義為IEmpDAO;
  • 對於整個資料層的開發嚴格來講只有兩類功能
  • |-資料更新:建議他的操作方法以doXxx()的形式命名,例如:doCreate()、doUpdate()、doRemove();
  • |-資料查詢:對於查詢分為兩種:
  •        |-查詢表中資料:以findXxx()形式命名,例如:findById()、findByName()、findAll()
  •        |-統計表中的資料:以getXxx()形式命名,如:getAllCount();

範例:定義IEmpDAO介面

package cn.mldn.dao;

import java.util.List;
import java.util.Set;

import cn.mldn.vo.Emp;
/**
 * 定義emp表的資料層的操作標準
 * @author Leeffy
 *
 */
public interface IEmpDAO {
    /**
     * 實現資料的增加操作
     * @param vo 包含了要增加資料的VO物件
     * @return 資料儲存成功返回true,否則返回false
     * @throws Exception SQL執行異常
     */
    public boolean doCreate(Emp vo) throws Exception;
    /**
     * 實現資料的修改操作,本次修改是根據id進行全部欄位資料的修改
     * @param vo包含了要修改的資訊,一定要提供有ID內容
     * @return 資料修改成功返回true,否則返回false
     * @throws Exception SQL執行異常
     */
    public boolean doUpdate(Emp vo) throws Exception;
    /**
     * 執行資料的批量刪除操作,所有要刪除的資料以Set集合的形式儲存
     * @param ids 包含了所有要刪除的資料ID,不包含有重複內容
     * @return 刪除成功返回true(刪除資料個數與要刪除的資料個數相同),否則返回false
     * @throws Exception SQL執行異常
     */
    public boolean doRemoveBatch(Set<Integer> ids) throws Exception;
    /**
     * 根據僱員編號查詢指定的僱員資訊
     * @param id 要查詢的僱員編號
     * @return 如果僱員資訊存在,則將資料以VO類物件的形式返回,否則返回null
     * @throws Exception SQL執行異常
     */
    public Emp findById(Integer id) throws Exception;
    /**
     * 查詢指定資料表的全部記錄,並且以集合的形式返回
     * @return 如果表中有資料,則資料封裝為VO物件而後利用List集合返回,否則,集合的長度為0(size==0,而不是null)
     * @throws Exception SQL執行異常
     */
    public List<Emp> findAll()throws Exception;
    /**
     * 分頁進行資料的模糊查詢,查詢的結果以集合的形式返回
     * @param currentPage 當前所在的頁
     * @param lineSize 每頁要顯示的資料行數
     * @param column 要進行模糊查詢的資料列
     * @param keyword 模糊查詢的關鍵字
     * @return
     * @throws Exception SQL執行異常
     */
    public List<Emp> findAllSplit(Integer currentPage,Integer lineSize,String column,String keyword)throws Exception;
    /**
     * 進行模糊查詢資料量的統計,如果表中沒有記錄統計的結果就是0
     * @param column 要進行模糊查詢的資料列
     * @param keyword 模糊查詢的關鍵字
     * @return 返回表中的資料量,如果沒有資料返回0
     * @throws Exception SQL執行異常
     */
    public Integer getAllCount(String column,String keyword) throws Exception;
}

3.3.2資料層實現類

資料層需要被業務層呼叫,資料層需要進行資料庫的執行(PreparedStatement),由於在開發之中一個業務層操作需要執行多個數據層的呼叫,資料庫的開啟和關閉應該由業務層控制。

所有的資料層實現類要求儲存在dao.impl子包下。

範例:EmpDAOImpl子類

package cn.mldn.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import cn.mldn.dao.IEmpDAO;
import cn.mldn.vo.Emp;

public class EmpDAOImpl implements IEmpDAO {
    private Connection conn;//需要利用Connection物件操作
    private PreparedStatement pstmt;
    /**
     * 如果要想使用資料層進行原子性的功能操作實現,必須提供有Connection介面物件<br>
     * 另外,由於開發之中業務層要呼叫資料層,所以資料庫的開啟與關閉交由業務層處理
     * @param conn 標識資料庫的連線物件
     */
    public EmpDAOImpl(Connection conn) {
        this.conn = conn;
    }
    @Override
    public boolean doCreate(Emp vo) throws Exception {
        String sql = "INSERT INTO EMP(empno,ename,job,hiredate,sal,comm) VALUES (?,?,?,?,?,?)";
        this.pstmt = this.conn.prepareStatement(sql);
        this.pstmt.setInt(1, vo.getEmpno());
        this.pstmt.setString(2, vo.getEname());
        this.pstmt.setString(3, vo.getJob());
        this.pstmt.setDate(4, new java.sql.Date(vo.getHiredate().getTime()));
        this.pstmt.setDouble(5, vo.getSal());
        this.pstmt.setDouble(6, vo.getComm());
        return this.pstmt.executeUpdate()>0;
    }

    @Override
    public boolean doUpdate(Emp vo) throws Exception {
        String sql = "Update EMP SET ename=?,job=?,hiredate=?,sal=?,comm=? where empno=?";
        this.pstmt = this.conn.prepareStatement(sql);
        this.pstmt.setString(1, vo.getEname());
        this.pstmt.setString(2, vo.getJob());
        this.pstmt.setDate(3, new java.sql.Date(vo.getHiredate().getTime()));
        this.pstmt.setDouble(4, vo.getSal());
        this.pstmt.setDouble(5, vo.getComm());
        this.pstmt.setInt(6, vo.getEmpno());
        return this.pstmt.executeUpdate()>0;
    }

    @Override
    public boolean doRemoveBatch(Set<Integer> ids) throws Exception {
        if(ids==null||ids.size()==0) {//沒有要刪除的資料
            return false;
        }
        StringBuffer sql = new StringBuffer();
        sql.append("DELETE FROM emp WHERE empno IN(");
        Iterator<Integer> iter = ids.iterator();
        while(iter.hasNext()) {
            sql.append(iter.next()).append(",");
        }
        sql.delete(sql.length()-1, sql.length()).append(")");
        this.pstmt = this.conn.prepareStatement(sql.toString());
        return this.pstmt.executeUpdate()==ids.size();
    }

    @Override
    public Emp findById(Integer id) throws Exception {
        Emp vo = null;
        String sql = "SELECT empno,ename,job,hiredate,sal,comm FROM emp WHERE empno=?";
        this.pstmt = this.conn.prepareStatement(sql);
        this.pstmt.setInt(1, id);
        ResultSet rs = this.pstmt.executeQuery();
        if(rs.next()) {
            vo = new Emp();
            vo.setEmpno(rs.getInt(1));
            vo.setEname(rs.getString(2));
            vo.setJob(rs.getString(3));
            vo.setHiredate(rs.getDate(4));
            vo.setSal(rs.getDouble(5));
            vo.setComm(rs.getDouble(6));
        }
        return vo;
    }

    @Override
    public List<Emp> findAll() throws Exception {
        List<Emp> all = new ArrayList<>();
        String sql = "SELECT empno,ename,job,hiredate,sal,comm FROM emp";
        this.pstmt = this.conn.prepareStatement(sql);
        ResultSet rs = this.pstmt.executeQuery();
        while(rs.next()) {
            Emp vo = new Emp();
            vo = new Emp();
            vo.setEmpno(rs.getInt(1));
            vo.setEname(rs.getString(2));
            vo.setJob(rs.getString(3));
            vo.setHiredate(rs.getDate(4));
            vo.setSal(rs.getDouble(5));
            vo.setComm(rs.getDouble(6));
            all.add(vo);
        }
        return all;
    }

    @Override
    public List<Emp> findAllSplit(Integer currentPage, Integer lineSize, String column, String keyword)
            throws Exception {
        List<Emp> all = new ArrayList<>();
        String sql = "SELECT * FROM "
                + "(SELECT empno,ename,job,hiredate,sal,comm,ROWNUM rn FROM emp"
                + "WHERE "+column+" LIKE ? AND ROWNUM<=?) temp "
                        + "where temp.rn>?";
        this.pstmt = this.conn.prepareStatement(sql);
        this.pstmt.setString(1, "%"+keyword+"%");
        this.pstmt.setInt(2, currentPage*lineSize);
        this.pstmt.setInt(3, (currentPage-1)*lineSize);
        ResultSet rs = this.pstmt.executeQuery();
        while(rs.next()) {
            Emp vo = new Emp();
            vo = new Emp();
            vo.setEmpno(rs.getInt(1));
            vo.setEname(rs.getString(2));
            vo.setJob(rs.getString(3));
            vo.setHiredate(rs.getDate(4));
            vo.setSal(rs.getDouble(5));
            vo.setComm(rs.getDouble(6));
            all.add(vo);
        }
        return all;
    }

    @Override
    public Integer getAllCount(String column, String keyword) throws Exception {
        String sql = "SELECT COUNT(empno) FROM emp WHERE "+column+" LIKE ?";
        this.pstmt = this.conn.prepareStatement(sql);
        this.pstmt.setString(1, "%"+keyword+"%");
        ResultSet rs = this.pstmt.executeQuery();
        if(rs.next()) {
            return rs.getInt(1);
        }
        return null;
    }
}

3.3.2、定義資料層工廠類——DAOFactory

業務層要想進行資料層的呼叫,那麼必須要取得IEmpDAO介面物件,但是不同層之間如果想取得介面物件例項 ,需要使用工廠設計模式,這個工廠類將其儲存在factory子包下。

範例:定義工廠類

package cn.mldn.factory;

import java.sql.Connection;

import cn.mldn.dao.IEmpDAO;
import cn.mldn.dao.impl.EmpDAOImpl;

public class DAOFactory {
    public static IEmpDAO getIEmpDAOInstance(Connection conn) {
        return new EmpDAOImpl(conn);
    }
}
使用工廠的特徵就是外層不需要知道具體的子類

3.4開發業務層

業務層是真正留給外部呼叫的,可能是控制層,或者是直接呼叫。既然業務層也是由不同的層進行呼叫,業務層開發的首要任務就是定義業務層的操作標準。

3.4.1開發業務層標準——IEmpService

業務層也可以成為serivice層,既然描述的是emp表的操作,所以名稱定義為IEmpService,並且儲存在service的子包下,但是對於業務層的方法定義沒有明確要求,只不過個人建議寫上有意義的統一名稱。

範例:定義IEmpService操作標準

package cn.mldn.service;

import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.mldn.vo.Emp;

/**
 * 定義emp表的業務層執行標準,此類一定要負責資料庫的開啟與關閉操作
 * 此類可以通過DAOFactory類取得IEmpDAO介面物件
 * @author Leeffy
 *
 */
public interface IEmpService {
    /**
     * 實現僱員資料的增加操作,本次操作要呼叫IEmpDAO介面的如下方法:<br>
     * <li>需要呼叫IEmpDAO.fingById()方法,判斷要增加的資料的id是否已經存在;
     * <li>如果現在要增加的資料編號不存在則呼叫IEmpDAO.doCreate()方法,返回操作的結果
     * @param vo 包含了要增加資料的VO物件
     * @return 如果增加資料的ID重複或者儲存失敗返回false,否則返回true
     * @throws Exception SQL執行異常
     */
    public boolean insert(Emp vo) throws Exception;
    /**
     * 實現僱員資料的修改操作,本次呼叫IEmpDAO.doUpdate()方法,本次修改屬於全部內容的修改;
     * @param vo 包含了要修改資料的V哦物件
     * @return 修改成功返回true,否則返回false
     * @throws Exception SQL執行異常
     */
    public boolean update(Emp vo) throws Exception;
    /**
     * 執行僱員資料的刪除操作,可以刪除多個僱員資訊,呼叫IEmpDAO.doRemoveBatch()方法
     * @param ids 包含了全部要刪除資料的集合,沒有重複資料
     * @return 刪除成功返回true,否則返回false
     * @throws Exception SQL執行異常
     */
    public boolean delete(Set<Integer> ids) throws Exception;
    /**
     * 根據僱員編號查詢完整資訊,呼叫IEmpDAO.findById()方法。
     * @param ids 要查詢僱員的編號
     * @return 如果找到以VO物件返回,否則返回null
     * @throws Exception SQL執行異常
     */
    public Emp get(Integer ids) throws Exception;
    /**
     * 查詢全部僱員資訊,呼叫IEmpDAO.findAll()方法
     * @return 查詢結果以list集合的形式返回,如果沒有資料則集合的長度為0
     * @throws Exception throws Exception;
     */
    public List<Emp> list() throws Exception;
    /**
     * 實現資料的模糊查詢與資料統計,要呼叫IEmpDAO介面的兩個方法:<br>
     * <li>呼叫IEmpDAO.findAllSplit()方法,查詢出所有的表資料,返回List<Emp>;
     * <li>呼叫IEmpDAO.getAllCount()方法,查詢出所有的資料量,返回Integer
     * @param currentPage 當前頁
     * @param lineSize 每頁的記錄數
     * @param column 模糊查詢的資料列
     * @param keyword 模糊查詢的關鍵字
     * @return 本方法返回多種資料型別,所以使用Map集合返回,由於型別不同意,所以所有Value設定為Object
     * <li> key = allEmps,
     * <li>
     * @throws Exception
     */
    public Map<String,Object> list(int currentPage,int lineSize ,String column,String keyword) throws Exception;
}

本介面的方法設計完全符合之前的分析過程。

3.4.2 業務層實現類

業務層實現的核心功能

  • 負責控制資料庫的開啟與關閉,當存在了業務層物件後其母的就是為了操作資料庫;即:業務層物件例項化之後就必須準備好資料庫連線;
  • 根據DAOFactory呼叫getIEmpDAOInstance()方法後取得IEmpDAO介面物件,
  • 業務層的實現類儲存在dao.impl子包中

範例:定義EmpServiceImpl子類。

package cn.mldn.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.mldn.dbc.DatabaseConnection;
import cn.mldn.factory.DAOFactory;
import cn.mldn.service.IEmpService;
import cn.mldn.vo.Emp;

public class EmpServiceImpl implements IEmpService {
    //在這個類的物件就提供有一個數據庫連線類的例項化物件
    private DatabaseConnection dbc = new DatabaseConnection();
    

    @Override
    public boolean insert(Emp vo) throws Exception {
        try {
            //要增加的僱員編號如果不存在,則findById()返回的結果就是null,null表示可以進行新僱員資料的儲存
            if(DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findById(vo.getEmpno())==null) {
                return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doCreate(vo);
            }
            return false;
        }catch(Exception e) {
            throw e;
        }finally {
            this.dbc.close();
        }
    }

    @Override
    public boolean update(Emp vo) throws Exception {
        try {
            return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doUpdate(vo);
        }catch(Exception e) {
            throw e;
        }finally {
            this.dbc.close();
        }
    }

    @Override
    public boolean delete(Set<Integer> ids) throws Exception {
        try {
            return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doRemoveBatch(ids);
        }catch(Exception e) {
            throw e;
        }finally {
            this.dbc.close();
        }
    }

    @Override
    public Emp get(Integer ids) throws Exception {
        try {
            return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findById(ids);
        }catch(Exception e) {
            throw e;
        }finally {
            this.dbc.close();
        }
    }

    @Override
    public List<Emp> list() throws Exception {
        try {
            return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findAll();
        }catch(Exception e) {
            throw e;
        }finally {
            this.dbc.close();
        }
    }

    @Override
    public Map<String, Object> list(int currentPage, int lineSize, String column, String keyword) throws Exception {
        try {
            Map<String,Object> map = new HashMap<String, Object>();
            map.put("allEmps",DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findAllSplit(currentPage, lineSize, column, keyword));
            map.put("empCount", DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).getAllCount(column, keyword));
            return map;
        }catch(Exception e) {
            throw e;
        }finally {
            this.dbc.close();
        }
    }

}

不同層之間訪問依靠的就是工廠類和介面進行操作。

3.5.3、定義業務層工廠類——ServiceFactory

業務層最終依然需要被其他的層所使用的,所以需要為其定義工廠類,該類也同樣儲存在factory子包下,如果從開發獎,業務層應該分為兩種:

  • 前臺業務邏輯:可以將其儲存在service.front包中,工廠類:ServiceFrontFactory;
  • 後臺業務邏輯:可以將其儲存在service.back包中,工廠類:ServiceBackFactory;

範例:定義ServiceFactory:

package cn.mldn.factory;

import cn.mldn.service.IEmpService;
import cn.mldn.service.impl.EmpServiceImpl;

public class ServiceFactory {
    public static IEmpService getIEmpServiceInstance() {
        return new EmpServiceImpl();
    }
}

在實際的編寫之中,子類永遠都是不可見的,同時在整個操作裡面,控制層完全看不見資料庫的任何操作(任何JDBC程式碼)