javaee三层架构案例--简单学生管理系统

背景

学了jdbc、jsp等需要串起来,不然会忘记

项目环境

win10
jdk11
mysql8.0.13

jar包

c3p0-0.9.5.2
commons-dbutils-1.7
jstl
mchange-commons-java-0.2.11
mysql-connector-java-8.0.14
standard

项目地址

还不会用github,所以只能这样咯

链接:https://pan.baidu.com/s/1JwSag2RIEBVhGZVAETNqlQ
提取码:o0x3
复制这段内容后打开百度网盘手机App,操作更方便哦

准备数据库

/*创建一个存放学生信息的表格*/

/*创建数据库stus*/
CREATE DATABASE stus; 
/*使用stus*/
USE stus;
/*创建学生表stu*/
CREATE TABLE stu(
  sid INT PRIMARY KEY AUTO_INCREMENT,
  sname VARCHAR(20),
  gender VARCHAR(5),
  phone VARCHAR(20),
  birthday DATE,
  hobby VARCHAR(50),
  info VARCHAR(200)
);

做一个主页

通过IDEA在web目录下创建一个index.jsp作为主页
页面先只有一个超链接叫做 显示所有学生列表
还没写链接到哪个Servlet,用 # 先代替下,等创建好了再写回来

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>首页</title>
</head>
<body>
<h3><a href="/studentList">显示所有学生列表</a></h3>
</body>
</html>

创建一个Servlet

index.jsp标记1出用的Servlet,创一个Servlet的包,创一个StudentListServlet类,用doGet方法(转发需要用doGet方法,且用doPost没有这个需求)

Servlet接受用户点击,去通知service实现业务逻辑

//这个是用反射,写在类上一行,也可以在web.xml中配置Servlet
@WebServlet(name = "StudentListServlet",urlPatterns = {"/StudentListServlet"})

//写在doGet方法然后在doPost方法中互调....
//因为之后转发只能用doGet方法,有点麻烦

//面向接口编程
//StudentService:接口 StudentServiceImpl:接口实现类
StudentService service = new StudentServiceImpl();
//调用实现类的findAll方法,把结果放在一个list表中,泛型为Student对象
List<Student> list = service.findAll();

创建Student类

上文中缺少Student类,这是一个JavaBean,封装用。

!!!JavaBean一定要有一个空参!!!

!!!JavaBean是用空参来反射得到实例的!!!

创建domian包,里面创建Student类,包含和数据库名字、类型对应的成员变量

//数据类型为Date的导util包,sql包中的Date也是继承该util包中的  
import java.util.Date;
	private int sid;
    private String sname;
    private String gender;
    private String phone;
    private Date birthday;
    private String hobby;
    private String info;
//生成getXxx和setXxx方法
//生成toString方法

创建一个Service接口和Service的实现类

上面没有Service接口,创建一个service包,下面创建StudentService接口

这里是为了实现学生业务逻辑的处理规范

目前只有一个查找所有学生信息的业务

public interface StudentService {
	//这里的throws SQLException是在最后面dao层发现需要抛,一步一步返回来的,当然IDEA中一键生成
    List<Student> findAll() throws SQLException;
}

创建接口的实现类,在service包下创建一个impl包,在impl包内创建StudentServiceImpl实现类

实现学生业务,findAll方法是去数据库中查询,因此要调用查询数据库的方法

public class StudentServiceImpl implements StudentService {
    @Override
    public List<Student> findAll() throws SQLException {
        //StudentDao:接口 StudentDaoImpl:实现类
        StudentDao dao = new StudentDaoImpl();
        return dao.findAll();
    }
}

创建dao层中接口和实现类

创建一个dao包,创建StudentDao接口,在dao包中创建一个impl包,里面创建一个StudentDaoimpl实现类

public interface StudentDao {
    List<Student> findAll() throws SQLException;
}

StudentDaoImpl实现findAll方法,通过C3P0,自己的工具类JDBCUtil调用

