背景
學了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要做什么呢
- 中文亂碼問題解決
- 要獲取客戶端提交上來的數據並處理
- 把數據打包交給service進行業務處理
- 交給別人展示數據
實現過程
中文亂碼問題解決
request.setCharacterEncoding("UTF-8");
獲取客戶端提交上來的數據
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);
把數據打包
就是弄個JavaBean對象封裝一下,用一堆set方法有點麻煩,直接在Student類中增加一個帶參的構造器(之前寫了空參的好處就在此,不會忘記寫)
Student student = new Student(sname, gender, phone, date, hobby, info);
交給service進行業務處理
取名為insert方法吧,等會去service中生成需要的接口和對應的實現類
StudentService service = new StudentServiceImpl(); service.insert(student);
交給別人展示數據
這里就是把結果返回給list.jsp中啦。如果直接轉發到list.jsp,會有一個問題,request域中是空的,會沒有元素。因此需要重新轉發到對應的Servlet中
目前看起來轉發需要加 / ,對其路徑的獲取還不是很懂
request.getRequestDispatcher("/StudentListServlet").forward(request,response);
繼續寫全service
把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層了
把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() ); }
添加功能小結
-
做個jsp表單界面,注意name屬性,這是之后獲取參數用的
-
做個提交過去的Servlet
-
Servlet收集數據,處理數據,封裝數據,傳遞數據(service),展示數據(轉發,可以發給Servlet)
-
service處理業務邏輯,遇到對數據庫處理的部分,調用dao層
-
dao實現對數據庫的處理
制作更新相關的功能
需求
- 點擊更新能得到當前行的信息,跳到一個新的頁面上
- 在表格上更改后點擊按鈕能更新數據庫並在list頁面上顯示
1.點擊更新能得到當前行的列表資料
這個頁面和之前的添加頁面差不多,稍微有點不同。我們直接復制為edit.jsp,稍作修改
需要獲取查詢的內容,自然使用servlet來處理
list.jsp需要改動的部分
取名為EditServlet,傳一個sid為參數,el表達式中的stu是之前jstl的for循環出來的,此時request域中還有。
<a href="EditServlet?sid=${stu.sid}">更新</a>
EditServlet需求分析
- 獲取傳來是sid
- 通知service去實現需要的業務邏輯
- 傳參數到request域中
- 帶着request域轉發到edit.jsp
EditServlet實現相關代碼
獲取傳來是sid
//轉成int類型,比較方便 int sid = Integer.parseInt(request.getParameter("sid"));
通知service去實現需要的業務邏輯
//這里是要通過sid查到對應的人,之后要對后續流程做出相應的更改 StudentService service = new StudentServiceImpl(); Student student = service.findStudentById(sid);
傳參數到request域中
//以示區分,設為student(不過用stu也是一樣的) request.setAttribute("student", student);
帶着request域轉發到edit.jsp
request.getRequestDispatcher("edit.jsp").forward(request,response);
對Service/DAO補上相關功能
- 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); }
- 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() ); }
讓我們從點擊更新按鈕開始,回顧整個流程。
- list.jsp中點擊更新===》"EditServlet?sid=${stu.sid}"
這里是帶着一個sid的
EditServlet中調用service.findStudentById(sid),傳到dao.findStudentById(sid),我們打印下這里返回的對象,發現返回的student對象是帶有sid的。
EditServlet轉發到edit.jsp中,那么edit.jsp的request域中的student是帶有sid的。
edit.jsp點擊提交到UpdateServlet。但是沒有sid。因此問題出在edit.jsp中。
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中進行相應的處理
當前頁,是可以直接獲得的,在方法傳入的參數中
pageBean.setCurrentPage(currentPage);
總頁數,需要稍作處理。邏輯是,先獲取所有條數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);
每頁顯示的條數,就是存在DAO實例中的常數
pageBean.setPageSize(StudentDaoImpl.PAGE_SIZE);
總記錄,在總頁數那里已經求出來了,直接調用存入
pageBean.setCountAllPage(countAllPage);
首尾頁,就是第一頁(=1)和最后一頁(countAllPage),不用傳
上一頁和下一頁,用之前的currentPage做加減,不用傳
每一頁單獨頁數,用第一頁和最后一頁遍歷即可,不用傳
繼續在jsp中處理
在中括號[ ]相應位置用el表達式取出相應的數據
而點擊跳轉功能的實現,就是一個超鏈接,傳servlet?=帶參數即可
這里需要對首尾頁和中間的遍歷做一點點處理。
首頁和上一頁加個判斷,當在第一頁時不需要顯示
<c:if test="${studentByPage.currentPage != 1}"> <a href="StudentListPageServlet?currentPage=1">首頁</a> | <a href="StudentListPageServlet?currentPage=${studentByPage.currentPage - 1}">上一頁</a> </c:if>
尾頁和下一頁,當在最后一頁時不需要顯示
<c:if test="${studentByPage.currentPage != studentByPage.countAllPage}"> <a href="StudentListPageServlet?currentPage=${studentByPage.currentPage + 1}">下一頁</a> | <a href="StudentListPageServlet?currentPage=${studentByPage.countAllPage}">尾頁</a> </c:if>
對中間的頁碼處理,用一個遍歷
<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>