案例简介
本人在上篇博客中对三层架构做了一个理论层面的介绍,同时又将其与MVC设计模式做了一个对比,但毕竟只停靠在理论层面是不行的,没有实践是无法彻底理解三层对现代互联网开发中的优势。本篇博客通过将数据库中的数据显示在网页上的一个小项目对三层做一个实践。
项目主要效果如下:
- 通过新增按钮向学生信息系统新增一个学生
- 通过删除按钮使学生信息系统减少一个学生
- 通过修改按钮将学生信息系统对应学号学生的信息进行修改
项目的执行图如下:
上图中红色的线表示上层调用下层,蓝色的线表示下层将处理结果一层层的返回给上层,最终通过表示层前台经过路由渲染打回到用户的浏览器上显示。特别的是,当然这也是我认为在三层中最重要的一点,那就是三层之间都是通过实体类进行数据的传输与方法的调用的,本案例的实体类就是学生对象。
下面附上本人项目结构(IDEA)图
三层分工
表示层
1.表示层前台
- 向表示层后台传输数据,例如要修改的学生学号等信息
- 接收表示层后台传输过来的处理结果,并将结果展示给用户
- 本案例的前台主要包括add.jsp、show.jsp、studentIfo.jsp。add.jsp页面向用户提供一个表单用于填写信息并将该条信息录入学生信息系统;show.jsp页面用于展示当前学生信息系统中的全部学生信息;studentIfo.jsp页面用于展示某一个具体学生的详细信息
2.表示层后台
- 转发前台的请求并为其挑选特定的的业务逻辑层功能。例如要前台用户想要删除学号为1的学生,那么表示层后台将调用业务逻辑层的删除学生功能并将学号1传给业务逻辑层
- 将处理结果返回给表示层前台
3.表示层前台代码
新增学生add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>新增页</title>
</head>
<body>
<form action="AddStudentServlet">
学号:<input type="text" name="sno"/><br/>
姓名:<input type="text" name="sname"/><br/>
年龄:<input type="text" name="sage"/><br/>
<input type="submit" value="新增"/>
</form>
</body>
</html>
显示当前系统中的所有学生
<%@ page import="java.util.List" %>
<%@ page import="com.mamusha.entity.Student" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>学生信息列表</title>
</head>
<body>
<!--增加成功与否提示-->
<%
// 接收AddStudentServlet传来的提示标记
String error = (String) request.getAttribute("error");
// 进行判断
if (error!=null){
if (error.equals("noError")){
// 增加成功
out.print("增加成功!");
}else if (error.equals("error")){
// 增加失败
out.print("增加失败!");
}
}
%>
<!--用一个表格显示学生信息-->
<table border="1px">
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
<!--学生信息应该是动态的,不应该写死,学生信息在QueryAllStudentServlet后台里,必须调用该类拿到学生信息-->
<%
// 接收后台传过来的学生信息
List<Student> students = (List<Student>) request.getAttribute("students");
// 循环(使表格动态变化)
for (Student student : students){
%>
<tr>
<td><a href="QueryStudentBySnoServlet?sno=<%=student.getSno()%>"><%=student.getSno()%></a></td>
<td><%=student.getSname()%></td>
<td><%=student.getSage()%></td>
<td><a href="DeleteStudentBySnoServlet?sno=<%=student.getSno()%>">删除</a></td>
</tr>
<%
}
%>
</table>
<a href="add.jsp">新增</a>
</body>
</html>
显示某个具体学生信息
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.mamusha.entity.Student" %>
<html>
<head>
<title>学生详情页</title>
</head>
<body>
<!-- 获取后台传过来的学生信息 -->
<%
Student student = (Student) request.getAttribute("student");
%>
<form action="UpdateStudentBySnoServlet">
学号:<input type="text" name="sno" value="<%=student.getSno()%>" readonly="readonly"/><br/>
姓名:<input type="text" name="sname" value="<%=student.getSname()%>"/><br/>
年龄:<input type="text" name="sage" value="<%=student.getSage()%>"/><br/>
<input type="submit" value="修改"/>
<a href="QueryAllStudentsServlet">返回</a>
</form>
</body>
</html>
4.表示层后台代码
// 表示层后台:增加学生
@WebServlet("/AddStudentServlet")
public class AddStudentServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取前台用户信息
request.setCharacterEncoding("utf-8");
int no = Integer.parseInt(request.getParameter("sno"));
String name = request.getParameter("sname");
int age = Integer.parseInt(request.getParameter("sage"));
// 将用户信息封装带实体类中
Student student = new Student(no, name, age);
// 创建业务逻辑层对象
StudentService service = new StudentService();
boolean result = service.addStudent(student);
// 设置响应编码
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("utf-8");
// 获取响应对象out,用于输出提示信息
PrintWriter out = response.getWriter();
// 判断增加是否成功
if (result){
// 增加成功
// out.println("增加成功!");
// 通过request域设置一个标记用于提示用户增加是否成功
request.setAttribute("error","noError");
}else {
// out.println("增加失败!");
request.setAttribute("error","error");
}
// 新增是否成功都应该跳回展示页
request.getRequestDispatcher("QueryAllStudentsServlet").forward(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
// 表示层后台:根据学号删除学生
@WebServlet("/DeleteStudentBySnoServlet")
public class DeleteStudentBySnoServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接收前台传过来的学号
request.setCharacterEncoding("utf-8");
int no = Integer.parseInt(request.getParameter("sno"));
// 调用业务逻辑层进行删除操作
StudentService service = new StudentService();
boolean result = service.deleteBySno(no);
// 设置响应编码
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("utf-8");
// 获取响应对象out,用于输出提示信息
PrintWriter out = response.getWriter();
// 判断删除是否成功
if (result){
// out.println("删除成功!");
}else {
// out.println("删除失败!");
}
// 删除成功后应该跳回到QueryAllStudentsServlet刷新一下信息列表
// 无论删除成功与否都应该跳回到展示页
response.sendRedirect("QueryAllStudentsServlet");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
// 表示层后台:按学号修改学生
@WebServlet("/UpdateStudentBySnoServlet")
public class UpdateStudentBySnoServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接受前台传递来的数据
request.setCharacterEncoding("utf-8");
int no = Integer.parseInt(request.getParameter("sno"));
String name = request.getParameter("sname");
int age = Integer.parseInt(request.getParameter("sage"));
// 将要修改的信息封装到实体类中
Student student = new Student(name, age);
StudentService service = new StudentService();
boolean result = service.updateStudentBySno(no, student);
// 设置响应编码
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("utf-8");
// 获取响应对象out,用于输出提示信息
PrintWriter out = response.getWriter();
// 判断删除是否成功
if (result){
// out.println("修改成功!");
response.sendRedirect("QueryAllStudentsServlet");
}else {
out.println("修改失败!");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
// 表示层后台:按学号查询学生
@WebServlet("/QueryStudentBySnoServlet")
public class QueryStudentBySnoServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接受前台传过来的学号
request.setCharacterEncoding("utf-8");
int no = Integer.parseInt(request.getParameter("sno"));
StudentService service = new StudentService();
Student student = service.queryStudentBySno(no);
// 简单测试
// System.out.println(student);
// 将查询到的学生对象放到requset域中带给前台
request.setAttribute("student",student);
// 页面跳转至前台
request.getRequestDispatcher("studentInfo.jsp").forward(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
// 表示层后台:查询所有学生
@WebServlet("/QueryAllStudentsServlet")
public class QueryAllStudentsServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 查询所有学生不需要前台传递数据过来,直接查询即可
request.setCharacterEncoding("utf-8");
StudentService service = new StudentService();
List<Student> students = service.queryAllStudents();
// 简单测试
// System.out.println(students);页面会瞬间跳转到show.jsp,故该行用户看不见
// 将学生信息放到request域中发送给前台
request.setAttribute("students",students);
// 将后台信息带给前台,因为带有数据所以使用转发
request.getRequestDispatcher("show.jsp").forward(request,response); // 此时学生信息就在前台了,前台只需要接收即可
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
业务逻辑层
业务逻辑层功能:
- 调用数据访问层对数据库进行增删改查操作,例如要删除一个学生,业务逻辑起先调用数据访问层去数据库查询是否存在该学生,若学生存在再删除
- 将数据访问层的处理结果返回给表示层后台
业务逻辑层源码:
// 业务逻辑层,用于组装数据访问层
public class StudentService {
// 创建数据访问层对象
StudentDao studentDao = new StudentDao();
// 增加学生功能
public boolean addStudent(Student student){
// 判断学生是否已经存在
if (!studentDao.isExist(student.getSno())){
// 学生不存在,可以增加
boolean result = studentDao.addStudent(student);
return result; // 增加成功
}else {
return false; // 增加失败
}
}
// 根据学号删除学生功能
public boolean deleteBySno(int sno){
// 先判断该学生是否存在
if (studentDao.isExist(sno)){
// 该学生存在,可以删除
boolean result = studentDao.deleteStudentBySno(sno);
return result;
}else {
return false;
}
}
// 根据学号修改学生信息功能
public boolean updateStudentBySno(int sno,Student student){
// 先判断该学生是否存在
if (studentDao.isExist(sno)){
// 该学生存在,可以修改
boolean result = studentDao.updateStudentBySno(sno, student);
return result;
}else {
return false;
}
}
// 根据学号查询学生功能
public Student queryStudentBySno(int sno){
// 查询操作不需要判断是否存在,直接查询即可
Student student = studentDao.queryBySno(sno);
return student;
}
// 查询全部学生功能
public List<Student> queryAllStudents(){
List<Student> students = studentDao.queryAllStudent();
return students;
}
}
数据访问层
数据访问层直接对数据库进行增删改查,并且将执行结果返回给业务逻辑层。
源码:
// 数据访问层,进行底层数据库的增删改查
public class StudentDao {
// 定义数据库连接常量
private final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
private final String USERNAME = "root";
private final String PASSWORD = "19960825";
// 判断学生是否存在
public boolean isExist(int sno){
return queryBySno(sno)==null?false:true;
}
// 增加学生
public boolean addStudent(Student student){
Connection connection = null;
PreparedStatement pstmt = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取数据库操作对象
String sql = "INSERT INTO student (sno,sname,sage) VALUES (?,?,?)";
pstmt = connection.prepareStatement(sql);
// 为占位符赋值
pstmt.setInt(1,student.getSno());
pstmt.setString(2,student.getSname());
pstmt.setInt(3,student.getSage());
// 执行SQL
int count = pstmt.executeUpdate();
if (count>0){
// 增加成功
return true;
}
return false;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
} catch (SQLException e) {
e.printStackTrace();
return false;
}finally {
// 关闭资源
try {
if (pstmt != null) pstmt.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 根据学号删除学生
public boolean deleteStudentBySno(int sno){
Connection connection = null;
PreparedStatement pstmt = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取数据库操作对象
String sql = "DELETE FROM student WHERE sno=?";
pstmt = connection.prepareStatement(sql);
// 为占位符赋值
pstmt.setInt(1,sno);
// 执行SQL
int count = pstmt.executeUpdate();
if (count>0){
// 删除成功
return true;
}
return false;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
} catch (SQLException e) {
e.printStackTrace();
return false;
}finally {
// 关闭资源
try {
if (pstmt != null) pstmt.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 根据学号修改学生信息:将指定学号的学生信息改为给定的值
public boolean updateStudentBySno(int sno,Student student){
Connection connection = null;
PreparedStatement pstmt = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取数据库操作对象
String sql = "UPDATE student SET sname=?,sage=? WHERE sno=?";
pstmt = connection.prepareStatement(sql);
// 为占位符赋值
pstmt.setString(1,student.getSname());
pstmt.setInt(2,student.getSage());
pstmt.setInt(3,sno);
// 执行SQL
int count = pstmt.executeUpdate();
if (count>0){
// 修改成功
return true;
}
return false;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
} catch (SQLException e) {
e.printStackTrace();
return false;
}finally {
// 关闭资源
try {
if (pstmt != null) pstmt.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 根据学号查询学生
public Student queryBySno(int sno){
// 该方法的返回值定义为一个学生
Student student = null;
// jdbc代码
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取数据库操作对象
String sql = "SELECT * FROM student WHERE sno=?";
pstmt = connection.prepareStatement(sql);
// 为占位符赋值
pstmt.setInt(1,sno);
// 执行SQL
rs = pstmt.executeQuery();
if (rs.next()){
// 查询成功,取出学生信息
int no = rs.getInt("sno");
String name = rs.getString("sname");
int age = rs.getInt("sage");
// 将学生信息封装到实体类中
student = new Student(no, name, age);
}
return student;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
} catch (SQLException e) {
e.printStackTrace();
return null;
}finally {
// 关闭资源
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 查询所有学生
public List<Student> queryAllStudent(){
List<Student> students = new ArrayList<>();
Student student = null;
// jdbc代码
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取数据库操作对象
String sql = "SELECT * FROM student";
pstmt = connection.prepareStatement(sql);
// 执行SQL
rs = pstmt.executeQuery();
while (rs.next()){ // 此处易出错,不是if循环而是while循环
// 查询成功,取出学生信息
int no = rs.getInt("sno");
String name = rs.getString("sname");
int age = rs.getInt("sage");
// 将学生信息封装到实体类中
student = new Student(no, name, age);
students.add(student);
}
return students;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
} catch (SQLException e) {
e.printStackTrace();
return null;
}finally {
// 关闭资源
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
不难看出,数据访问层的代码存在大量的冗余,再后期我们可以进行优化。
下面给出实体类源码:
// 实体类
public class Student {
private int sno;
private String sname;
private int sage;
public Student() {
}
public Student(String sname, int sage) {
this.sname = sname;
this.sage = sage;
}
public Student(int sno, String sname, int sage) {
this.sno = sno;
this.sname = sname;
this.sage = sage;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getSage() {
return sage;
}
public void setSage(int sage) {
this.sage = sage;
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", sname='" + sname + '\'' +
", sage=" + sage +
'}';
}
}
以上就是本人对这个项目的各个实施,应该附上项目运行图的,但是奈何目前还没找到如何上传视频,还请大家见谅,有兴趣的小伙伴可以将本文源码考到自己的电脑上跑一下这个项目。对了,还有重要的一点,就是配置文件的首选项要做一下更改,不然会报空指针异常,下面是我的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">
<welcome-file-list>
<!--将QueryAllStudentServlet放在第一位,否则show.jsp页在接收后台数据时会报空指针异常,很简单,因为此时QueryAllStudentServlet还没被加载-->
<welcome-file>QueryAllStudentsServlet</welcome-file>
<welcome-file>show.jsp</welcome-file>
</welcome-file-list>
</web-app>``