通过数据库代码查询,结果返回到BeanListHandler<>(Student.class)中

public class StudentDaoImpl implements StudentDao {
    @Override
    public List<Student> findAll() throws SQLException {
        QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
        String sql = "SELECT * FROM stu";
        return runner.query(sql, new BeanListHandler<>(Student.class));
    }
}

把查询出的结果发到list.jsp中

在StudentListServlet类的doGet方法中,要把结果存到request域中

//名字就叫list,值也是list
request.setAttribute("list",list);

再把结果转发到list.jsp中,不需要改变页面地址

request.getRequestDispatcher("list.jsp").forward(request, response);

目前先做红框内的东西,分析一下,就是2行8列,一行是标题,一行是内容(靠循环出来的结果)

要用el表达式,导包jstl.jar和standard.jar
导jstl标签库

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--2行8列展示结果--%>
<table border="1px" width="600px">
    <tr>
        <td>编号</td>
        <td>姓名</td>
        <td>性别</td>
        <td>电话</td>
        <td>生日</td>
        <td>爱好</td>
        <td>简介</td>
        <td>操作</td>
    </tr>
    <c:forEach items="${list }" var="stu">
    <tr>
        <td>${stu.sid }</td>
        <td>${stu.sname }</td>
        <td>${stu.gender }</td>
        <td>${stu.phone }</td>
        <td>${stu.birthday }</td>
        <td>${stu.hobby }</td>
        <td>${stu.info }</td>
        <td>!~~~超链接还没写,等下补完<a href="#">更新</a><a href="#">删除</a></td>
    </tr>
    </c:forEach>

第一步小结

用图片来表示以下上面的流程


继续完善list.jsp

做一个添加功能,其他先不管

所以让我们继续补充一个超链接

提交到add.jsp中吧

    <tr>
        <td colspan="8"><a href="add.jsp">添加</a></td>
    </tr>

没有add.jsp,我们在web文件夹下创建一个,大概长这个样子


信息很多,我们用post方法提交,交到一个addServlet让他处理

<h3>添加学生页面</h3>
<form action="${pageContext.request.contextPath}/addServlet" method="post">
    <table border="1px" width="600px">
        <tr>
            <td>姓名</td>
            <td><input type="text" name="sname"/></td>
        </tr>
        <tr>
            <td>性别</td>
            <td>
                <input type="radio" name="gender" value="男" checked/>男
                <input type="radio" name="gender" value="女"/>女
            </td>
        </tr>
        <tr>
            <td>电话</td>
            <td><input type="text" name="phone"/></td>
        </tr>
        <tr>
            <td>生日</td>
            <td><input type="text" name="birthday"/></td>
        </tr>
        <tr>
            <td>爱好</td>
            <td>
                <input type="checkbox" name="hobby" value="游泳"/>游泳
                <input type="checkbox" name="hobby" value="篮球"/>篮球
                <input type="checkbox" name="hobby" value="足球"/>足球
                <input type="checkbox" name="hobby" value="看书"/>看书
                <input type="checkbox" name="hobby" value="写字"/>写字
            </td>
        </tr>
        <tr>
            <td>简介</td>
            <td><textarea name="info" rows="3" cols="20"></textarea></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="添加"/>
            </td>
        </tr>
    </table>
</form>

addServlet要做什么呢

  1. 中文乱码问题解决
  2. 要获取客户端提交上来的数据并处理
  3. 把数据打包交给service进行业务处理
  4. 交给别人展示数据

