背景
学了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>