javaee三層架構案例--簡單學生管理系統


背景

學了jdbc、jsp等需要串起來,不然會忘記

項目環境

win10
jdk11
mysql8.0.13

jar包

c3p0-0.9.5.2
commons-dbutils-1.7
jstl
mchange-commons-java-0.2.11
mysql-connector-java-8.0.14
standard

項目地址

還不會用github,所以只能這樣咯

鏈接:https://pan.baidu.com/s/1JwSag2RIEBVhGZVAETNqlQ
提取碼:o0x3
復制這段內容后打開百度網盤手機App,操作更方便哦

准備數據庫

/*創建一個存放學生信息的表格*/

/*創建數據庫stus*/
CREATE DATABASE stus; 
/*使用stus*/
USE stus;
/*創建學生表stu*/
CREATE TABLE stu(
  sid INT PRIMARY KEY AUTO_INCREMENT,
  sname VARCHAR(20),
  gender VARCHAR(5),
  phone VARCHAR(20),
  birthday DATE,
  hobby VARCHAR(50),
  info VARCHAR(200)
);

做一個主頁

通過IDEA在web目錄下創建一個index.jsp作為主頁
頁面先只有一個超鏈接叫做 顯示所有學生列表
還沒寫鏈接到哪個Servlet,用 # 先代替下,等創建好了再寫回來

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>首頁</title>
</head>
<body>
<h3><a href="/studentList">顯示所有學生列表</a></h3>
</body>
</html>

創建一個Servlet

index.jsp標記1出用的Servlet,創一個Servlet的包,創一個StudentListServlet類,用doGet方法(轉發需要用doGet方法,且用doPost沒有這個需求)

Servlet接受用戶點擊,去通知service實現業務邏輯

//這個是用反射,寫在類上一行,也可以在web.xml中配置Servlet
@WebServlet(name = "StudentListServlet",urlPatterns = {"/StudentListServlet"})

//寫在doGet方法然后在doPost方法中互調....
//因為之后轉發只能用doGet方法,有點麻煩

//面向接口編程
//StudentService:接口 StudentServiceImpl:接口實現類
StudentService service = new StudentServiceImpl();
//調用實現類的findAll方法,把結果放在一個list表中,泛型為Student對象
List<Student> list = service.findAll();

創建Student類

上文中缺少Student類,這是一個JavaBean,封裝用。

!!!JavaBean一定要有一個空參!!!

!!!JavaBean是用空參來反射得到實例的!!!

創建domian包,里面創建Student類,包含和數據庫名字、類型對應的成員變量

//數據類型為Date的導util包,sql包中的Date也是繼承該util包中的  
import java.util.Date;
	private int sid;
    private String sname;
    private String gender;
    private String phone;
    private Date birthday;
    private String hobby;
    private String info;
//生成getXxx和setXxx方法
//生成toString方法

創建一個Service接口和Service的實現類

上面沒有Service接口,創建一個service包,下面創建StudentService接口

這里是為了實現學生業務邏輯的處理規范

目前只有一個查找所有學生信息的業務

public interface StudentService {
	//這里的throws SQLException是在最后面dao層發現需要拋,一步一步返回來的,當然IDEA中一鍵生成
    List<Student> findAll() throws SQLException;
}

創建接口的實現類,在service包下創建一個impl包,在impl包內創建StudentServiceImpl實現類

實現學生業務,findAll方法是去數據庫中查詢,因此要調用查詢數據庫的方法

public class StudentServiceImpl implements StudentService {
    @Override
    public List<Student> findAll() throws SQLException {
        //StudentDao:接口 StudentDaoImpl:實現類
        StudentDao dao = new StudentDaoImpl();
        return dao.findAll();
    }
}

創建dao層中接口和實現類

創建一個dao包,創建StudentDao接口,在dao包中創建一個impl包,里面創建一個StudentDaoimpl實現類

public interface StudentDao {
    List<Student> findAll() throws SQLException;
}

StudentDaoImpl實現findAll方法,通過C3P0,自己的工具類JDBCUtil調用

通過數據庫代碼查詢,結果返回到BeanListHandler<>(Student.class)中