实现过程

  1. 中文乱码问题解决

    request.setCharacterEncoding("UTF-8");
    
  2. 获取客户端提交上来的数据

                String sname = request.getParameter("sname");
                String gender = request.getParameter("gender");
                String phone = request.getParameter("phone");
                String birthday = request.getParameter("birthday");
                String hobby = request.getParameter("hobby");
                String info = request.getParameter("info");
    

    并处理下,考虑到birthday是data类型,要转换下

    Date date = new SimpleDateFormat("yyyy-MM-dd").parse(birthday);
    

    这里要注意下,用getParemeter方法得到的参数永远只有一个,对于爱好需要传入很多个,因此考虑使用getParameterValues方法,返回一个String[ ]数组,用Arrays.toString方法,打印之后发现有多出[ ],用substring方法截取中间段

   String hobby = Arrays.toString(request.getParameterValues("hobby"));
   hobby = hobby.substring(1, hobby.length() - 1);
  1. 把数据打包

    就是弄个JavaBean对象封装一下,用一堆set方法有点麻烦,直接在Student类中增加一个带参的构造器(之前写了空参的好处就在此,不会忘记写)

    Student student = new Student(sname, gender, phone, date, hobby, info);
    

    交给service进行业务处理

    取名为insert方法吧,等会去service中生成需要的接口和对应的实现类

    StudentService service = new StudentServiceImpl();
    service.insert(student);
    
  2. 交给别人展示数据

    这里就是把结果返回给list.jsp中啦。如果直接转发到list.jsp,会有一个问题,request域中是空的,会没有元素。因此需要重新转发到对应的Servlet中

    目前看起来转发需要加 / ,对其路径的获取还不是很懂

    request.getRequestDispatcher("/StudentListServlet").forward(request,response);
    

继续写全service

  1. 把StudentService补全,把其实现类补全

    接口就多一个insert方法

        /**
         * 需要添加到数据库的学生对象
         * @param student 封装
         * @throws SQLException 异常
         */
        void insert(Student student) throws SQLException;
    

    实现类

    业务没什么新的,就是在数据库里加东西,调用DAO层

        @Override
        public void insert(Student student) throws SQLException {
            StudentDao dao = new StudentDaoImpl();
            dao.insert(student);
        }
    

该传到DAO层了

  1. 把DAO补全,把其实现类补全

    接口和前面service层的接口是一样的

        /**
         * 需要添加到数据库的学生对象
         * @param student 封装
         * @throws SQLException 异常
         */
        void insert(Student student) throws SQLException;
    

    实现类

    之前sql代码打错了,大家一定要在sql试过了再写进来,这样成功率高点

     @Override
        public void insert(Student student) throws SQLException {
            QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
    
            //INSERT INTO stu VALUES(NULL,'姓名','性别','电话','1999-1-1','爱好','备注');
            runner.update("INSERT INTO stu VALUES(null,?,?,?,?,?,?)",
                    student.getSname(),
                    student.getGender(),
                    student.getPhone(),
                    student.getBirthday(),
                    student.getHobby(),
                    student.getInfo()
            );
        }
    

添加功能小结

  1. 做个jsp表单界面,注意name属性,这是之后获取参数用的

  2. 做个提交过去的Servlet

  3. Servlet收集数据,处理数据,封装数据,传递数据(service),展示数据(转发,可以发给Servlet)

  4. service处理业务逻辑,遇到对数据库处理的部分,调用dao层

  5. dao实现对数据库的处理



制作更新相关的功能

需求

  1. 点击更新能得到当前行的信息,跳到一个新的页面上
  2. 在表格上更改后点击按钮能更新数据库并在list页面上显示

1.点击更新能得到当前行的列表资料

这个页面和之前的添加页面差不多,稍微有点不同。我们直接复制为edit.jsp,稍作修改

需要获取查询的内容,自然使用servlet来处理

list.jsp需要改动的部分

取名为EditServlet,传一个sid为参数,el表达式中的stu是之前jstl的for循环出来的,此时request域中还有。

<a href="EditServlet?sid=${stu.sid}">更新</a>

EditServlet需求分析

  1. 获取传来是sid
  2. 通知service去实现需要的业务逻辑
  3. 传参数到request域中
  4. 带着request域转发到edit.jsp

