這幾天開始和社會董還有小孟願開始寫小組的圖書管理系統,將在此期間遇到的一些問題和學到的一些知識總結在博客裏。
這篇博客要總結的就是在WEB項目中用到的很重要的一種數據庫設計模式DAO。
DAO是什麼
DAO是WEB項目裏面的數據層,主要負責爲其他各層(MVC(Model View Controller))提供數據。DAO層裏封裝了對數據庫的各種操作的代碼。
爲什麼要使用DAO
我們在寫WEB項目時,經常會有這樣的需求:需要從數據庫拿到數據,然後再展示再前端頁面上,這也就說明我們需要在JSP頁面中使用JDBC連接數據庫進行各種操作。且不說在JSP頁面上操作數據庫有多繁複,單是大段大段的JAVA代碼就已經使得JSP頁面變得很複雜了。JSP頁面應該專注於數據的展示結果,而非數據的取得過程。所以我們使用DAO設計模式,提供一組通用的數據庫操作方法,簡化代碼,增強程序的可移植性。
DAO層的組成
DAO層主要由五個部分組成:DBUtil(數據庫連接類),VO(valueObject值對象),IDAO(DAO接口),DAOImpl(DAO實現類),DAOFactory(DAO工廠類)。
下面來詳細介紹這五個部分。
1. DBUtil(數據庫連接類)
在DBUtil數據庫連接類中,主要是封裝了數據庫的連接和關閉操作。
在使用JDBC連接數據庫中,不同的數據庫操作都有一段相同的代碼,那就是連接數據庫和關閉數據庫。爲了避免代碼重複多次,我們將這種重複性的操作封裝進一個類中,即數據庫連接類。
DBUtil類代碼如下:
import java.sql.*;
import java.util.ResourceBundle;
/**
* Created by dela on 7/17/17.
*/
public class DBUtils {
public static String URL;
public static String USERNAME;
public static String PASSWD;
public static String Driver;
//加載配置文件
//如果利用resourse目錄操作配置文件時,應該用resourceBundel類來操作文件
private static ResourceBundle rb = ResourceBundle.getBundle("db-config");
//靜態代碼塊在加載類時只會被執行一次
static {
URL = rb.getString("jdbc.URL");
USERNAME = rb.getString("jdbc.USERNAME");
PASSWD = rb.getString("jdbc.PASSWD");
Driver = rb.getString("jdbc.Driver");
try {
Class.forName(Driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//開啓數據庫連接
public static Connection getConnection(){
Connection conn = null;
try {
conn = DriverManager.getConnection(URL, USERNAME, PASSWD);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("獲取連接失敗!");
}
return conn;
}
//關閉數據庫連接
public static void close(ResultSet rs, Statement stat, Connection conn){
try{
if(rs != null){
rs.close();
}
if(stat != null){
stat.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
相關代碼的大概含義在註釋中都說明了,db-config.properties裏用來存儲數據庫連接URL/用戶名/密碼的相關信息,一般放在工程的resource目錄下,使用resourceBundel類來操作配置文件。具體格式如下:
jdbc.URL = jdbc:mysql://localhost:3306/BookManager?characterEncoding=utf-8&useUnicode=true&useSSL=true
jdbc.USERNAME = root
jdbc.PASSWD = ******
jdbc.Driver = com.mysql.jdbc.Driver
2. VO(valueObject值對象)
VO層裏面都是JAVA類,每一個JAVA類都具備Bean的特性。VO層裏面的每一個類,分別對應數據庫中的每一張表。VO提供了一種面向對象的思想和方法來操作數據庫,以後我們的DAO接口就是通過調用VO來進行數據庫操作的。
用圖書系統數據庫中的Book表來舉例,book表有如下字段:
int id;
String name;
String author;
String owner;
int amount;
String upload_date;
String describe;
int borrow_num;
則其對應的VO類應該爲:
/**
* Created by dela on 7/20/17.
*/
public class Book {
private int id;
private String name;
private String author;
private String owner;
private int amount;
private String upload_date;
private String describe;
private int borrow_num;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getUpload_date() {
return upload_date;
}
public void setUpload_date(String upload_date) {
this.upload_date = upload_date;
}
public String getDescribe() {
return describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
public int getBorrow_num() {
return borrow_num;
}
public void setBorrow_num(int borrow_num) {
this.borrow_num = borrow_num;
}
}
看過代碼,應該很容易能理解VO層的內容,就不過多描述了。
3. IDAO(DAO接口)
IDAO定義了對一張表的所有可能操作,比如增刪改查,但是IDAO中只是定義這些方法的接口,具體實現留給DAOImpl層。一個IDAO類可以有不同的實現,比如同一個接口可以實現出它的多個不同子類,甚至對同一個接口用不同的數據庫來實現,這也是根據需求而定的。
BookIDAO的代碼如下:
import Dao.ValueObject.Book;
import java.sql.SQLException;
import java.util.List;
/**
* Created by dela on 7/20/17.
*/
public interface BookDao {
//添加操作
public void insert(Book book);
//刪除操作
public void delete(String name) throws SQLException;
//更改操作
public void update(Book book);
//查詢操作
//書名|作者|所屬者模糊查詢
public List<Book> queryByNAO(String keyWords);
//書ID查詢
public List<Book> queryByBookId(int bookId);
//所有書查詢
public List<Book> queryAllBook();
//分類查詢
public List<Book> queryByFatherClassId(int fatherClassId);
//後續有其他需求的操作再行添加
}
4. DAOImpl(DAO實現類)
DAOImpl層主要是對IDAO層的實現,如上述所說,可以對同一接口有多種實現。
BookDAOImpl的代碼如下:
import Dao.DBConn.DBUtils;
import Dao.IDao.BookDao;
import Dao.ValueObject.Book;
import Model.DealKeywords;
import org.apache.commons.lang.StringEscapeUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by dela on 7/21/17.
*/
public class BookDaoImpl implements BookDao {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
//添加操作
public void insert(Book book) {
String sql = "insert into book_info values(?, ?, ?, ?, ?, ?, ?, ?);";
connection = DBUtils.getConnection();
try {
statement = connection.prepareStatement(sql);
statement.setInt(1, book.getId());
statement.setString(2, book.getName());
statement.setString(3, book.getAuthor());
statement.setString(4, book.getOwner());
statement.setInt(5, book.getAmount());
statement.setString(6, book.getUpload_date());
statement.setString(7, book.getDescribe());
statement.setInt(8, book.getBorrow_num());
sql = StringEscapeUtils.escapeSql(sql);
statement.execute();
DBUtils.close(resultSet, statement, connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
//刪除操作
public void delete(String name) throws SQLException {
String sql = "delete * from book_info where owner = ?;";
sql = StringEscapeUtils.escapeSql(sql);
connection = DBUtils.getConnection();
try {
statement = connection.prepareStatement(sql);
statement.setString(1, name);
statement.executeUpdate();
DBUtils.close(resultSet, statement, connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
//更改操作
public void update (Book book) {
String sql = "update book_info set name = ?, author = ?, " +
"owner = ?, amount = ?, upload_date = ?, describe = ?, borrow_num = ?" +
"where id = ?;";
connection = DBUtils.getConnection();
try {
statement = connection.prepareStatement(sql);
statement.setString(1, book.getName());
statement.setString(2, book.getAuthor());
statement.setString(3, book.getOwner());
statement.setInt(4, book.getAmount());
statement.setString(5, book.getUpload_date());
statement.setString(6, book.getDescribe());
statement.setInt(7, book.getBorrow_num());
statement.setInt(8, book.getId());
sql = StringEscapeUtils.escapeSql(sql);
statement.executeUpdate();
DBUtils.close(resultSet, statement, connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
//查詢操作
//書名|作者|所屬者模糊查詢
public List<Book> queryByNAO(String keyWords) {
keyWords = StringEscapeUtils.escapeSql(keyWords);
System.out.println("***");
System.out.println(keyWords);
keyWords = DealKeywords.dealKeyWords(keyWords);
String sql = "select * from book_info where (owner like '" + keyWords + "' or name like '"
+ keyWords + "' or author like '" + keyWords + "');";
System.out.println(sql);
List<Book> books = new ArrayList<Book>();
Book book = null;
connection = DBUtils.getConnection();
try {
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
if(resultSet.next()){
book = new Book();
book.setId(resultSet.getInt(1));
book.setName(resultSet.getString(2));
book.setAuthor(resultSet.getString(3));
book.setOwner(resultSet.getString(4));
book.setAmount(resultSet.getInt(5));
book.setUpload_date(resultSet.getString(6));
book.setDescribe(resultSet.getString(7));
book.setBorrow_num(resultSet.getInt(8));
books.add(book);
}
DBUtils.close(resultSet, statement, connection);
} catch (SQLException e) {
e.printStackTrace();
}
return books;
}
//剩下三種查詢方式的實現由於篇幅關係在這裏就不貼上了,實現起來也很簡單
}
5. DAOFactory(DAO工廠類)
DAOFactory到底是什麼呢?DAO工廠模式又該怎樣理解呢?爲什麼要有DAO工廠類的存在?先來模擬一個場景。
當我和社會董還有小孟願千辛萬苦終於把圖書系統各自負責的功能模塊寫完了,要合併代碼了,好開心!但是合併代碼的時候卻傻眼了,我和小孟願用的是JDBC連接數據庫,社會董用的是hibernate連接數據庫(很社會!),那麼合併代碼時,DAO層的合併存在分歧,到底是遵循哪種連接呢?最終少數服從多數,社會董決定將自己的DAO層hibernate操作改爲JDBC,但是他仍想保留他的hibernate操作,以便日後勸我和小孟願也將JDBC操作改爲hibernate操作。於是他開始改代碼,反正我們的數據庫操作都用DAO層封裝了嘛,改起來也沒多大事。前面說過,DAOImpl可以對同一接口實現不同數據庫類型的子類,大不了之前是BookIDAOImpl,現在改爲BookIDAOJDBCImpl,社會董就將自己的數據庫操作用JDBC重新實現了一遍(恩,沒毛病!)。但是,他在改JSP頁面的時候震驚了,各種數據庫操作全部都是BookDAO bookDAO=new BookDAOImpl();這時候要怎麼辦呢?
爲了避免上述情況的發生,就要引進DAOFactory。
先來看看DAOFactory的內容是什麼樣的:
import Dao.DaoObject.*;
import Dao.IDao.*;
/**
* Created by dela on 7/21/17.
*/
public class DaoFactory {
public static BookDao getBookDaoInstance(){
return new BookDaoImpl();
}
}
引入DAOFactory後,在JSP中只需要使用工廠裏面的靜態方法BookDAO,不需要BookDAO bookDAO=new BookDAOImpl();就可直接操作DAO層,而即使社會董用JDBC重新實現了數據庫操作,也不用在JSP頁面中逐一去修改BookDAO bookDAO=new BookDAOImpl();只需要在DAOFactory中將getBookDaoInstance()的返回值改爲他新實現的子類BookIDAOJDBCImpl就好啦,如此操作,是不是很簡單!
JSP中使用DAO
在JSP中使用DAO來獲取所有圖書的數據並顯示,示例代碼如下:
<%@ page import="Dao.ValueObject.Book" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="Dao.DaoFactory.DaoFactory" %>
<%@ page import="java.util.Iterator" %><%--
Created by IntelliJ IDEA.
User: dela
Date: 7/25/17
Time: 3:53 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>所有圖書</title>
</head>
<body>
<div id="content" class="contents">
<%
List<Book> books = new ArrayList<Book>();
Book book = null;
books = DaoFactory.getBookDaoInstance().queryAllBook();
Iterator<Book> bookIterator = books.iterator();
while (bookIterator.hasNext()){
book = bookIterator.next();
%>
<div class="books">
<p>
<img src="img/kernel.jpg">
</p>
<p><%=book.getName()%></p>
<p><%=book.getAuthor()%></p>
<p><%=book.getDescribe()%></p>
<p><%=book.getOwner()%></p>
<p>被借:<%=book.getBorrow_num()%>次</p>
<button>我要借閱</button>
</div>
<%
}
%>
</div>
</body>
</html>