很多人大学的第一个小项目就是使用JavaWeb实现了一个学生管理系统或者是图书管理系统。在没有项目经验的情况下,前后端都需要自己去完成,还是要耗费不少时间精力的。本次我就分享一下我在大学期间完成的第一个小项目:学生管理系统。采用的技术有:Thymeleaf+Ajax+HTML+JQuery+Java+MySQL。下面会进行详细介绍,也可以关注微信公众号【橙晴丰Ciao】,私信我获取项目源码:JavaWeb学生管理系统。
效果演示
如何实现上面图片所实现的效果呢?接下来我将进行一个简单介绍。
项目搭建
- 安装好Java环境【验证方式:同时点击win+r输入java -version】
- 安装好MySQL环境
- 先→创建一个JavaWeb项目,然后再实现具体的功能。
项目结构
JavaWeb项目搭建好后,就可以进行开发了,为了让第一次学习的小伙伴更加清楚地知道项目结构,我先把开发好后的结构图展示一下:
数据库搭建
在MySQL数据库中创建一个student_info
数据库,用于存储我们要操作的学生信息表以及管理员登录表。
CREATE DATABASE student_info;
DROP TABLE IF EXISTS `t_login`;
CREATE TABLE `t_login` (
`user_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_login
-- ----------------------------
INSERT INTO `t_login` VALUES ('admin', 'admin');
-- ----------------------------
-- Table structure for `t_student`
-- ----------------------------
DROP TABLE IF EXISTS `t_student`;
CREATE TABLE `t_student` (
`student_id` int(11) NOT NULL AUTO_INCREMENT,
`student_name` varchar(100) DEFAULT NULL,
`student_class` varchar(100) DEFAULT NULL,
PRIMARY KEY (`student_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1704134100 DEFAULT CHARSET=utf8;
项目开发
项目对数据库的操作是必不可少的,所以我们先进行数据库方面的配置。在resource文件夹中进行数据库连接方面的配置:
常用配置
druid.properties
数据库可能存在时区问题,根据报错提示网上查找解决方案修改url即可
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student_info?
username=root
password=root
initialSize=5
maxActive=10
maxWait=1000
JDBCUtil
在src/utils
工具包中创建JDBCUtil工具类进行数据库连接等操作:
package com.lzk.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtil {
private static DataSource dataSource;
static {
try {
//读取配置文件,创建连接池
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");
Properties properties = new Properties();
properties.load(inputStream);
//使用DruidDataSourceFactory创建连接池
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接池
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
/**
* 获取连接
* @return
*/
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* 归还连接的方法
* @param connection
*/
public static void releaseConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
BaseDao
在src/utils
工具包中创建BaseDao工具类进行数据增删改查等操作:
package com.lzk.utils;
import com.lzk.utils.JDBCUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class BaseDao<T> {
private QueryRunner queryRunner = new QueryRunner();
/**
* 批处理方法
* @param sql
* @param paramArr
* @return
*/
public int[] batchUpdate(String sql,Object[][] paramArr){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.batch(conn,sql,paramArr);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
/**
* 执行增删改的sql语句
* @param sql
* @param params
* @return
*/
public int update(String sql,Object... params){
Connection conn = JDBCUtil.getConnection();
//执行增删改的sql语句,返回受到影响的行数
try {
return queryRunner.update(conn,sql,params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
/**
* 执行查询一行数据的sql语句,将结果集封装到JavaBean对象中
* @param clazz
* @param sql
* @param params
* @return
*/
public T getBean(Class<T> clazz,String sql,Object... params){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.query(conn,sql,new BeanHandler<>(clazz),params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
/**
* 执行查询多行数据的sql语句,并且将结果集封装到List<JavaBean>
* @param clazz
* @param sql
* @param params
* @return
*/
public List<T> getBeanList(Class<T> clazz, String sql, Object... params){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.query(conn,sql,new BeanListHandler<>(clazz),params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
//查询数据条数
public long findCount(String sql, Object... params) throws SQLException {
Connection conn = JDBCUtil.getConnection();
try {
long count= queryRunner.query(conn,sql,new ScalarHandler<>(),params);
return count;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
JDBCUtil.releaseConnection(conn);
}
}
}
数据模型层
User
用户实体类,跟数据库中的t_login
表对应,存储管理员的信息。要有构造方法(无参、多参)以及getter和setter方法。
public class User {
private String userId;
private String userPassword;
}
Student
学生实体类,对应学生信息表。要有构造方法(无参、多参)以及getter和setter方法。
public class Student {
private Integer studentId;
private String studentName;
private String studentClass;
}
PageInfo
分页信息实体类,不涉及数据库存储,只用于分页信息展示。要有构造方法(无参、多参)以及getter和setter方法。
public class PageInfo {
//当前页 currentPage
private Long currentPage;
//页面大小
private Long pageSize = 5L;
//总数据totalCount
private Long totalCount;
//总页数totalPage
private Long totalPage;
//上一页
private Long previousPage;
//下一页
private Long nextPage;
}
以上实体类都需要有getter和setter方法,否则在进行Thymeleaf模板渲染时就会出错。
web.xml配置文件
这个文件在web/WEB-INF
,可以通过配置此文件进行控制层的映射。
<?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">
<!-- 把首页指向portalServlet,表示请求路径为/时,交由portalServlet进行处理 -->
<welcome-file-list>
<welcome-file>portalServlet</welcome-file>
<!-- 也可以通过这种方式配置首页,但login页面有使用thymeleaf语法,直接使用静态资源访问方式需要进行页面变量修改 -->
<!-- <welcome-file>/WEB-INF/view/login.html</welcome-file>-->
</welcome-file-list>
<!-- 在上下文参数中配置视图前缀和视图后缀,这个是使用了Thymeleaf后需要进行配置的,
表示经过servlet渲染后返回的页面的路径以及后缀 -->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/WEB-INF/view/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
<!-- 这个servlet由项目中哪个包下的servlet进行处理 -->
<servlet>
<servlet-name>PortalServlet</servlet-name>
<servlet-class>com.lzk.servlet.model.PortalServlet</servlet-class>
</servlet>
<!-- 处理请求学生数据时路径的映射配置 -->
<servlet-mapping>
<servlet-name>StudentServlet</servlet-name>
<url-pattern>/student</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>StudentServlet</servlet-name>
<servlet-class>com.lzk.servlet.model.StudentServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>CodeServlet</servlet-name>
<servlet-class>com.lzk.servlet.model.CodeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CodeServlet</servlet-name>
<url-pattern>/code</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>PortalServlet</servlet-name>
<url-pattern>/portalServlet</url-pattern>
</servlet-mapping>
</web-app>
经过上面的配置后,当访问首页/时,就会交由portalServlet
进行处理。
ProtalServlet
package com.lzk.servlet.model;
import com.lzk.servlet.base.ViewBaseServlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class PortalServlet extends ViewBaseServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解析模板
processTemplate("login", request, response);
}
}
protalServlet进行请求处理时会将login页面进行渲染后返回,之所以可以实现模板渲染,是因为继承了ViewBaseServlet
类。
ViewBaseServlet
这个类是基于Thymeleaf实现的可以进行模板渲染,继承自该类的所有子实现类也具有相同的功能。
package com.lzk.servlet.base;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
login页面
在ProtalServlet中会进行模板渲染login页面,也就是登录时候的界面。因为我们在web.xml中进行了模板渲染视图上下文配置,所以该页面需要在/WEB-INF/view/
中,并且为html形式。
这个页面就是一个登录表单,就是样式比较多
<!doctype html>
<html lang="zh-CN" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/favicon.ico">
<link rel="canonical" href="https://getbootstrap.com/docs/3.4/examples/signin/">
<title>学生管理系统登录界面</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/examples/signin/signin.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/assets/js/ie8-responsive-file-warning.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/assets/js/ie-emulation-modes-warning.js"></script>
<script language="javascript" th:inline="javascript">
function flushCode() {
// 每次刷新的时候获取当前时间,防止浏览器缓存刷新失败
var time = new Date();
document.getElementById("scode").src="/StudentManage01/code?method=imageCode&time="+time;
}
</script>
<style type="text/css">
body{
background-size:auto,auto;
background-repeat: no-repeat;
}
.code-class{
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
color: inherit;
font: inherit;
margin: 0;
width: 180px;
height: 44px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
}
.identity{vertical-align: middle;height: 34px}
</style>
</head>
<body>
<div class="container">
<form class="form-signin" th:action="@{/student(method='Login')}" method="post">
<h1>LOGIN</h1>
<input type="text" id="inputEmail" name="userId" class="form-control" placeholder="账号" required autofocus>
<br/>
<input type="password" id="inputPassword" name="userPassword" class="form-control" placeholder="密码" required>
<div >
<input type="text" id="inputCode" name="code" class="code-class" placeholder="验证码" required>
<img alt="验证码" id="scode" class="identity" onclick="flushCode()" th:src="@{/code(method='imageCode')}" >
</div>
<br/>
<input class="btn btn-lg btn-primary btn-block" type="submit" value="登录"><br/>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/assets/js/ie10-viewport-bug-workaround.js"></script>
</body>
</html>
登录页面使用了bootstrap,但是采用的是在线方式导入样式,所以需要联网才能显示效果。
可以看到页面有一个验证码的图片,这个验证码是通过后端进行绘制后返回给前端页面的。
访问的路径为:/StudentManage01/code?method=imageCode&time="+time;
在web.xml中有进行验证码路径的相关配置,发起的/code
请求会交由CodeServlet
进行处理。跟ProtalServlet一样,但是这里多了两个参数。我们先看下CodeServlet
是怎么对验证码进行处理的。
CodeConstant
验证码图片长宽的参数定义。
public class CodeConstant {
// 图片高度
public static final int IMG_HEIGHT = 100;
// 图片宽度
public static final int IMG_WIDTH = 30;
// 验证码长度
public static final int CODE_LEN = 4;
}
CodeServlet
package com.lzk.servlet.model;
import com.lzk.constant.CodeConstant;
import com.lzk.servlet.base.ModelBaseServlet;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
public class CodeServlet extends ModelBaseServlet {
public void imageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 用于绘制图片,设置图片的长宽和图片类型(RGB)
BufferedImage bi = new BufferedImage(CodeConstant.IMG_HEIGHT, CodeConstant.IMG_WIDTH, BufferedImage.TYPE_INT_RGB);
// 获取绘图工具
Graphics graphics = bi.getGraphics();
graphics.setColor(new Color(100, 230, 200)); // 使用RGB设置背景颜色
graphics.fillRect(0, 0, 100, 30); // 填充矩形区域
// 验证码中所使用到的字符
char[] codeChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456".toCharArray();
String captcha = ""; // 存放生成的验证码
Random random = new Random();
for(int i = 0; i < CodeConstant.CODE_LEN; i++) { // 循环将每个验证码字符绘制到图片上
int index = random.nextInt(codeChar.length);
// 随机生成验证码颜色
graphics.setColor(new Color(random.nextInt(150), random.nextInt(200), random.nextInt(255)));
// 将一个字符绘制到图片上,并制定位置(设置x,y坐标)
graphics.drawString(codeChar[index] + "", (i * 20) + 15, 20);
captcha += codeChar[index];
}
// 将生成的验证码code放入sessoin中
request.getSession().setAttribute("code", captcha);
// 通过ImageIO将图片输出
ImageIO.write(bi, "JPG", response.getOutputStream());
}
}
这个类中对请求的处理跟ProtalServlet不一样,他有更具体的方法进行处理,而ProtalServlet是只针对Post请求和Get请求进行处理,而Post请求又调用Get请求的方法,因此ProtalServlet对请求只有一种应对方法,当然要想访问ProtalServlet也只能通过/
进行。也就是一个请求路径对应一个Servlet,不管参数怎么变,对应的Servlet最多只能对不同请求进行两种不同的处理,一种为客户端发起Post请求时进行的处理,一种为Get。这种方式会非常繁琐,一是每个请求路径都要在web.xml进行配置,二是不好管理,例如对学生信息的增删改查,要写四个Servlet,而这四个Servlet关联性又很强。所以我们引入了ModelBaseServlet。
ModelBaseServlet
这个Servlet继承了ViewBaseServlet,所以也有模板渲染的功能并且通过暴力反射的方式去获取当前访问的方法名进行调用。所以当前端页面发起/StudentManage01/code?method=imageCode&time="+time;
请求时,就会路径映射到codeServlet并且暴力反射调用CodeServlet中的imageCode。这种方式就可以实现针对CodeServlet中对验证码不同请求有多种不同的处理方式,只需要在里面写不同的方法即可。
package com.lzk.servlet.base;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
public class ModelBaseServlet extends ViewBaseServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//获取请求参数method的值
String method = request.getParameter("method");
//method参数的值就是要调用的方法的方法名,那就是已知方法名要去查找调用本对象的方法
try {
Method declaredMethod = this.getClass().getDeclaredMethod(method, HttpServletRequest.class, HttpServletResponse.class);
//暴力反射
declaredMethod.setAccessible(true);
//调用方法
declaredMethod.invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
StudentServlet
现在是对学生信息数据方面的处理,通过该Servlet实现学生数据的增删改查。在这个Servlet中就可以看到引入的ModelBaseServlet的作用。里面可以实现四个方法,请求时只需要通过method参数指定请求的方法即可。
在登录页面点击登录按钮后会向@{/student(method='login')}
发起请求,即调用login方法进行验证,成功则到list
页面,失败则到·error
页面。该servlet需渲染的页面后面都会提供。
Servlet在进行业务处理时会调用Service层,Servcie层调用Dao层。
package com.lzk.servlet.model;
import com.lzk.bean.PageInfo;
import com.lzk.bean.Student;
import com.lzk.service.StudentService;
import com.lzk.service.impl.StudentServiceImpl;
import com.lzk.servlet.base.ModelBaseServlet;
import org.apache.commons.beanutils.BeanUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.List;
public class StudentServlet extends ModelBaseServlet {
//创建Service对象
private StudentService studentService = new StudentServiceImpl();
//删除学生信息
public void deleteStudent(HttpServletRequest request, HttpServletResponse response) throws Exception {
Integer id = Integer.valueOf(request.getParameter("id"));
studentService.deleteStudentById(id);
}
//跳转到学生信息修改页面
public void toStudentInfoPage(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取要修改的学生的id
Integer id = Integer.valueOf(request.getParameter("id"));
Long currentPage = Long.valueOf(request.getParameter("currentPage"));
Student student = new Student();
Long pageSize = new PageInfo().getPageSize();
//查询出当前学生的信息
try {
//查询指定学生信息
if(!id.equals(-1)){
student = studentService.findStudentById(id);
}
if(currentPage.equals(-1)){
currentPage = studentService.getPageInfo(1L, pageSize).getTotalPage();
}
//将student存储到请求域中
request.setAttribute("currentPage",currentPage);
request.setAttribute("student", student);
//跳转到修改页面
processTemplate("student-update", request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
//修改和增加学生信息
public void updateOrAddStudent(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取请求参数
Map<String, String[]> parameterMap = request.getParameterMap();
Long currentPage = Long.valueOf(parameterMap.get("currentPage")[0]);
Long pageSize = new PageInfo().getPageSize();
if(currentPage==-1){
currentPage = studentService.getPageInfo(1L, pageSize).getTotalPage()-1;
}
//将请求参数封装到Student对象
Student student = new Student();
try {
BeanUtils.populate(student, parameterMap);
System.out.println(student.toString());
//调用业务层的方法执行修改
if(student.getStudentId()!=null){
studentService.updateStudent(student);
}else{
studentService.insertStudent(student);
}
request.setAttribute("currentPage",currentPage);
processTemplate("list", request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
//登录:查找登录用户是否存在
public void login(HttpServletRequest request, HttpServletResponse response) throws Exception {
long count = -1;
try{
//获取要登录的用户的id
String id = String.valueOf(request.getParameter("userId"));
//获取要登录的用户的密码
String password = String.valueOf(request.getParameter("userPassword"));
//调用业务层的方法查询用户是否存在
count = studentService.findExistStudent(id, password);
//验证码
String code = (String) request.getSession().getAttribute("code");
// 获取页面提交的验证码
String inputCode = request.getParameter("code");
//登录成功要同时满足用户存在且验证码正确
boolean equals_code = code.toLowerCase().equals(inputCode.toLowerCase());
if (count > 0 && equals_code) {
//查询成功,解析Thymeleaf模板
processTemplate("list", request, response);
} else if(!equals_code){
request.setAttribute("errorMsg", "验证码错误");
processTemplate("error", request, response);
} else {
request.setAttribute("errorMsg", "用户不存在");
processTemplate("error", request, response);
}
} catch (Exception e) {
e.printStackTrace();
//跳转到统一的异常提示页面
request.setAttribute("errorMsg", e.getMessage());
processTemplate("error", request, response);
}
}
//获取分页学生列表数据
public void getStudentList(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String[]> parameterMap = request.getParameterMap();
//将请求参数封装到PageStudent对象
PageInfo pageStudent = new PageInfo();
//将请求域中的数据与每个pageStudent对应起来
BeanUtils.populate(pageStudent, parameterMap);
Long currentPage = pageStudent.getCurrentPage();
Long pageSize = pageStudent.getPageSize();
pageStudent = studentService.getPageInfo(currentPage, pageSize);
//调用业务层的方法查询某一页的学生信息,所以currentPage需要前端处理好访问哪一页后传递过来
List<Student> studentList = studentService.findStudentByPage(currentPage);
//将pageStudent存储到请求域中
request.setAttribute("pageInfo", pageStudent);
//将某一页的学生信息存储到request一个名为list的对象中
request.setAttribute("list", studentList);
//跳转到展示页面
processTemplate("student-table", request, response);
}
}
StudentService
根据面向接口编程的原则,所以先实现这个接口。
面向接口的好处:当具体的业务逻辑要进行变更时,不需要改变Servlet层的代码,只需要重新写一个实现类去实现该接口,当然原先的接口也可以保存下来,只需要在导入实现类包的时候选择最新的那个。
package com.lzk.service;
import com.lzk.bean.PageInfo;
import com.lzk.bean.Student;
import java.sql.SQLException;
import java.util.List;
public interface StudentService {
//查询某一页学生信息
List<Student> findStudentByPage(long pagenumber) throws Exception;
//删除学生信息
public void deleteStudentById(Integer id) throws Exception;
//添加学生信息
public void insertStudent(Student student) throws Exception;
//查询指定学生信息
public Student findStudentById(Integer id) throws Exception ;
//修改学生信息
public void updateStudent(Student student) throws Exception;
//查询学生是否存在
public long findExistStudent(String id,String name) throws Exception;
//查询学生总数
public long findAllStudentCount() throws Exception;
//返回学生的分页导航数据
PageInfo getPageInfo(Long currentPage, Long pageSize) throws Exception;
}
StudentServiceImpl
package com.lzk.service.impl;
import com.lzk.bean.PageInfo;
import com.lzk.bean.Student;
import com.lzk.dao.StudentDao;
import com.lzk.dao.impl.StudentDaoImpl;
import com.lzk.service.StudentService;
import java.sql.SQLException;
import java.util.List;
public class StudentServiceImpl implements StudentService {
//创建Dao对象
private StudentDao studentDao = new StudentDaoImpl();
//查询某一页学生信息
@Override
public List<Student> findStudentByPage(long currentpage) throws Exception {
return studentDao.findStudentByPage(currentpage);
}
//删除学生信息
@Override
public void deleteStudentById(Integer id) throws Exception{
studentDao.deleteStudentById(id);
}
//添加学生信息
@Override
public void insertStudent(Student student) throws Exception {
studentDao.insertStudent(student);
}
//查询指定学生信息
@Override
public Student findStudentById(Integer id) throws Exception {
return studentDao.findStudentById(id);
}
//修改学生信息
@Override
public void updateStudent(Student student) throws Exception {
studentDao.updateStudent(student);
}
//查询学生是否存在
public long findExistStudent(String id,String password) throws Exception{
return studentDao.findExistStudent(id, password);
}
//查询学生总数
public long findAllStudentCount() throws Exception{
return studentDao.findAllStudnetCount();
}
@Override
public PageInfo getPageInfo(Long currentPage,Long pageSize) throws SQLException {
long totalCount = studentDao.findAllStudnetCount();
long totalPage = totalCount % pageSize == 0 ? totalCount/pageSize : totalCount/pageSize + 1;
Long nextPage = null;
Long previousPage = null;
if(currentPage < totalPage-1){
nextPage = currentPage + 1;
}
if(currentPage != 0){
previousPage = currentPage - 1;
}
PageInfo pageInfo = new PageInfo(currentPage,pageSize,totalCount,totalPage,previousPage,nextPage);
return pageInfo;
}
}
StudentDao
Service需要调用Dao层
为什么要分为三层,而不让StudentService直接处理完呢?
其实每一层都有对应的功能,servlet负责请求的处理,service负责具体的业务逻辑处理,dao层负责对数据库数据的处理。分为三层可以解耦合,让结构更为清晰。
package com.lzk.dao;
import com.lzk.bean.PageInfo;
import com.lzk.bean.Student;
import java.sql.SQLException;
import java.util.List;
public interface StudentDao {
//查询所有学生信息
List<Student> findAll() throws SQLException;
//查询某一页学生信息
List<Student> findStudentByPage(long pagenumber) throws SQLException;
//删除学生信息
void deleteStudentById(Integer id) throws SQLException;
//添加学生信息
void insertStudent(Student student) throws SQLException;
//查询指定学生信息
Student findStudentById(Integer id) throws SQLException;
//修改学生信息
void updateStudent(Student student) throws SQLException;
//根据学号,姓名查询学生是否存在,k
long findExistStudent(String id,String password) throws SQLException;
//查询数据总数
long findAllStudnetCount() throws SQLException;
}
list页面
list页面只展示导航条部分数据,学生数据是通过页面加载时发起th:onload="|getStudentListByPage(${currentPage == null ? 0 : currentPage})|"
请求去获取的。这个方法在后面的student.js
里。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>展示学生列表信息</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.25/examples/dashboard/dashboard.css" rel="stylesheet">
<script src="js/jquery-1.8.3.js"></script>
<script src="js/student.js"></script>
</head>
<body th:onload="|getStudentListByPage(${currentPage == null ? 0 : currentPage})|">
<div>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">学生信息管理系统</a>
</div>
</div>
</nav>
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li><a th:onclick="getStudentListByPage(0)" style="cursor: pointer">分页学生信息</a></li>
<li><a th:onclick="toStudentInfoPage(-1,-1)" style="cursor: pointer">添加学生信息</a></li>
</ul>
</div>
<div id="content"></div>
</div>
</div>
</body>
</html>
student.js
第一次发起的请求为:student?method=getStudentList¤tPage=0
,这个请求还是通过web.xml中配置的StudentServlet处理,处理逻辑看上面的StudentServlet中的getStudentList
方法。其中的PageInfo包装了分页信息。
//异步加载学生信息列表
function getStudentListByPage(currentPage) {
$.ajax({
url: 'student?method=getStudentList¤tPage='+currentPage,
type: 'GET',
success: function(response) {
$("#content").empty();
$("#content").html(response)
}
});
}
//删除学生信息
function deleteStudentById(studentId,currentPage) {
$.ajax({
url: 'student?method=deleteStudent&id=' + studentId,
type: 'GET',
success: function () {
getStudentListByPage(currentPage);
}
});
}
function showAppointPage() {
var current = document.getElementById("appointPage").value;
getStudentListByPage(current-1);
}
function toStudentInfoPage(studentId,currentPage) {
$.ajax({
url: 'student',
type: 'GET',
data: { 'method': 'toStudentInfoPage', 'id': studentId, 'currentPage': currentPage},
success: function(response) {
$("#content").empty();
$("#content").html(response)
}
});
}
function updateOrAddStudent() {
// 获取表单数据
var formData = new FormData(document.getElementById('myForm'));
$.ajax({
url: 'student',
type: 'post',
data: formData,
contentType: false
});
}
function checkPage() {
var appointPage = parseInt($("#appointPage").val());
var totalPage = parseInt($("#totalPage").text());
if (appointPage < 0 ) {
$("#appointPage").val("1");
}else if(appointPage > totalPage){
$("#appointPage").val("1");
}
}
student-table.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div class="col-sm-8 col-sm-offset-3 col-md-10 col-md-offset-2 main" >
<h1 class="sub-header">List Student</h1>
<div class="table-responsive" >
<table class="table table-striped" th:unless="${#lists.isEmpty(list)}" border="1" cellspacing="0" width="800">
<thead>
<tr>
<th style="text-align:center">学生的编号</th>
<th style="text-align:center">学生的姓名</th>
<th style="text-align:center">学生的班级</th>
<th>删除信息</th>
<th>修改信息</th>
</tr>
</thead>
<!-- 如果没有学生信息,则显示没有学生数据,请添加学生 -->
<tbody th:if="${#lists.isEmpty(list)}">
<tr>
<td th:colspan="5">没有学生数据,请添加学生</td>
</tr>
</tbody>
<!-- 如果有学生信息,则遍历展示学生信息 -->
<tbody th:unless="${#lists.isEmpty(list)}">
<!-- 使用th:each遍历域对象里面的集合 -->
<tr th:each="student : ${list}">
<td th:text="${student.studentId}" align="center">学生的编号</td>
<td th:text="${student.studentName}" align="center">学生的姓名</td>
<td th:text="${student.studentClass}" align="center">学生的班级</td>
<td><a th:onclick="|deleteStudentById(${student.studentId},${pageInfo.currentPage})|" style="cursor: pointer">删除信息</a></td>
<td><a th:onclick="|toStudentInfoPage(${student.studentId},${pageInfo.currentPage})|" style="cursor: pointer">修改</a></td>
</tr>
</tbody>
<!-- 页面导航的超链接 -->
<tr th:unless="${#lists.isEmpty(list)}">
<td colspan="5" align="center">
共<span id="totalPage" th:text="${pageInfo.totalPage}">共多少页</span>页 第<span th:text="${pageInfo.currentPage}+1">第几页</span>页
</td>
</tr >
<tr th:unless="${#lists.isEmpty(list)}">
<td colspan="5" align="center" >
<input type="button" value="跳转" onclick="showAppointPage()" />
<input style="width:70px;" id="appointPage" th:value="${pageInfo.currentPage+1}" onkeyup="checkPage()" /> 页
<input type="button" value="首页" onclick="getStudentListByPage(0)" />
<input th:if="${pageInfo.previousPage != null}" type="button" value="上一页" th:onclick="|getStudentListByPage(${pageInfo.previousPage})|" />
<input th:if="${pageInfo.nextPage != null}" type="button" value="下一页" th:onclick="|getStudentListByPage(${pageInfo.nextPage})|" />
<input type="button" value="尾页" th:onclick="|getStudentListByPage(${pageInfo.totalPage-1})|" />
<br/>
</td>
</tr>
</table>
</div>
</div>
</body>
</html>
error.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>错误页面</title>
<script language="javascript" th:inline="javascript">
//返回登录页面
function backLogin() {
window.location.href = "portalServlet";
}
</script>
<style type="text/css">
.back-button{
width: 65px;
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
-ms-touch-action: manipulation;
border: 1px solid transparent;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
border-radius: 4px;
}
</style>
</head>
<body>
<div align="center">
<img src="image/pange.gif" >
<h1 th:text="${errorMsg}">显示一个动态的错误信息</h1>
<input class="back-button" type="submit" onclick="backLogin()" value="返回"><br/>
</div>
</body>
</html>
student-insertion.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>学生信息添加页面</title>
<base th:href="@{/}"/>
<script type="text/javascript" src="js/jquery-1.8.3.js"></script>
<link href="/bootstrap-3.4.1-dist/css/bootstrap.css" rel="stylesheet">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
function check() //onsubmit:return true:表单正常提交,return false表单终止
{
var sno=$("#stu_name").val();
var sname=$("#stu_class").val();
if(!(sno.length>1&&sno.length<10)){
alert("填写姓名信息有误!");
return false;
}
if(!(sname.length>2&&sname.length<10)){
alert("班级信息有误!")
return false;
}
return true;
}
</script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12 ">
<form th:action="@{/student(method='insertStudent')}" method="post" class="form-horizontal" onsubmit="return check()">
<!-- 表单区域-->
<fieldset>
<!--表单主题-->
<legend>填写新增学生信息</legend>
<!-- 每一个form-group都可以自定义布局-->
<tr ><a th:href="@{/student(method='LoginSuccess')}">返回</a></tr>
<div class="form-group">
<!-- label表示文字提示标签,可以通过表单的组建的id提示-->
<label class="col-md-2 control-label" for="stu_name">学生姓名</label>
<div class="col-md-4">
<input class="form-control" input type="text" id="stu_name" name="studentName"/>
</div>
</div>
<div class="form-group">
<!-- label表示文字提示标签,可以通过表单的组建的id提示-->
<label class="col-md-2 control-label" for="stu_class">学生班级</label>
<div class="col-md-4">
<input class="form-control" type="text" id="stu_class" name="studentClass"/>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-3">
<input class="btn btn-primary" type="submit" value="添加"/>
<input class="btn btn-warning" type="reset" value="重置"/>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</body>
</html>
student-update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div>
<div style="width: 75%;padding-left: 25%;padding-top: 2%">
<!-- 表单区域-->
<form id="myForm">
<fieldset>
<legend th:if="${student.studentId != null}">修改学生信息</legend>
<legend th:if="${student.studentId == null}">添加学生信息</legend>
<input type="hidden" name="method" value="updateOrAddStudent">
<input type="hidden" name="currentPage" th:value="${currentPage == null ? -1 : currentPage}" placeholder="Appoint Page">
<div class="form-group row" th:if="${student.studentId != null}" >
<label class="col-md-2 control-label" for="stu_id">学生学号</label>
<div class="col-md-4">
<input class="form-control" readonly="readonly" type="text" id="stu_id" th:value="${student.studentId}" name="studentId" />
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label" for="stu_name">学生姓名</label>
<div class="col-md-4">
<input th:if="${student.studentId != null}" class="form-control" type="text" id="stu_name" th:value="${student.studentName}" name="studentName"/>
<input th:if="${student.studentId == null}" class="form-control" type="text" id="stu_name" th:value="${student.studentName}" name="studentName"/>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label" for="stu_class">学生班级</label>
<div class="col-md-4">
<input th:if="${student.studentId != null}" class="form-control" type="text" id="stu_class" th:value="${student.studentClass}" name="studentClass"/>
<input th:if="${student.studentId == null}" class="form-control" type="text" id="stu_class" th:value="${student.studentClass}" name="studentClass"/>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-3">
<input th:if="${student.studentId != null}"class="btn btn-primary" type="submit" value="修改" onclick="updateOrAddStudent()"/>
<input th:if="${student.studentId == null}" class="btn btn-primary" type="submit" value="添加" onclick="updateOrAddStudent(-1)"/>
<input class="btn btn-warning" type="reset" value="重置"/>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</body>
</html>