EditServlet实现相关代码

  1. 获取传来是sid

    //转成int类型,比较方便
    int sid = Integer.parseInt(request.getParameter("sid"));
    
  2. 通知service去实现需要的业务逻辑

    //这里是要通过sid查到对应的人,之后要对后续流程做出相应的更改
    StudentService service = new StudentServiceImpl();
    Student student = service.findStudentById(sid);
    
  3. 传参数到request域中

    //以示区分,设为student(不过用stu也是一样的)
    request.setAttribute("student", student);
    
  4. 带着request域转发到edit.jsp

    request.getRequestDispatcher("edit.jsp").forward(request,response);
    

对Service/DAO补上相关功能

  1. service

StudentService接口,补一个findStudentById方法

/**
 * 找到某条学生数据
 * @param sid 学生ID
 * @return  学生对象
 * @throws SQLException sql异常
 */
Student findStudentById(int sid) throws SQLException;

StudentServiceImpl实现类,补一个业务流程处理,涉及数据库的CRUD部分,调用DAO

@Override
public Student findStudentById(int sid) throws SQLException {
    StudentDao dao = new StudentDaoImpl();
    return dao.findStudentById(sid);
}

  1. dao

接口,就是做个抽象类,通过sid返回一个Student对象,因为之后要显示到界面还需要提取参数

/**
 * 找到某条学生数据
 * @param sid 学生ID
 * @return  学生对象
 * @throws SQLException sql异常
 */
Student findStudentById(int sid) throws SQLException;

实现类,通过sql语句找到对应的数据,返回只有一条结果,用BeanHandler就好

@Override
public Student findStudentById(int sid) throws SQLException {
    QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
    String sql = "SELECT * FROM stu WHERE sid = ?";
    return runner.query(sql, new BeanHandler<>(Student.class), sid);
}

最终返回到edit.jsp中之后,要在相对应的地方获取对应的数据

type="text" 用value="${对应的数据}",举例

<input type="text" name="sname" value="${student.sname}"/>

type="radio",需要用对应的结果选中的,参数是checked,需要用if来判断一下。这里引入jstl核心标签。如果传入的文字是男,则设置为checked。女同理。

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<input type="radio" name="gender" value="男" <c:if test="${student.gender == '男'}">checked</c:if>/>男
<input type="radio" name="gender" value="女" <c:if test="${student.gender == '女'}">checked</c:if>/>女

type="checkbox",需要用对应的结果选中的,参数也是checked。但是爱好很多,这里不是用if,而是用包含contains来选择。引入jstl的function库。其余类似。

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<input type="checkbox" name="hobby" value="游泳" <c:if test="${fn:contains(student, '游泳')}">checked</c:if>/>游泳

textarea,需要在里面显示字的,直接在尖括号外面。

<textarea name="info" rows="3" cols="20">${student.info}</textarea>

到这里,第一步显示数据就完成了。

2. 在表格上更改后点击按钮能更新数据库并在list页面上显示

表单提交的地方要改一下,涉及到业务,还是用servlet

servlet需求:获取edit.jsp的数据,封装成JavaBean,传到service,再展示结果。和addServlet差不多,直接复制修改。

service、dao和上文都差不多,方法名用update吧,对数据库的操作中,因为没有传sid回来,因此就用其他的数据作为where条件

daoImpl的代码

@Override
public void update(Student student) throws SQLException {
   QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
   String sql = "UPDATE stu SET sname=?,gender=?,phone=?,birthday=?,info=? WHERE sid=?";
   //注意这里的要多一个sid,那么到底能不能传过来sid呢?
   runner.update(sql,
           student.getSname(),
           student.getGender(),
           student.getPhone(),
           student.getBirthday(),
           student.getInfo(),
           student.getSid()
   );
}

让我们从点击更新按钮开始,回顾整个流程。

  1. list.jsp中点击更新===》"EditServlet?sid=${stu.sid}"

这里是带着一个sid的

  1. EditServlet中调用service.findStudentById(sid),传到dao.findStudentById(sid),我们打印下这里返回的对象,发现返回的student对象是带有sid的。

  2. EditServlet转发到edit.jsp中,那么edit.jsp的request域中的student是带有sid的。

  3. edit.jsp点击提交到UpdateServlet。但是没有sid。因此问题出在edit.jsp中。

  4. edit.jsp发现没有调用出sid的代码,因此我们补充一个。