public class StudentDaoImpl implements StudentDao {
    @Override
    public List<Student> findAll() throws SQLException {
        QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
        String sql = "SELECT * FROM stu";
        return runner.query(sql, new BeanListHandler<>(Student.class));
    }
}

把查詢出的結果發到list.jsp中

在StudentListServlet類的doGet方法中,要把結果存到request域中

//名字就叫list,值也是list
request.setAttribute("list",list);

再把結果轉發到list.jsp中,不需要改變頁面地址

request.getRequestDispatcher("list.jsp").forward(request, response);

目前先做紅框內的東西,分析一下,就是2行8列,一行是標題,一行是內容(靠循環出來的結果)

要用el表達式,導包jstl.jar和standard.jar
導jstl標簽庫

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--2行8列展示結果--%>
<table border="1px" width="600px">
    <tr>
        <td>編號</td>
        <td>姓名</td>
        <td>性別</td>
        <td>電話</td>
        <td>生日</td>
        <td>愛好</td>
        <td>簡介</td>
        <td>操作</td>
    </tr>
    <c:forEach items="${list }" var="stu">
    <tr>
        <td>${stu.sid }</td>
        <td>${stu.sname }</td>
        <td>${stu.gender }</td>
        <td>${stu.phone }</td>
        <td>${stu.birthday }</td>
        <td>${stu.hobby }</td>
        <td>${stu.info }</td>
        <td>!~~~超鏈接還沒寫,等下補完<a href="#">更新</a><a href="#">刪除</a></td>
    </tr>
    </c:forEach>

第一步小結

用圖片來表示以下上面的流程


繼續完善list.jsp

做一個添加功能,其他先不管

所以讓我們繼續補充一個超鏈接

提交到add.jsp中吧

    <tr>
        <td colspan="8"><a href="add.jsp">添加</a></td>
    </tr>

沒有add.jsp,我們在web文件夾下創建一個,大概長這個樣子


信息很多,我們用post方法提交,交到一個addServlet讓他處理

<h3>添加學生頁面</h3>
<form action="${pageContext.request.contextPath}/addServlet" method="post">
    <table border="1px" width="600px">
        <tr>
            <td>姓名</td>
            <td><input type="text" name="sname"/></td>
        </tr>
        <tr>
            <td>性別</td>
            <td>
                <input type="radio" name="gender" value="男" checked/>男
                <input type="radio" name="gender" value="女"/>女
            </td>
        </tr>
        <tr>
            <td>電話</td>
            <td><input type="text" name="phone"/></td>
        </tr>
        <tr>
            <td>生日</td>
            <td><input type="text" name="birthday"/></td>
        </tr>
        <tr>
            <td>愛好</td>
            <td>
                <input type="checkbox" name="hobby" value="游泳"/>游泳
                <input type="checkbox" name="hobby" value="籃球"/>籃球
                <input type="checkbox" name="hobby" value="足球"/>足球
                <input type="checkbox" name="hobby" value="看書"/>看書
                <input type="checkbox" name="hobby" value="寫字"/>寫字
            </td>
        </tr>
        <tr>
            <td>簡介</td>
            <td><textarea name="info" rows="3" cols="20"></textarea></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="添加"/>
            </td>
        </tr>
    </table>
</form>

addServlet要做什么呢

  1. 中文亂碼問題解決
  2. 要獲取客戶端提交上來的數據並處理
  3. 把數據打包交給service進行業務處理
  4. 交給別人展示數據

