JSP
一、概述
Java Server Pages
(Java服务器页面)
简化Servlet开发
,在HTML标签中加入Java代码,用以高效的开发动态网页
二、创建JSP
在Web目录中创建一个
JSP
文件,命名为index.jsp
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>第一个JSP</title>
</head>
<body>
<%-- 这行为注释,在<% %>中写的内容为java语句 --%>
<%
Date date = new Date();
<%-- 这行为注释,out是内置的对象,已经存在我们可以直接用,后面会讲 --%>
out.print("当前时间为:" + date.toLocaleString());
%>
<%-- 这行为注释,写在<% %>之外的内容会被直接打印出来 --%>
out.print(date.toLocaleString());
</body>
</html>
三、JSP原理
- JSP必须先转换成Servlet才能编译执行
- JSP本质是对Servlet的基于输出HTML标签的封装
- 本质还是一个Servlet
优点:可以直接编写HTML标签
xxx.jsp–>转译成–>xxx_jsp.java–>编译成–>xxx_jsp.class–>运行
将JSP转换成java源代码,然后再编译成字节码,在运行。由tomcat容器进行
四、JSP语法
在页面上可以直接写java代码:
脚本,语法:
<%java代码%>
声明,语法:
<%! java定义变量和方法的代码 %>
,声明的变量是该类的属性表达式,语法:
<%= 变量或表达式 %>
例如:<% out.print(j);>
可以简写为:<%= j %>
**注意:**表达式后面一定不要加分号
JSP注释:只会显示在服务器,不会发送到客户端,而HTML注释会发送到客户端,查看网页源代码可以看到HTML注释
<%--我是jsp的注释--%>
<!--我是html的注释-->
问题:在声明的方法中,能不能使用out.print(“”);
- 不能。因为out是service方法中定义的临时变量
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>第一个JSP</title>
</head>
<body>
<%-- 我是定义的属性和方法,属于类属性--%>
<%!
int i = 1;
%>
<%--我是脚本,--%>
<%
int j = 1;
i++;
j++;
// 每次刷新,i会一直增加,而j一直不会变,因为i属于类属性,j属于局部变量
out.print("i=" + i);
%>
<%--我相当于<% out.print("j=" + j);%>--%>
<%="j=" + j%>
<%-- 我是脚本,不能在脚本中定义方法,因为脚本在service中运行,只能在<%!!>中定义方法--%>
<%
Date date = new Date();
out.print("<h1>当前时间为:" + date.toLocaleString() + "</h1>");
%>
<%-- 我是直接打印的--%>
<h1>out.print(date.toLocaleString());</h1>
</body>
</html>
五、JSP指令
用来对页面进行设置
常用有三种
page
:对页面设置include
:将另一个页面包含到当前页面taglib
:导入第三方标签库
5.1 page指令
contentType
:指定内容类型和字符集
language
:指定页面脚本语言,默认为java
pageEncoding
:指定字符集
import
:导入页面脚本中需要的包
errorPage
:当前页面如果出错跳转到哪个页面
isErrorPage
:当前页面是否错误页面
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
5.2 include指令
include指令属于静态包含。在转换成java源代码时将两个文件合并到一个文件,在进行编译
include动作属于动态包含。在转换成java源代码时以及编译时都是独立完成,直到运行时在将包含的页面的内容加载到一起
<jsp:include page="b.jsp"></jsp:includejsp:include>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>第二个JSP</title>
</head>
<body>
<h1>我是a.jsp</h1>
<%
int a = 10;
%>
<%--include指令,会将a传递到b.jsp中--%>
<%@include file="b.jsp"%>
<%--当这里使用include动作时,这里的a将不会传递到b.jsp中且网页显示500错误--%>
<%--<jsp:include page="b.jsp"></jsp:include>--%>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>我是b.jsp</h1>
<%="a===" + a%>
</body>
</html>
5.3 taglib指令
导入第三方的标签库
<%--这里的prefix="c"表示使用时用c来调用库中方法,后面会介绍该库--%>
<%--@taglib prefix="c" url="http://java.sun.com/jsp/jstl/core"%>
六、内置对象
又称为隐式对象。表示在jsp页面上不需要定义就可以直接使用的对象
原因:在jsp页面上写的java代码,会自动转译时放到service方法中,而该方法默认会定义一些变量以及对应的方法参数可以直接在方法中使用,统称为内置对象。(例如前面使用过的out)
输入输出对象:
request
response
out
作用域对象:
- request(与上面重复)
session
applocation
pageContext
:页面作用域,仅在当前页面有效servlet相关对象:
config
:servlet配置page
:就是this异常对象:
exception
,只有isErrorPage=true时才能使用
如果要保障系统正常运行,一般使用在
web.xml
中配置错误页面
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<error-page>
<!-- 当系统报500错误时,会自动跳转到500.jsp页面-->
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
<error-page>
<!-- 当系统报400错误时,会自动跳转到404.jsp页面-->
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
</web-app>
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
<title>Title</title>
</head>
<body>
404
<%=exception.getMessage()%>
</body>
</html>
七、EL表达式
表达式语言。主要用来获取某个作用域的数据
语法:
${某个作用域的对象}
EL表达式如果不写作用域,会从小到大每个作用域逐一寻找,找到后停止,如果所有的都没有找到,就不显示。如果只需要在某个作用域中寻找,可以指定作用域。
<%
request.setAttribute("name","lmz");
request.setAttribute("age",null);
// 创建一个类的对象,Student是我们自定义的类,里面有id和name字段4
Student student = new Student(1,"zhangsan");
request.setAttribute("stu",student);
%>
<%
String n = (String)request.getAttribute("name");
String a = (String)request.getAttribute("age");
out.print(n + "<br>");
// 脚本会显示null
out.print(a + "<br>");
%>
${name}
<%-- EL不会显示null--%>
${age}<br>
<%-- 以下是对象的使用,可见使用EL语言比较简便 --%>
<%
Student s = (Student)request.getAttribute("stu");
out.print(s.getId() + "<br>");
out.print(s.getName() + "<br>");
%>
${stu.id}
${stu.name}
${"3" + "5"}
${"5" / 2}
<%--上面输出为8 2.5--%>
<%--下面这条语句会直接让网页报错,因为字符串A无法转成数字--%>
<%--${'A' + 1}--%>
与JSP脚本的区别
- 脚本会出现null,而EL会忽略null,为null时不显示
注意:如果使用EL引用一个对象,例如:
${user.name}
会自动拼接get,然后去调用getName方法,如果没有提供getName方法,则不显示,并非调用属性。
**注意:**map的key可以使用.a也可以使用[“a”],中括号中的引号使用双引号和单引号都可以,因为是一样的。
EL中的运算符:
+、-、*、/、%、==(eq)、>(gt)、<(lt)、>=(ge)、<=(le)、&&(and)、||(or)、!(not)
empty判断是否为空
注意:
+
只能做数字运算,不能拼接字符串,$("1"+"2")
得到3,+两边如果有字符串会自动先转换为数字,在计算,如果无法完成转换,就报错。$(5/2)
结果为2.5
八、JSTL
全称Java Server Pages Standard Tag Library,JSP标准标签库
常用的是
c:if、c:choose、c:forEach 、日期格式化
用来在JSP页面上处理常见逻辑
使用步骤:
- 先导入jar包
- 导入标签库
注意:页面上的JSTL一定要与EL结合使用
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--第一个大于会显示,第二个不会,因为是根据判断条件来,当为true才显示--%>
<c:if test="${5 > 3}">
<h1>大于</h1>
</c:if>
<c:if test="${2 > 3}">
<h1>大于</h1>
</c:if>
<%--该语句犹如if..else if...else if...else--%>
<c:choose>
<c:when test="$(1 > 3)">1大于3</c:when>
<c:when test="$(2 > 3)">2大于3</c:when>
<c:when test="$(3 > 3)">2大于3</c:when>
<c:otherwise>不大于3</c:otherwise>
</c:choose>
<br>
<%
String [] arr = {"h","e","l","l","o","w","o","r","l","d"};
request.setAttribute("arr",arr);
Date date = new Date();
request.setAttribute("now",date);
%>
<br>
<%--循环,var是变量(遍历时值由变量接收) begin(下标从哪开始遍历)end(遍历到下标几结束) step(步长,相当于自增几或者自减几)
varStatus(用于指定循环的状态变量,该属性有四个状态变量,index 索引下标(从0开始) count 计数(从1开始) first(是否为第一次循环) last(是否为最后一次循环))--%>
<c:forEach items="${arr}" var="s" begin="2" end="5" varStatus="m">
${m.index}=====${s}<br>
</c:forEach>
<br>
<%--格式化时间,切记中间不能加空格或者回车,可以直接结束--%>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss"></fmt:formatDate><br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss"/>
</body>
</html>
九、MVC模式
是一种编程思路,设计模式
M:model,模型层,实体类,也包含DAO(数据模型)、Service(业务模型)
V:View,视图层,用来展示页面
C:Controlier,控制层,Servlet,用来处理请求,和返回响应
讲项目设计分为MVC三层,称为MVC设计模式
**核心思想:**将视图和模型分离,以提高项目的可扩展性和可维护性
- 为什么要将视图和模型分离?
- 在早期,项目的开发都是使用JSP页面上直接调用模型层代码,称为
模式1
即由视图(JSP)和模型(Model)组成,并且在视图上大量的调用模型代码- 后来,项目要求越来越复杂,功能越来越多,需求经常变更。导致页面的代码非常多,而且很杂乱。导致基本无法维护,维护成本高,业内就探索出另一种方案。该方案要求
不能在页面上写代码。
使用Servlet和JSP,JSP侧重与写页面标签,Servlet侧重于写代码。将这种模式称为模式2
,即将视图和模型分离,使用Controller(Servlet)层来建立视图和模型的关系。后来发展为MVC模式。- 如何将视图和模型分离?
- 使用Servlet(Controller)层
经典面试题:什么是MVC?
MVC是模型视图控制器,将视图和模型分离,主要是用来体现控制器这一层,避免在页面中写代码,提高项目的可扩展性和可维护性。
十、Servlet封装
10.1 概述
将增删改查的Servlet改造成普通的方法放在一个
实例类名Servlet
中,这个Servlet类继承一个通用的BeanServlet
,BeanServlet
继承 HttpServlet,当需要调用实例类名Servlet
的A方法
时,优先调用父类BeanServlet
的doPost
通用,通过反射去调用实例类名Servlet
的A方法
例如:如果使用下面方法封装,下面这个jsp页面那么会调用子类的
findAll
方法
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="student.do?method=findAll">查看所有用户信息</a>
</body>
</html>
父类:BeanServlet
//创建一个BaseServlet类来判断此时要执行什么方法
public class BaseServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;utf-8");
//规定jsp调用Servlet时,需要在后面拼接方法名,当此时你要添加数据时,后面要 '?add' ,这个add是子类StudentServlet定义的方法
String methodName = request.getParameter("method");
//当没有拼接时默认为查找全部 findAll 方法
if (methodName == null || methodName.trim().equals("")){
methodName = "findAll";
}
try {
//利用反射来执行拼接的方法,this表示当前对象,即调用post方法的类,此处只有一个StudentServlet子类,且子类会被jsp页面使用student.do找到,所以此处指这个子类(子类调用父类的post方法。this即自己)
//利用反射先找到类中的方法,且方法的参数有两个,然后再用invoke执行当前对象的方法
this.getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class).invoke(this,request,response);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
子类:StudentServlet
//将所有的增删改查Servlet都封装到一个类中成为方法,该类要继承BaseServlet
//父类会使用反射来执行子类的方法
@WebServlet("/student.do")
public class StudentServlet extends BaseServlet{
//添加
public void add(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
String[] hobby = request.getParameterValues("hobby");
String phone = request.getParameter("phone");
StudentService studentService = new StudentServiceImpl();
studentService.add(Student.builder()
.username(username).pwd(password).age(Integer.parseInt(age))
.sex(sex).hobby(String.join(",",hobby)).phone(phone).build());
response.sendRedirect("student.do");
}
//删除
public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException {
String id = request.getParameter("id");
StudentService studentService = new StudentServiceImpl();
studentService.delete(Integer.valueOf(id));
response.sendRedirect("student.do");
}
//修改
public void update(HttpServletRequest request, HttpServletResponse response) throws IOException {
StudentServiceImpl studentService = new StudentServiceImpl();
request.setCharacterEncoding("utf-8");
String id = request.getParameter("id");
String username = request.getParameter("username");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
String[] hobby = request.getParameterValues("hobby");
String phone = request.getParameter("phone");
studentService.update(Student.builder().id(Integer.parseInt(id))
.username(username).age(Integer.parseInt(age))
.sex(sex).hobby(String.join(",",hobby)).phone(phone).build());
response.sendRedirect("student.do");
}
//查找全部
public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
StudentService studentService = new StudentServiceImpl();
List<Student> list = studentService.findALL();
request.setAttribute("list", list);
request.getRequestDispatcher("all.jsp").forward(request,response);
}
//根据id查找,然后再调用update更新
public void preUpdate(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
StudentServiceImpl studentService = new StudentServiceImpl();
Student updateStu = studentService.findByID(Integer.parseInt(id));
request.setAttribute("stu",updateStu);
request.getRequestDispatcher("update.jsp").forward(request,response);
}
}
10.2 案例
使用MVC模式写一个增删改查的案例(资料中有案例代码)
- 在数据库中建立一个Student表,表中字段有id,name,password,sex,age,hobby,phone字段,且id可以设置为自动增长,用户注册时不用输入id。
- 前端网页让用户选择操作,当选择添加时,用户输入信息,然后提交保存到数据库,回显所有的用户信息到前端,当选择删除时就删除,修改就修改用户信息,不修密码(可以使用bootstrap来优化前端页面)
10.2.1 前端
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 1.head中需要设置移动设备优先 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$Title$</title>
</head>
<body>
<div class="container-fluid">
<a href="student.do">查看所有用户信息</a>
</div>
</body>
</html>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/css/bootstrap.css">
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<script src="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/js/bootstrap.js"></script>
all.jsp
<%@ page import="com.lmz.demo1.entity.Student" %>
<%@ page import="java.util.List" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 1.head中需要设置移动设备优先 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>用户信息</title>
</head>
<body>
<a class="btn btn-success" href="add.jsp">添加用户</a>
<table class="table table-bordered table-hover">
<thead>
<tr>
<td>ID</td>
<td>用户名</td>
<td>年龄</td>
<td>性别</td>
<td>爱好</td>
<td>电话</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<c:forEach items="${list}" var="s">
<tr>
<td>${s.getId()}</td>
<td>${s.getUsername()}</td>
<td>${s.getAge()}</td>
<td>${s.getSex()}</td>
<td>${s.getHobby()}</td>
<td>${s.getPhone()}</td>
<td>
<a class="btn btn-info" href="student.do?method=preUpdate&id=${s.getId()}" >修改</a>
<a class="btn btn-danger" href="student.do?method=delete&id=${s.getId()}" οnclick="return confirm('确定要删除吗?')">删除</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/css/bootstrap.css">
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<script src="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/js/bootstrap.js"></script>
update.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 1.head中需要设置移动设备优先 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body>
<div class="container-fluid">
<div class="">
<h1>修改用户信息</h1>
</div>
<form action="student.do?method=update" method="post" role="form" class="form-horizontal">
<input type="text" name = "id" hidden value="${stu.id}">
<div class="form-group">
<label class="control-label col-sm-1">用户名:</label>
<div class="col-sm-2">
<input type="text" name="username" placeholder="用户名" class="form-control" value="${stu.username}">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">年 龄: </label>
<div class="col-sm-2">
<input type="text" name="age" value="${stu.age}" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">性 别:</label>
<div class="col-sm-2">
<label class="radio-inline"><input type="radio" name="sex" value="男" <c:if test="${stu.sex eq '男'}">checked</c:if>>男 </label>
<label class="radio-inline"><input type="radio" name="sex" value="女" <c:if test="${stu.sex eq '女'}">checked</c:if>>女</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">爱 好: </label>
<div class="col-sm-2">
<label class="checkbox-inline"><input type="checkbox" name="hobby" value="吃饭" <c:if test="${stu.hobby.indexOf('吃饭') != -1}">checked</c:if>>吃饭</label>
<label class="checkbox-inline"><input type="checkbox" name="hobby" value="睡觉" <c:if test="${stu.hobby.indexOf('睡觉') != -1}">checked</c:if>>睡觉</label>
<label class="checkbox-inline"><input type="checkbox" name="hobby" value="打豆豆" <c:if test="${stu.hobby.indexOf('打豆豆') != -1}">checked</c:if>>打豆豆</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">电 话:</label>
<div class="col-sm-2">
<input type="text" name="phone" class="form-control" placeholder="电话" value="${stu.phone}" />
</div>
</div>
<div class="col-sm-offset-1">
<input type="submit" value="提交" class="btn btn-success" />
<input type="reset" value="重置" class="btn btn-info" />
</div>
</form>
</div>
</body>
</html>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/css/bootstrap.css">
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<script src="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/js/bootstrap.js"></script>
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 1.head中需要设置移动设备优先 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body>
<div class="container-fluid">
<div class="page-header">
<h1>用户注册</h1>
</div>
<form action="student.do?method=add" method="post" role="form" class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-1">用户名:</label>
<div class="col-sm-2">
<input type="text" name="username" placeholder="用户名" class="form-control"><br>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">密    码:</label>
<div class="col-sm-2">
<input type="password" name="pwd" placeholder="密码" class="form-control"><br>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">年    龄:</label>
<div class="col-sm-2">
<input type="text" name="age" value="18" class="form-control"><br>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">性    别:</label>
<div class="col-sm-2">
<label class="radio-inline"><input type="radio" name="sex" value="男" checked>男</label>
<label class="radio-inline"><input type="radio" name="sex" value="女">女</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">爱    好:</label>
<div class="col-sm-2">
<label class="checkbox-inline"><input type="checkbox" name="hobby" value="吃饭">吃饭</label>
<label class="checkbox-inline"><input type="checkbox" name="hobby" value="睡觉">睡觉</label>
<label class="checkbox-inline"><input type="checkbox" name="hobby" value="打豆豆">打豆豆</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-1">电 话:</label>
<div class="col-sm-2">
<input type="text" name="phone" placeholder="电话" class="form-control"><br>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-1">
<input type="submit" value="提交" class="btn btn-info active"/>
<input type="reset" value="重置" class="btn btn-info active"/>
</div>
</div>
</form>
</div>
</body>
</html>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/css/bootstrap.css">
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<script src="${pageContext.request.contextPath}/css/bootstrap-3.4.1-dist/js/bootstrap.js"></script>
10.2.2 后端
10.2.2.1 Servlet
使用Servlet封装,只有两个类(BeanServlet、StudentServlet)
//创建一个BaseServlet类来判断此时要执行什么方法
public class BaseServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;utf-8");
//规定jsp调用Servlet时,需要在后面拼接方法名,当此时你要添加数据时,后面要 '?add' ,这个add是子类StudentServlet定义的方法
String methodName = request.getParameter("method");
//当没有拼接时默认为查找全部 findAll 方法
if (methodName == null || methodName.trim().equals("")){
methodName = "findAll";
}
try {
//利用反射来执行拼接的方法,this表示当前对象,即调用post方法的类,此处只有一个StudentServlet子类,且子类会被jsp页面使用student.do找到,所以此处指这个子类(子类调用父类的post方法。this即自己)
//利用反射先找到类中的方法,且方法的参数有两个,然后再用invoke执行当前对象的方法
this.getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class).invoke(this,request,response);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
//将所有的增删改查Servlet都封装到一个类中成为方法,该类要继承BaseServlet
@WebServlet("/student.do")
public class StudentServlet extends BaseServlet{
//添加
public void add(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
String[] hobby = request.getParameterValues("hobby");
String phone = request.getParameter("phone");
StudentService studentService = new StudentServiceImpl();
studentService.add(Student.builder()
.username(username).pwd(password).age(Integer.parseInt(age))
.sex(sex).hobby(String.join(",",hobby)).phone(phone).build());
response.sendRedirect("student.do");
}
//删除
public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException {
String id = request.getParameter("id");
StudentService studentService = new StudentServiceImpl();
studentService.delete(Integer.valueOf(id));
response.sendRedirect("student.do");
}
//修改
public void update(HttpServletRequest request, HttpServletResponse response) throws IOException {
StudentServiceImpl studentService = new StudentServiceImpl();
request.setCharacterEncoding("utf-8");
String id = request.getParameter("id");
String username = request.getParameter("username");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
String[] hobby = request.getParameterValues("hobby");
String phone = request.getParameter("phone");
studentService.update(Student.builder().id(Integer.parseInt(id))
.username(username).age(Integer.parseInt(age))
.sex(sex).hobby(String.join(",",hobby)).phone(phone).build());
response.sendRedirect("student.do");
}
//查找全部
public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
StudentService studentService = new StudentServiceImpl();
List<Student> list = studentService.findALL();
request.setAttribute("list", list);
request.getRequestDispatcher("all.jsp").forward(request,response);
}
//根据id查找,然后再调用update更新
public void preUpdate(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
StudentServiceImpl studentService = new StudentServiceImpl();
Student updateStu = studentService.findByID(Integer.parseInt(id));
request.setAttribute("stu",updateStu);
request.getRequestDispatcher("update.jsp").forward(request,response);
}
}
10.2.2 Utils
工具类,定义了常量接口(sql语句)和数据库连接
public interface Constant {
//因为是查询,所以查询的所有字段必须和实例类属性名一致
String FINDALL = "SELECT id,name as username,age,sex,hobby,phone FROM student;";
String ADD = "INSERT INTO student(name,age,sex,hobby,phone) VALUES (?,?,?,?,?);";
String DELETEBYID = "DELETE FROM student WHERE id = ?;";
String UPDATEBYID = "UPDATE student SET name=?,age=?,sex=?,hobby=?,phone=? where id = ?;";
String FINDBYID = "SELECT id,name as username,age,sex,hobby,phone FROM student where id = ?;";
}
public class DBConnection {
private static final ThreadLocal<Connection> threadLocal = new ThreadLocal();
private static final Properties PROPERTIES = new Properties();
private static DataSource dataSource;
static {
try {
InputStream inputStream = DBConnection.class.getResourceAsStream("/mysqlconfig.properties");
PROPERTIES.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(PROPERTIES);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource;
}
public static Connection getConnection() throws SQLException {
Connection connection = threadLocal.get();
if (connection == null || connection.isClosed()){
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
}
public static void close() throws SQLException {
Connection connection = threadLocal.get();
if (connection!=null && !connection.isClosed()){
connection.close();
threadLocal.remove();
}
}
}
10.2.3 Service
定义了service层接口以及实现类
public interface StudentService {
boolean add(Student student);
boolean delete(int id);
boolean update(Student student);
List<Student> findALL();
Student findByID(int id);
}
public class StudentServiceImpl implements StudentService {
StudentDAO studentDAO = new StudentDAOImpl();
@Override
public boolean add(Student student) {
try {
return studentDAO.add(student);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return false;
}
@Override
public boolean delete(int id) {
try {
return studentDAO.delete(id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return false;
}
@Override
public boolean update(Student student) {
try {
return studentDAO.update(student);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return false;
}
@Override
public List<Student> findALL() {
try {
return studentDAO.findALL();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public Student findByID(int id){
try {
return studentDAO.findByID(id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
}
10.2.4 entity
实例类,此处我们使用了lombok注解代替了有参和无参构造,以及getter和setter方法
@Data
:如果你想使数据类尽可能精简,可以使用 @Data 注解。 @Data 是 @Getter、 @Setter、 @ToString、 @EqualsAndHashCode 和 @RequiredArgsConstructor 的快捷方式。
@NoArgsConstructor
:无参构造
@AllArgsConstructor
:有参构造
@Builder
:建造者设计模式描述了一种灵活的创建对象的方式。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Student {
private int id;
private String username;
private String pwd;
private int age;
private String sex;
private String hobby;
private String phone;
}
10.2.5 DAO
DAO层,定义了数据库的操作接口,以及接口的实现类
public interface StudentDAO {
boolean add(Student student) throws SQLException;
boolean delete(int id) throws SQLException;
boolean update(Student student) throws SQLException;
List<Student> findALL() throws SQLException;
Student findByID(int id) throws SQLException;
}
public class StudentDAOImpl implements StudentDAO {
QueryRunner queryRunner = new QueryRunner(DBConnection.getDataSource());
@Override
public boolean add(Student student) throws SQLException {
//name,age,sex,hobby,phone
//update不需要字段名和属性一致,因为底层没有反射
return queryRunner.update(Constant.ADD,student.getUsername(),
student.getAge(),student.getSex(),student.getHobby(),student.getPhone()) > 0;
}
@Override
public boolean delete(int id) throws SQLException {
return queryRunner.update(Constant.DELETEBYID,id) > 0;
}
@Override
public boolean update(Student student) throws SQLException {
//name=?,age=?,sex=?,hobby=?,phone=? where id = ?;
return queryRunner.update(Constant.UPDATEBYID,student.getUsername(),student.getAge()
,student.getSex(),student.getHobby(),student.getPhone(),student.getId()) > 0;
}
@Override
public List<Student> findALL() throws SQLException {
return queryRunner.query(Constant.FINDALL,new BeanListHandler<>(Student.class));
}
@Override
public Student findByID(int id) throws SQLException{
return queryRunner.query(Constant.FINDBYID,new BeanHandler<>(Student.class),id);
}
}
十一、分页
界面实现:页数、条数、当前页、上一页、下一页、首页、尾页、通过数字来点击第几页
数据库实现:通过limlt来实现
limit(page-1)*size,size
后台实现:主要是需要一个PageBean对象来处理分页效果
(上一个案例中有分页代码)
//实例类
@Data
public class PageBean {
public final static int PAGE_SIZE = 2;//一页展示多少条数据
private int pages;//一共多少页
private int count;//一共多少数据
private int current;//当前页数
private int prev;//上一页数
private int next;//下一页数
private int first = 1;//第一页
private int last;//最后一页
private boolean hasPrev;//是否有上一页
private boolean hasNext;//是否有下一页
private List list;//当前页有哪些数据
private int[] numbers;
public PageBean(int current, int count, List list) {
this.count = count;
this.current = current;
this.list = list;
//求出总页数,根据数据条数%每页展示数得出,当有5条数据,且每页显示2条时,一共有 5 / 2 + 1 = 3页
this.pages = count % PAGE_SIZE == 0 ? count / PAGE_SIZE : count / PAGE_SIZE + 1;
this.prev = current - 1;
this.next = current + 1;
this.last = this.pages;
this.hasPrev = current != 1;//当不是第一页时为true,是第一页为false
this.hasNext = current != this.last;//当不是最后一页时为true,是第一页为false
//计算当前页左右的页数,当前面满足四页就显示前四页页数让其用户可以点击,后面满足四页就显示当前页数+4页让用户点击
int start = this.current > 4 ? this.current - 4 : 1;
int end = this.pages - this.current >= 4 ? this.current + 4 : this.pages;
this.numbers = new int[end - start + 1];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = start++;
}
}
}
//Service核心代码
@Override
//page表示当前的页数
public PageBean findALL(int page) {
try {
//count方法是统计有多少数据
int count = (int)studentDAO.count();
//根据页数计算跳过多少数据
int skip = (page - 1) * PageBean.PAGE_SIZE;
//当用户选择的数据比数据库中数据多,就直接返回null,不允许
if (skip > count)return null;
//搜索数据库中在 skip和 skip+PageBean.PAGE_SIZE之间的数据,包装成对象集合返回
List<Student> all = studentDAO.findALL(skip, PageBean.PAGE_SIZE);
//new一个PageBean返回给前端,传入页数,所有数据,以及得到的在范围内见的数据
return new PageBean(page, count, all);
} catch (Exception throwables) {
throwables.printStackTrace();
}
return null;
}
//Servlet核心代码
//查找全部
public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
StudentService studentService = new StudentServiceImpl();
//得到前端此时的页数
String page = request.getParameter("page");
int npage = 1;
try {
//判断是否为第一次执行,因为第一次是index页面,并没有传入page
npage = Integer.parseInt(page);
}catch (Exception e){
//当index页点击进入时会进入这里,此时用默认1
}
//进入Service核心代码
PageBean bean = studentService.findALL(npage);
//将得到的PageBean设置成值传给下一个页面
request.setAttribute("bean", bean);
request.getRequestDispatcher("all.jsp").forward(request,response);
}
<%-- 前端核心代码--%>
<%-- 分页--%>
<div>
<label>一共有${bean.count}条数据,一共${bean.pages}页,当前在第${bean.current}页</label>
</div>
<c:choose>
<c:when test="${bean.current == 1}">首页</c:when>
<c:otherwise>
<a href="student.do?page=1">首页</a>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${bean.hasPrev}">
<a class="btn btn-info" href="student.do?page=${bean.prev}">上一页</a>
</c:when>
<c:otherwise><span class="btn btn-default">上一页</span></c:otherwise>
</c:choose>
<c:forEach items="${bean.numbers}" var="p">
<c:choose>
<c:when test="${bean.current == p}">${p}</c:when>
<c:otherwise><a href="student.do?page=${p}">${p}</a></c:otherwise>
</c:choose>
</c:forEach>
<c:choose>
<c:when test="${bean.hasNext}">
<a class="btn btn-info" href="student.do?page=${bean.next}">下一页</a>
</c:when>
<c:otherwise><span class="btn btn-default">下一页</span></c:otherwise>
</c:choose>
<c:choose>
<c:when test="${bean.current == bean.last}">尾页</c:when>
<c:otherwise>
<a href="student.do?page=${bean.last}">尾页</a>
</c:otherwise>
</c:choose>
十二、文件上传和下载
12.1 文件上传
前端页面编写:
- 必须使用post方式(因为当文件过大时,get请求将文件放入url不够放)
- 必须添加enctype属性,并设置为文件上传
enctype="multipart/form-data"
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%-- 如果不设置enctype="multipart/form-data"属性,那么后端接收的只是图片的名称,设置后相当于前端会将这些变成流传递给后端--%>
<form action="upload.do" method="post" enctype="multipart/form-data"><br>
用户名:<input type="text" name="username" value="" placeholder="用户名"><br>
密码:<input type="password" name="pwd" value=""><br>
图片:<input type="file" name="photo"><br>
<input type="submit"><br>
</form>
</body>
</html>
后台编写:
- 添加
@MultipartConfig
注解
@MultipartConfig//如果不使用该注解,前端设置enctype属性后,后端值全为null
@WebServlet("/upload.do")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
// String photo = request.getParameter("photo");
//设置注解后,文件需要使用getPart来接收
Part photo = request.getPart("photo");
System.out.println(username);
System.out.println(pwd);
System.out.println(photo.getName());
System.out.println(photo.getContentType());
//调用写方法将文件写到指定地方,我们可以使用UUID来生成一个随机字符串作为文件名称上传
String fileName = UUID.randomUUID().toString().replaceAll("-", "");
photo.write("D:\\study\\练习目录\\" + fileName);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
public interface Constans {
//文件存放位置
String PATH = "D:\\study\\练习目录\\";
}
12.2 在线查看
在上传的基础上,将文件名称传递给下一个页面,然后根据名称建立输入输出流来获取图片查看
因此,数据库中只需要存放图片的名称,根据存放的图片名称就可以将图片查找出来
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
Part photo = request.getPart("photo");
String fileName = UUID.randomUUID().toString().replaceAll("-", "");
photo.write(Constans.PATH + fileName);
//将名称记录,传递给show.jsp
request.setAttribute("filename",fileName);
System.out.println(fileName);
request.getRequestDispatcher("show.jsp").forward(request,response);
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--调用show.do--%>
<img src="show.do?filename=${filename}">
</body>
</html>
@WebServlet("/show.do")
public class ShowServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取传递的filename
String filename = request.getParameter("filename");
//获取输入流(地址为文件保存的路径,即设置的常量地址 + 得到的文件名称)
InputStream is = new BufferedInputStream(new FileInputStream(Constans.PATH + filename));
//获得响应输出流
ServletOutputStream sos = response.getOutputStream();
byte[] bytes = new byte[1024 * 8];
int len = 0;
//边读边写
while ((len = is.read(bytes)) != -1) {
sos.write(bytes, 0, len);
}
sos.flush();
sos.close();
is.close();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
12.3 下载
将前面在线查看的代码中加一句响应流头信息设置即可
//后面的1.jpg代表下载时的名称
response.setHeader("Content-Disposition","attachment;filename=1.jpg");
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--调用show.do--%>
<img src="show.do?filename=${filename}">
<a href="show.do?filename=${filename}">下载图片</a>
</body>
</html>
在查看的基础上加一句响应头信息即可
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getParameter("filename");
//响应流头信息设置为下载
response.setHeader("Content-Disposition","attachment;filename=1.jpg");
InputStream is = new BufferedInputStream(new FileInputStream(Constans.PATH + filename));
ServletOutputStream sos = response.getOutputStream();
byte[] bytes = new byte[1024 * 8];
int len = 0;
while ((len = is.read(bytes)) != -1) {
sos.write(bytes, 0, len);
}
sos.flush();
sos.close();
is.close();
}