<input type="hidden" name="sid" value="${student.sid}" />

更新代码小结

逻辑都差不多,一层调用一层,前面懂了这里自然懂。

不过需要注意一些小问题。比如最后的sid,以及如何分析问题出在哪里的方法:按流程寻找法。


最后分页查询功能

这是界面效果

三层架构的业务处理逻辑

这个是我今天刚刚感受出来的

service封装各种JavaBean,然后回到servlet中展示数据,最后在jsp里调用域中的数据

制作过程

我个人喜欢从jsp开始做起来,缺什么补什么。

首先是一个入口,在index.jsp加入一行代码。StudentListPageServlet,再传一个参数currentPage=1

<h3><a href="${pageContext.request.contextPath}/StudentListPageServlet?currentPage=1">分页显示学生列表</a></h3>

1.servlet

//1.获取数据 : 获取页码数
int currentPage = Integer.parseInt(request.getParameter("currentPage"));
//2.得到处理好的封装数据
//这里创建一个新的JavaBean,因为一方面要保存查询出来的List,另一方面要保存当前页面、所有页面信息。但是JavaBean中要存放多少东西呢,不知道。做到后面,缺啥补啥。反正用原来的JavaBean不行就是了
StudentService service = new StudentServiceImpl();
PageBean<Student> studentByPage = service.findStudentByPage(currentPage);

//3.显示数据 : 存到quest域中转发
//因为现在我习惯流程来继续制作,所以第三步先不写了。

2.StudentService和其实现类

/**
* 查询当前页的数据
* @param currentPage 页码数
* @return 查询出的学生列表
* @throws SQLException SQL
*/
 PageBean<Student> findStudentByPage(int currentPage) throws SQLException;