實現過程

  1. 中文亂碼問題解決

    request.setCharacterEncoding("UTF-8");
    
  2. 獲取客戶端提交上來的數據

                String sname = request.getParameter("sname");
                String gender = request.getParameter("gender");
                String phone = request.getParameter("phone");
                String birthday = request.getParameter("birthday");
                String hobby = request.getParameter("hobby");
                String info = request.getParameter("info");
    

    並處理下,考慮到birthday是data類型,要轉換下

    Date date = new SimpleDateFormat("yyyy-MM-dd").parse(birthday);
    

    這里要注意下,用getParemeter方法得到的參數永遠只有一個,對於愛好需要傳入很多個,因此考慮使用getParameterValues方法,返回一個String[ ]數組,用Arrays.toString方法,打印之后發現有多出[ ],用substring方法截取中間段

   String hobby = Arrays.toString(request.getParameterValues("hobby"));
   hobby = hobby.substring(1, hobby.length() - 1);
  1. 把數據打包

    就是弄個JavaBean對象封裝一下,用一堆set方法有點麻煩,直接在Student類中增加一個帶參的構造器(之前寫了空參的好處就在此,不會忘記寫)

    Student student = new Student(sname, gender, phone, date, hobby, info);
    

    交給service進行業務處理

    取名為insert方法吧,等會去service中生成需要的接口和對應的實現類

    StudentService service = new StudentServiceImpl();
    service.insert(student);
    
  2. 交給別人展示數據

    這里就是把結果返回給list.jsp中啦。如果直接轉發到list.jsp,會有一個問題,request域中是空的,會沒有元素。因此需要重新轉發到對應的Servlet中

    目前看起來轉發需要加 / ,對其路徑的獲取還不是很懂

    request.getRequestDispatcher("/StudentListServlet").forward(request,response);
    

繼續寫全service

  1. 把StudentService補全,把其實現類補全

    接口就多一個insert方法

        /**
         * 需要添加到數據庫的學生對象
         * @param student 封裝
         * @throws SQLException 異常
         */
        void insert(Student student) throws SQLException;
    

    實現類

    業務沒什么新的,就是在數據庫里加東西,調用DAO層

        @Override
        public void insert(Student student) throws SQLException {
            StudentDao dao = new StudentDaoImpl();
            dao.insert(student);
        }
    

該傳到DAO層了

  1. 把DAO補全,把其實現類補全

    接口和前面service層的接口是一樣的

        /**
         * 需要添加到數據庫的學生對象
         * @param student 封裝
         * @throws SQLException 異常
         */
        void insert(Student student) throws SQLException;
    

    實現類

    之前sql代碼打錯了,大家一定要在sql試過了再寫進來,這樣成功率高點

     @Override
        public void insert(Student student) throws SQLException {
            QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
    
            //INSERT INTO stu VALUES(NULL,'姓名','性別','電話','1999-1-1','愛好','備注');
            runner.update("INSERT INTO stu VALUES(null,?,?,?,?,?,?)",
                    student.getSname(),
                    student.getGender(),
                    student.getPhone(),
                    student.getBirthday(),
                    student.getHobby(),
                    student.getInfo()
            );
        }
    

添加功能小結

  1. 做個jsp表單界面,注意name屬性,這是之后獲取參數用的

  2. 做個提交過去的Servlet

  3. Servlet收集數據,處理數據,封裝數據,傳遞數據(service),展示數據(轉發,可以發給Servlet)

  4. service處理業務邏輯,遇到對數據庫處理的部分,調用dao層

  5. dao實現對數據庫的處理



制作更新相關的功能

需求

  1. 點擊更新能得到當前行的信息,跳到一個新的頁面上
  2. 在表格上更改后點擊按鈕能更新數據庫並在list頁面上顯示

1.點擊更新能得到當前行的列表資料

這個頁面和之前的添加頁面差不多,稍微有點不同。我們直接復制為edit.jsp,稍作修改

需要獲取查詢的內容,自然使用servlet來處理

list.jsp需要改動的部分

取名為EditServlet,傳一個sid為參數,el表達式中的stu是之前jstl的for循環出來的,此時request域中還有。

<a href="EditServlet?sid=${stu.sid}">更新</a>

EditServlet需求分析

  1. 獲取傳來是sid
  2. 通知service去實現需要的業務邏輯
  3. 傳參數到request域中
  4. 帶着request域轉發到edit.jsp

