6.1 什么是三层
在实际的开发中,为了更好的解耦合、使开发人员之间的分工明确、提高代码的可重用性等,通常会采用“三层架构”的模式来组织代码。
1) 三层定义:
所谓“三层”,是指表示层(User Show Layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data Access Layer)。
三层中使用的数据,是通过实体类(即封装数据的JavaBean)来传递的。实体类一般放在entity包下。
2) 各层的简介:
①数据访问层(Data Access Layer):
也称为持久层,位于三层中的最下层,用于对数据进行处理。该层中的方法一般都是“原子性”的,即每一个方法都不可再分。比如,可以在DAL层中实现数据的增删改查操作,而增、删、改、查四个操作是非常基本的功能,都是不能再拆分的。
在程序中,DAL一般写在dao包中,包里面的类名也是以”Dao”结尾,如StudentDao.java、DepartmentDao.java等;换句话说,在程序中,DAL是由dao包中的多个“类名Dao.java”组成。每一个“类名Dao.java”类,就包含着对该“类名”的所有对象的数据操作,如StudentDao.java中包含对Student对象的增、删、改、查等数据操作,DepartmentDao.java中包含对Department对象的增、删、改、查等数据操作。
②业务逻辑层(Business Login Layer):
位于三层中的中间层(表示层与数据访问层中间),起到了数据交换中承上启下的作用,用于对业务逻辑的封装。业务逻辑层的设计对于一个支持可扩展的架构尤为关键,因为它扮演了两个不同的角色。业务逻辑层沟通了表示层和数据访问层。
使用上,就是对数据访问层中的方法进行“组装”。比如,该层也可以实现对Student的增删改查,但与数据访问层不同的是,业务逻辑层中的增、删、改、查不再是 “原子性”的功能,而是包含了一定的业务逻辑。比如该层中的“删”不再像数据访问层中那样仅仅实现“删”,而是在“删”之前要进行业务逻辑的判断:先查找该学生是否存在(即先执行业务逻辑层的“查”),如果存在才会真正的“删”(再执行数据访问层的“删”),如果不存在则应该提示错误信息。即业务逻辑层中的“删”,应该是“带逻辑的删”(即先“查”后“删”),也就是对数据访问层中的“查”和“删”两个方法进行了“组装”。
在程序中,业务逻辑层一般写在service包中,包里面的类名也是以”Service”结尾,如StudentService.java、DepartmentService.java等。换句话说,在程序中,业务逻辑层是由service包中的多个“类名Service.java”组成。每一个“类名Service.java”类,就包含着对该“类名”的对象的业务操作,如StudenService.java中包含对Student对象的“带逻辑的删”、“带逻辑的增”等业务逻辑操作,DepartmentService.java中包含对所有Department对象的“带逻辑的删”、“带逻辑的增”等业务逻辑操作。
③表示层(User Show Layer):
位于三层中的最上层,用于显示数据和接收用户输入的数据,为用户提供一种交互式操作的界面。表示层又分为“表示层前台代码”和“表示层后台代码”,其中“表示层前台代码”是指用户能直接访问到的界面,一般是程序的外观(如html文件、JSP文件等),类似于MVC模式中的“视图”;“表示层后台代码”是指用来调用业务逻辑层的JAVA代码(如Servlet),类似于MVC模式中的“控制器”。表示层前台代码一般放在WebContent目录下,而表示层后台代码目前放在servlet包下。
MVC和三层架构,是分别从两个不同的角度去设计的,但目的都是为了“解耦,分层,代码复用等”。
6.2 三层结构案例
项目的目录结构如下:
第一步:写操作数据库的工具类
①工具类建在com.lee.util下:
DBUtil.java
package com.lee.util;
public class DBUtil {
private static String DRIVER="";
private static String URL="";
private static String USER="";
private static String PASSWORD="";
private static Connection connection = null;
private static PreparedStatement pstmt=null;
private static ResultSet resultSet=null;
/**
* 获取连接
* @return
*/
public static Connection getConnection() {
//加载数据库配置文件
try {
String path = DBUtil.class.getClassLoader().getResource("db.properties").toString().substring(5);
Properties properties = new Properties();
FileInputStream inputStream = new FileInputStream(path);
properties.load(inputStream);
DRIVER=properties.getProperty("driver");
URL=properties.getProperty("url");
USER=properties.getProperty("user");
PASSWORD=properties.getProperty("password");
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//记载驱动类
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取连接
try {
connection = DriverManager.getConnection(URL,USER,PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
/**
* 执行insert语句
* @param sql:增加的SQL语句
* @param os:传递实体中的属性
* @return
*/
public static boolean executeInsert(String sql,Object[] os) {
boolean flag = false;
try {
pstmt = getConnection().prepareStatement(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (os!=null) {
for(int i=0;i<os.length;i++) {
try {
pstmt.setObject(i+1,os[i]);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag=false;
}
}
try {
pstmt.executeUpdate();
flag=true;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag=false;
}finally {
releaseResource();
}
}
return flag;
}
/**
* 执行delete语句
* @param sql
* @param os
* @return
*/
public static boolean executeDelete(String sql ,Object[] os) {
boolean flag = false;
//获取PreparedStatement对象
try {
pstmt = getConnection().prepareStatement(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (os!=null) {
//赋值
for (int i = 0; i < os.length; i++) {
try {
pstmt.setObject(i + 1, os[i]);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//执行
try {
pstmt.executeUpdate();
flag = true;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag=false;
}finally {
releaseResource();
}
return flag;
}
/**
* 执行update语句
* @param sql
* @param os
* @return
*/
public static boolean executeUpdate(String sql ,Object[] os) {
boolean flag = false;
try {
pstmt = getConnection().prepareStatement(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<os.length;i++) {
try {
pstmt.setObject(i+1,os[i]);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
pstmt.executeUpdate();
flag=true;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag=false;
}finally {
releaseResource();
}
return flag;
}
/**
* 执行select语句
* @param sql
* @param os
* @return
*/
public static ResultSet executeSelect(String sql ,Object[] os) {
try {
pstmt = getConnection().prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}
if (os!=null) {
for (int i = 0; i < os.length; i++) {
try {
pstmt.setObject(i + 1, os[i]);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
try {
resultSet = pstmt.executeQuery();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//releaseResource();注意:这里不要资源释放,否则会提前关闭resultset,导致结果集不能为空
}
return resultSet;
}
public static void releaseResource() {
if(resultSet !=null) {
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(pstmt !=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
②数据库配置信息:
db.properties
#mysql配置文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/studentcrud
user=root
password=root
第二步:建表
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`no` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of `student`
-- ----------------------------
BEGIN;
INSERT INTO `student` VALUES ('1', 'a', '10', '男'), ('2', 'b', '20', '女'), ('3', 'cc', '33', '女'), ('4', 'dd', '44', '男');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
第三步:写数据访问层(dao)
①dao层接口,写在com.lee.dao下
IStudentDao.java
package com.lee.dao;
public interface IStudentDao {
public boolean addStudent(Student sutdent);//增加学生
public boolean deleteStudentByNo(int studentNo);//通过学号删除学生
public boolean updateStudent(Student student);//更新学生信息
public Student queryStudentByNo(int studentNo);//通过学号查询学生信息
public List<Student> queryAllStudent();//查询所有学生
public boolean isExitByNo(int studentNo);//通过学号判断学生是否存在
}
②dao层接口实现类,写在com.lee.dao.impl下
StudentDaoImpl.java
package com.lee.dao.impl;
public class StudentDaoImpl implements IStudentDao{
@Override
public boolean addStudent(Student student) {
String sql = "insert into student(no,name,age,sex) values(?,?,?,?) ";
Object[] os = {student.getStuNo(),student.getStuName(),student.getStuAge(),student.getStuSex()};
return DBUtil.executeInsert(sql, os);
}
@Override
public boolean deleteStudentByNo(int studentNo) {
String sql ="delete from student where no=?";
Object[] os = {studentNo};
return DBUtil.executeDelete(sql, os);
}
@Override
public boolean updateStudent(Student student) {
String sql ="update student set no=?,name=?,age=?,sex=? where no=? ";
Object[] os = {student.getStuNo(),student.getStuName(),student.getStuAge(),student.getStuSex(),student.getStuNo()};
return DBUtil.executeUpdate(sql, os);
}
@Override
public Student queryStudentByNo(int studentNo) {
String sql ="select no,age,name,sex from student where no=?";
Object[] os = {studentNo};
Student student = null;
ResultSet rs = DBUtil.executeSelect(sql, os);
try {
if(rs.next()) {
int stuNo =rs.getInt("no");
String stuName = rs.getString("name");
int stuAge = rs.getInt("age");
String stuSex = rs.getString("sex");
student = new Student(stuNo, stuName, stuAge, stuSex);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return student;
}
@Override
public List<Student> queryAllStudent() {
String sql ="select no,name,age,sex from student";
Object[] os= {};
Student student = null;
ArrayList<Student> students = new ArrayList<>();
ResultSet rs = DBUtil.executeSelect(sql, os);
try {
while(rs.next()) {
int stuNo =rs.getInt("no");
String stuName = rs.getString("name");
int stuAge = rs.getInt("age");
String stuSex = rs.getString("sex");
student = new Student(stuNo, stuName, stuAge, stuSex);
students.add(student);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return students;
}
@Override
public boolean isExitByNo(int studentNo) {
Student studentByNo = this.queryStudentByNo(studentNo);
if(studentByNo!=null) {
return true;
}
return false;
}
}
第四步:写业务逻辑层(service)
①service层接口,写在com.lee.service下
IStudentService.java
package com.lee.service;
public interface IStudentService {
public boolean addStudent(Student student);//增加学生
public boolean deleteStudentByNo(int studentNo);//根据学号删除学生
public boolean updateStudent(Student student);//更新学生信息
public Student queryStudentByNo(int studentNo);//根据学号查询学生
public List<Student> queryAllStudent();//查询所有学生
public boolean isExitStudent(int studentNo);//根据学号查询学生是否存在
}
②service层接口实现类,写在com.lee.service.impl下
StudentServiceImpl.java
package com.lee.service.impl;
public class StudentServiceImpl implements IStudentService {
public IStudentDao studentDao =new StudentDaoImpl();
@Override
public boolean addStudent(Student student) {
boolean isExit = studentDao.isExitByNo(student.getStuNo());
if(isExit) {//如果学生存在
return false;
}
return studentDao.addStudent(student);
}
@Override
public boolean deleteStudentByNo(int studentNo) {
boolean isExit = studentDao.isExitByNo(studentNo);
if(isExit) {//如果学生存在
return studentDao.deleteStudentByNo(studentNo);
}
return false;
}
@Override
public boolean updateStudent(Student student) {
boolean isExit = studentDao.isExitByNo(student.getStuNo());
if(isExit) {//如果学生存在
return studentDao.updateStudent(student);
}
return false;
}
@Override
public Student queryStudentByNo(int studentNo) {
return studentDao.queryStudentByNo(studentNo);
}
@Override
public List<Student> queryAllStudent() {
return studentDao.queryAllStudent();
}
@Override
public boolean isExitStudent(int studentNo) {
return studentDao.isExitByNo(studentNo);
}
}
第五步:写视图层的后端部分(controller)
①添加学生的servlet
AddStudentServlet.java
package com.lee.servlet;
@WebServlet("/AddStudentServlet")
public class AddStudentServlet extends HttpServlet {
private static final long serialVersionUID = -9067988240490709448L;
private IStudentService studentService = new StudentServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
req.setCharacterEncoding("UTF-8");
Integer sno = Integer.parseInt(req.getParameter("sno"));
String sname = req.getParameter("sname");
Integer sage = Integer.parseInt(req.getParameter("sage"));
String ssex = req.getParameter("ssex");
Student student = new Student(sno, sname, sage, ssex);
boolean result = studentService.addStudent(student);
if(!result) {//如果添加失败
req.setAttribute("error", "添加失败!");
req.getRequestDispatcher("addStudent.jsp").forward(req, resp);
}else {
req.setAttribute("success", "添加成功!");
resp.sendRedirect("QueryAllStudentServlet");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
this.doGet(req, resp);
}
}
②删除学生的servlet
DeleteStudentServlet.java
package com.lee.servlet;
@WebServlet("/DeleteStudentServlet")
public class DeleteStudentServlet extends HttpServlet {
private static final long serialVersionUID = -5246898354271104648L;
private IStudentService studentService = new StudentServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
Integer sno = Integer.parseInt(req.getParameter("sno"));
boolean result = studentService.deleteStudentByNo(sno);
if(!result) {//如果删除失败
req.setAttribute("error", "删除失败!");
req.getRequestDispatcher("index.jsp").forward(req, resp);;
}else {
req.setAttribute("success", "删除成功!");
resp.sendRedirect("QueryAllStudentServlet");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
super.doGet(req, resp);
}
}
③查询所有学生的servlet
QueryAllStudentServlet.java
package com.lee.servlet;
@WebServlet("/QueryAllStudentServlet")
public class QueryAllStudentServlet extends HttpServlet {
private static final long serialVersionUID = -5246898354271104648L;
private IStudentService studentService = new StudentServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
List<Student> students = studentService.queryAllStudent();
if(students!=null) {//如果查询失败
req.setAttribute("students", students);
req.getRequestDispatcher("index.jsp").forward(req, resp);;
}else {
req.setAttribute("error", "查询失败!");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
super.doGet(req, resp);
}
}
④通过学号查询学生
QueryStudentByNOServlet.java
package com.lee.servlet;
@WebServlet("/QueryStudentByNOServlet")
public class QueryStudentByNOServlet extends HttpServlet {
private static final long serialVersionUID = -5246898354271104648L;
private IStudentService studentService = new StudentServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
Integer sno = Integer.parseInt(req.getParameter("sno"));
Student result = studentService.queryStudentByNo(sno);
if(result!=null) {//如果查询失败
req.setAttribute("student", result);
}else {
req.setAttribute("error", "查询失败!");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
super.doGet(req, resp);
}
}
⑤通过学号更新学生信息
UpdateStudentServlet.java
package com.lee.servlet;
@WebServlet("/UpdateStudentServlet")
public class UpdateStudentServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private IStudentService studentService = new StudentServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
Integer sno = Integer.parseInt(req.getParameter("sno"));
String sname = req.getParameter("sname");
Integer sage = Integer.parseInt(req.getParameter("sage"));
String ssex = req.getParameter("ssex");
Student student = new Student(sno, sname, sage, ssex);
boolean result = studentService.updateStudent(student);
if(!result) {//如果更新失败
req.setAttribute("error", "更新失败!");
req.getRequestDispatcher("error.jsp").forward(req, resp);
}else {
req.setAttribute("success", "更新成功!");
resp.sendRedirect("QueryAllStudentServlet");
}
}
}
第六步:写视图层的前端部分(jsp、html等)
①显示所有学生信息
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List" %>
<%@ page import="com.lee.entity.Student" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="./js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="./js/bootstrap.min.js"></script>
<link href="./css/bootstrap.min.css" rel="stylesheet">
<title>首页</title>
</head>
<body>
<table class="table table-striped table-hover">
<tr>
<td class="success">学号</td>
<td class="warning">姓名</td>
<td class="danger">年龄</td>
<td class="info">性别</td>
<td class="active">操作</td>
</tr>
<%
List<Student> students =(List<Student>) request.getAttribute("students") ;
for(Student stu: students){
%>
<tr>
<td><%=stu.getStuNo() %></td>
<td><%=stu.getStuName() %></td>
<td><%=stu.getStuAge() %></td>
<td><%=stu.getStuSex() %></td>
<td>
<a class="btn btn-primary" href="update.jsp?sno=<%=stu.getStuNo() %>&sname=<%=stu.getStuName() %>&sage=<%=stu.getStuAge()%>&ssex=<%=stu.getStuSex()%>">修改</a>
<a class="btn btn-danger" href="DeleteStudentServlet?sno=<%=stu.getStuNo()%>">删除</a>
</td>
</tr>
<%
}
%>
</table>
<a href="add.jsp">添加</a>
</body>
</html>
注意:引用bootstrap的时候,要先引入jQuery。
②添加学生信息
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加学生</title>
</head>
<body>
<form action="AddStudentServlet" method="post">
学号:<input type="text" name="sno"></br>
姓名:<input type="text" name="sname"></br>
年龄:<input type="text" name="sage"></br>
性别:<input type="text" name="ssex"></br>
<input type="submit" value="添加">
</form>
</body>
</html>
③修改学生的信息
update.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>修改学生信息</title>
</head>
<body>
<form action="UpdateStudentServlet" method="post">
学号:<input type="text" name="sno" value=<%=request.getParameter("sno") %>></br>
姓名:<input type="text" name="sname" value=<%=request.getParameter("sname") %>></br>
年龄:<input type="text" name="sage" value=<%=request.getParameter("sage") %>></br>
性别:<input type="text" name="ssex" value=<%=request.getParameter("ssex") %>></br>
<input type="submit" value="修改">
</form>
</body>
</html>
④错误页面
error.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>错误页面</title>
</head>
<body>
<%
out.print(request.getAttribute("error"));
%>
</body>
</html>