@Override
public PageBean<Student> findStudentByPage(int currentPage) throws SQLException {
	PageBean<Student> pageBean = new PageBean<>();
	StudentDao dao = new StudentDaoImpl();
	//第一步就是要得出list
	List<Student> list = dao.findStudentByPage(currentPage);

3.StudentDao及其实现类

这是查询的分页list

/**
 * 查询当前页的数据
 * @param currentPage 页码数
 * @return 查询出的学生列表
 * @throws SQLException SQL
 */
List<Student> findStudentByPage(int currentPage)throws SQLException;
@Override
public List<Student> findStudentByPage(int currentPage) throws SQLException {
    QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
    String sql = "SELECT * FROM stu LIMIT ? OFFSET ?";
    //PAGE_SIZE是5,常数,第二个参数是偏移量。
    List<Student> query = runner.query(sql, new BeanListHandler<>(Student.class), PAGE_SIZE, (currentPage - 1) * PAGE_SIZE);
    return query;
}

补完servlet,制作pageList.jsp

接着从dao层往回传,到了servlet处,补充完

//3.显示数据 : 存到quest域中转发,名字为studentByPage,之后可以在jsp中取出对应的数据
request.setAttribute("studentByPage",studentByPage);
request.getRequestDispatcher("pageList.jsp").forward(request, response);

制作JavaBean

通过PageBean

public class PageBean<T> {
    //目前只需要一个list
    private List<T> list;
}

制作pageList.jsp

这个界面和查询的界面差不多,因此复制list.jsp,改名为pageList.jsp。

因为上步servlet中是存到request域中的studentByPage里面,page中将表达式中的list改为studentByPage.list

 <c:forEach items="${studentByPage.list }" var="stu">
            <tr>
                <td>${stu.sid }</td>
                <td>${stu.sname }</td>
                <td>${stu.gender }</td>
                <td>${stu.phone }</td>
                <td>${stu.birthday }</td>
                <td>${stu.hobby }</td>
                <td>${stu.info }</td>
                <td><a href="EditServlet?sid=${stu.sid}">更新</a> <a href="#" onclick="doDelete(${stu.sid})">删除</a></td>
            </tr>
        </c:forEach>

对比下jsp页面,lis分页已经好了,我们来制作最下面页码行。我们先做预处理,把需要的的东西先静态表示出来。下图是最后一行东西。

这个中括号内的数据都是动态的,也是需要从request域中取出的数据。因此按上面的流程,request域是servlet传的JavaBean中。因此只要把相关数据存到JavaBean对象即可。因此在JavaBean中加入相关的成员变量,并在service层中加入对应处理。

对service、dao等进行处理

在service中进行相应的处理

  1. 当前页,是可以直接获得的,在方法传入的参数中

    pageBean.setCurrentPage(currentPage);
    
  2. 总页数,需要稍作处理。逻辑是,先获取所有条数count,然后用其除每页条数,如果除不尽就多算一页。获取count是对数据库操作,因此依次补上需要的代码。以下是关键代码:

    //dao层对数据库的操作
    @Override
    public int findCount() throws SQLException {
        QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
        String sql = "SELECT COUNT(*) FROM stu";
        //ScalarHandler<>()常用来存数字,如count值,平均值等,数据类型是long,需要一个强转
        Long query = runner.query(sql, new ScalarHandler<>());
        return Math.toIntExact(query);
    }
    
    //service中对页数的处理
    int count = dao.findCount();
    int countAllPage = count % StudentDaoImpl.PAGE_SIZE == 0 ? count / StudentDaoImpl.PAGE_SIZE : count % StudentDaoImpl.PAGE_SIZE + 1;
    //存入request域中
    pageBean.setCountAllPage(countAllPage);
    
  3. 每页显示的条数,就是存在DAO实例中的常数

    pageBean.setPageSize(StudentDaoImpl.PAGE_SIZE);
    
  4. 总记录,在总页数那里已经求出来了,直接调用存入

    pageBean.setCountAllPage(countAllPage);
    
  5. 首尾页,就是第一页(=1)和最后一页(countAllPage),不用传

  6. 上一页和下一页,用之前的currentPage做加减,不用传

  7. 每一页单独页数,用第一页和最后一页遍历即可,不用传

继续在jsp中处理

在中括号[ ]相应位置用el表达式取出相应的数据

而点击跳转功能的实现,就是一个超链接,传servlet?=带参数即可

这里需要对首尾页和中间的遍历做一点点处理。

  1. 首页和上一页加个判断,当在第一页时不需要显示

    <c:if test="${studentByPage.currentPage != 1}">
        <a href="StudentListPageServlet?currentPage=1">首页</a>
        |
        <a href="StudentListPageServlet?currentPage=${studentByPage.currentPage - 1}">上一页</a>
    </c:if>
    
  2. 尾页和下一页,当在最后一页时不需要显示

    <c:if test="${studentByPage.currentPage != studentByPage.countAllPage}">
        <a href="StudentListPageServlet?currentPage=${studentByPage.currentPage + 1}">下一页</a>
        |
        <a href="StudentListPageServlet?currentPage=${studentByPage.countAllPage}">尾页</a>
    </c:if>
    
  3. 对中间的页码处理,用一个遍历

    <c:forEach begin="1" end="${studentByPage.countAllPage}" var="i">
       ${i}
    </c:forEach>
    

    补上超链接

    <c:forEach begin="1" end="${studentByPage.countAllPage}" var="i">
            <a href="StudentListPageServlet?currentPage=${i}">${i}
    </c:forEach>
    

    在当前页时,不需要超链接,用if判断

    <c:forEach begin="1" end="${studentByPage.countAllPage}" var="i">
        <c:if test="${studentByPage.currentPage == i}">${i}</c:if>
        <c:if test="${studentByPage.currentPage != i}">
            <a href="StudentListPageServlet?currentPage=${i}">${i}</a>
        </c:if>
    </c:forEach>
    

做好啦,完结撒花~

未完待续

原文地址:https://www.cnblogs.com/richardwlee/p/10316042.html