EditServlet實現相關代碼

  1. 獲取傳來是sid

    //轉成int類型,比較方便
    int sid = Integer.parseInt(request.getParameter("sid"));
    
  2. 通知service去實現需要的業務邏輯

    //這里是要通過sid查到對應的人,之后要對后續流程做出相應的更改
    StudentService service = new StudentServiceImpl();
    Student student = service.findStudentById(sid);
    
  3. 傳參數到request域中

    //以示區分,設為student(不過用stu也是一樣的)
    request.setAttribute("student", student);
    
  4. 帶着request域轉發到edit.jsp

    request.getRequestDispatcher("edit.jsp").forward(request,response);
    

對Service/DAO補上相關功能

  1. service

StudentService接口,補一個findStudentById方法

/**
 * 找到某條學生數據
 * @param sid 學生ID
 * @return  學生對象
 * @throws SQLException sql異常
 */
Student findStudentById(int sid) throws SQLException;

StudentServiceImpl實現類,補一個業務流程處理,涉及數據庫的CRUD部分,調用DAO

@Override
public Student findStudentById(int sid) throws SQLException {
    StudentDao dao = new StudentDaoImpl();
    return dao.findStudentById(sid);
}

  1. dao

接口,就是做個抽象類,通過sid返回一個Student對象,因為之后要顯示到界面還需要提取參數

/**
 * 找到某條學生數據
 * @param sid 學生ID
 * @return  學生對象
 * @throws SQLException sql異常
 */
Student findStudentById(int sid) throws SQLException;

實現類,通過sql語句找到對應的數據,返回只有一條結果,用BeanHandler就好

@Override
public Student findStudentById(int sid) throws SQLException {
    QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
    String sql = "SELECT * FROM stu WHERE sid = ?";
    return runner.query(sql, new BeanHandler<>(Student.class), sid);
}

最終返回到edit.jsp中之后,要在相對應的地方獲取對應的數據

type="text" 用value="${對應的數據}",舉例

<input type="text" name="sname" value="${student.sname}"/>

type="radio",需要用對應的結果選中的,參數是checked,需要用if來判斷一下。這里引入jstl核心標簽。如果傳入的文字是男,則設置為checked。女同理。

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<input type="radio" name="gender" value="男" <c:if test="${student.gender == '男'}">checked</c:if>/>男
<input type="radio" name="gender" value="女" <c:if test="${student.gender == '女'}">checked</c:if>/>女

type="checkbox",需要用對應的結果選中的,參數也是checked。但是愛好很多,這里不是用if,而是用包含contains來選擇。引入jstl的function庫。其余類似。

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<input type="checkbox" name="hobby" value="游泳" <c:if test="${fn:contains(student, '游泳')}">checked</c:if>/>游泳

textarea,需要在里面顯示字的,直接在尖括號外面。

<textarea name="info" rows="3" cols="20">${student.info}</textarea>

到這里,第一步顯示數據就完成了。

2. 在表格上更改后點擊按鈕能更新數據庫並在list頁面上顯示

表單提交的地方要改一下,涉及到業務,還是用servlet

servlet需求:獲取edit.jsp的數據,封裝成JavaBean,傳到service,再展示結果。和addServlet差不多,直接復制修改。

service、dao和上文都差不多,方法名用update吧,對數據庫的操作中,因為沒有傳sid回來,因此就用其他的數據作為where條件

daoImpl的代碼

@Override
public void update(Student student) throws SQLException {
   QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
   String sql = "UPDATE stu SET sname=?,gender=?,phone=?,birthday=?,info=? WHERE sid=?";
   //注意這里的要多一個sid,那么到底能不能傳過來sid呢?
   runner.update(sql,
           student.getSname(),
           student.getGender(),
           student.getPhone(),
           student.getBirthday(),
           student.getInfo(),
           student.getSid()
   );
}

讓我們從點擊更新按鈕開始,回顧整個流程。

  1. list.jsp中點擊更新===》"EditServlet?sid=${stu.sid}"

這里是帶着一個sid的

  1. EditServlet中調用service.findStudentById(sid),傳到dao.findStudentById(sid),我們打印下這里返回的對象,發現返回的student對象是帶有sid的。

  2. EditServlet轉發到edit.jsp中,那么edit.jsp的request域中的student是帶有sid的。

  3. edit.jsp點擊提交到UpdateServlet。但是沒有sid。因此問題出在edit.jsp中。

  4. edit.jsp發現沒有調用出sid的代碼,因此我們補充一個。

<input type="hidden" name="sid" value="${student.sid}" />

更新代碼小結

邏輯都差不多,一層調用一層,前面懂了這里自然懂。

不過需要注意一些小問題。比如最后的sid,以及如何分析問題出在哪里的方法:按流程尋找法。


最后分頁查詢功能

這是界面效果

三層架構的業務處理邏輯

這個是我今天剛剛感受出來的

service封裝各種JavaBean,然后回到servlet中展示數據,最后在jsp里調用域中的數據

制作過程

我個人喜歡從jsp開始做起來,缺什么補什么。

首先是一個入口,在index.jsp加入一行代碼。StudentListPageServlet,再傳一個參數currentPage=1

<h3><a href="${pageContext.request.contextPath}/StudentListPageServlet?currentPage=1">分頁顯示學生列表</a></h3>

1.servlet

//1.獲取數據 : 獲取頁碼數
int currentPage = Integer.parseInt(request.getParameter("currentPage"));
//2.得到處理好的封裝數據
//這里創建一個新的JavaBean,因為一方面要保存查詢出來的List,另一方面要保存當前頁面、所有頁面信息。但是JavaBean中要存放多少東西呢,不知道。做到后面,缺啥補啥。反正用原來的JavaBean不行就是了
StudentService service = new StudentServiceImpl();
PageBean<Student> studentByPage = service.findStudentByPage(currentPage);

//3.顯示數據 : 存到quest域中轉發
//因為現在我習慣流程來繼續制作,所以第三步先不寫了。

2.StudentService和其實現類

/**
* 查詢當前頁的數據
* @param currentPage 頁碼數
* @return 查詢出的學生列表
* @throws SQLException SQL
*/
 PageBean<Student> findStudentByPage(int currentPage) throws SQLException;
@Override
public PageBean<Student> findStudentByPage(int currentPage) throws SQLException {
	PageBean<Student> pageBean = new PageBean<>();
	StudentDao dao = new StudentDaoImpl();
	//第一步就是要得出list
	List<Student> list = dao.findStudentByPage(currentPage);

3.StudentDao及其實現類

這是查詢的分頁list

/**
 * 查詢當前頁的數據
 * @param currentPage 頁碼數
 * @return 查詢出的學生列表
 * @throws SQLException SQL
 */
List<Student> findStudentByPage(int currentPage)throws SQLException;
@Override
public List<Student> findStudentByPage(int currentPage) throws SQLException {
    QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
    String sql = "SELECT * FROM stu LIMIT ? OFFSET ?";
    //PAGE_SIZE是5,常數,第二個參數是偏移量。
    List<Student> query = runner.query(sql, new BeanListHandler<>(Student.class), PAGE_SIZE, (currentPage - 1) * PAGE_SIZE);
    return query;
}

補完servlet,制作pageList.jsp

接着從dao層往回傳,到了servlet處,補充完

//3.顯示數據 : 存到quest域中轉發,名字為studentByPage,之后可以在jsp中取出對應的數據
request.setAttribute("studentByPage",studentByPage);
request.getRequestDispatcher("pageList.jsp").forward(request, response);

制作JavaBean

通過PageBean

public class PageBean<T> {
    //目前只需要一個list
    private List<T> list;
}

制作pageList.jsp

這個界面和查詢的界面差不多,因此復制list.jsp,改名為pageList.jsp。

因為上步servlet中是存到request域中的studentByPage里面,page中將表達式中的list改為studentByPage.list

 <c:forEach items="${studentByPage.list }" var="stu">
            <tr>
                <td>${stu.sid }</td>
                <td>${stu.sname }</td>
                <td>${stu.gender }</td>
                <td>${stu.phone }</td>
                <td>${stu.birthday }</td>
                <td>${stu.hobby }</td>
                <td>${stu.info }</td>
                <td><a href="EditServlet?sid=${stu.sid}">更新</a> <a href="#" onclick="doDelete(${stu.sid})">刪除</a></td>
            </tr>
        </c:forEach>

對比下jsp頁面,lis分頁已經好了,我們來制作最下面頁碼行。我們先做預處理,把需要的的東西先靜態表示出來。下圖是最后一行東西。

這個中括號內的數據都是動態的,也是需要從request域中取出的數據。因此按上面的流程,request域是servlet傳的JavaBean中。因此只要把相關數據存到JavaBean對象即可。因此在JavaBean中加入相關的成員變量,並在service層中加入對應處理。

對service、dao等進行處理

在service中進行相應的處理

  1. 當前頁,是可以直接獲得的,在方法傳入的參數中

    pageBean.setCurrentPage(currentPage);
    
  2. 總頁數,需要稍作處理。邏輯是,先獲取所有條數count,然后用其除每頁條數,如果除不盡就多算一頁。獲取count是對數據庫操作,因此依次補上需要的代碼。以下是關鍵代碼:

    //dao層對數據庫的操作
    @Override
    public int findCount() throws SQLException {
        QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
        String sql = "SELECT COUNT(*) FROM stu";
        //ScalarHandler<>()常用來存數字,如count值,平均值等,數據類型是long,需要一個強轉
        Long query = runner.query(sql, new ScalarHandler<>());
        return Math.toIntExact(query);
    }
    
    //service中對頁數的處理
    int count = dao.findCount();
    int countAllPage = count % StudentDaoImpl.PAGE_SIZE == 0 ? count / StudentDaoImpl.PAGE_SIZE : count % StudentDaoImpl.PAGE_SIZE + 1;
    //存入request域中
    pageBean.setCountAllPage(countAllPage);
    
  3. 每頁顯示的條數,就是存在DAO實例中的常數

    pageBean.setPageSize(StudentDaoImpl.PAGE_SIZE);
    
  4. 總記錄,在總頁數那里已經求出來了,直接調用存入

    pageBean.setCountAllPage(countAllPage);
    
  5. 首尾頁,就是第一頁(=1)和最后一頁(countAllPage),不用傳

  6. 上一頁和下一頁,用之前的currentPage做加減,不用傳

  7. 每一頁單獨頁數,用第一頁和最后一頁遍歷即可,不用傳

繼續在jsp中處理

在中括號[ ]相應位置用el表達式取出相應的數據

而點擊跳轉功能的實現,就是一個超鏈接,傳servlet?=帶參數即可

這里需要對首尾頁和中間的遍歷做一點點處理。

  1. 首頁和上一頁加個判斷,當在第一頁時不需要顯示

    <c:if test="${studentByPage.currentPage != 1}">
        <a href="StudentListPageServlet?currentPage=1">首頁</a>
        |
        <a href="StudentListPageServlet?currentPage=${studentByPage.currentPage - 1}">上一頁</a>
    </c:if>
    
  2. 尾頁和下一頁,當在最后一頁時不需要顯示

    <c:if test="${studentByPage.currentPage != studentByPage.countAllPage}">
        <a href="StudentListPageServlet?currentPage=${studentByPage.currentPage + 1}">下一頁</a>
        |
        <a href="StudentListPageServlet?currentPage=${studentByPage.countAllPage}">尾頁</a>
    </c:if>
    
  3. 對中間的頁碼處理,用一個遍歷

    <c:forEach begin="1" end="${studentByPage.countAllPage}" var="i">
       ${i}
    </c:forEach>
    

    補上超鏈接

    <c:forEach begin="1" end="${studentByPage.countAllPage}" var="i">
            <a href="StudentListPageServlet?currentPage=${i}">${i}
    </c:forEach>
    

    在當前頁時,不需要超鏈接,用if判斷

    <c:forEach begin="1" end="${studentByPage.countAllPage}" var="i">
        <c:if test="${studentByPage.currentPage == i}">${i}</c:if>
        <c:if test="${studentByPage.currentPage != i}">
            <a href="StudentListPageServlet?currentPage=${i}">${i}</a>
        </c:if>
    </c:forEach>
    

做好啦,完結撒花~

未完待續


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM