从MVC角度来理解SMBMS项目
登录功能
View
首先是V(View)层,也就是展示数据的前端界面,给servlet提供数据发起请求的一环:
Login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>系统登录 - 超市订单管理系统</title>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css"/>
</head>
<body class="login_bg">
<section class="loginBox">
<header class="loginHeader">
<h1>超市订单管理系统</h1>
</header>
<section class="loginCont">
<form class="loginForm" action="${pageContext.request.contextPath}/login.do" method="post" name="actionForm"
id="actionForm">
<div class="info">${error}</div>
<div class="inputbox">
<label for="userCode">用户名:</label>
<input type="text" class="input-text" id="userCode" name="userCode" placeholder="请输入用户名" required/>
</div>
<div class="inputbox">
<label for="userPassword">密码:</label>
<input type="password" id="userPassword" name="userPassword" placeholder="请输入密码" required/>
</div>
<div class="subBtn">
<input type="submit" value="登录"/>
<input type="reset" value="重置"/>
</div>
</form>
</section>
</section>
</body>
</html>
这部分不需要自己练习去写,因此这也不是我写的,我们只需要知道这里面的一些重点内容即可,比如这网页的标题是:系统登录 - 超市订单管理系统,通过一个form表单获取登录的用户名userCode和密码userPassword,用作后续的验证。
Controller
然后是C(Controller)层,也就是Servlet类所在的层,主要作用就是接收用户的请求 比如req的请求参数、Session信息等,还有控制视图的跳转的代码以及交给业务层处理对应的代码。
LoginServlet.java
public class LoginServlet extends HttpServlet {
//Servlet控制层,调用业务层代码
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("LoginServlet Satrt...");
//获取用户、密码
String userCode = req.getParameter("userCode");
String userPassword = req.getParameter("userPassword");
//和数据库中的密码进行对比,调用业务层
UserServiceImpl userService = new UserServiceImpl();
User user = userService.login(userCode, userPassword); //这里将登录的人查到了
if(user!=null){//查有此人,可以登录
//将用户信息放到Session中
req.getSession().setAttribute(Constants.USER_SESSION, user);
//跳转到内部主页
resp.sendRedirect("jsp/frame.jsp");
}else{//查无此人,转发会登录页面,提示登录错误
req.setAttribute("error", "用户名或密码不正确");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
这里servlet做的事情很简单,通过req.gerParameter获取前端输入的参数,并且调用业务层Service的代码,查询数据库中是否有对应的信息,如果有则将信息保存到Session中,并跳转到内部主页(或者此人的定制主页),如果没有则提示错误信息且转发到登陆界面Login.jsp。
Model
M(Model)层包括业务层和数据持久层,是代码量最多的一层,通常要做业务逻辑(Service)和CRUD(Dao——数据持久)。
业务层
做法通常是写一个Interface接口,然后在用一个实现类去实现对应的方法:
UserService.java
public interface UserService {
//用户登录
public User login(String userCode, String password);
}
UserServiceImpl.java
public class UserServiceImpl implements UserService{
//业务层都会调用Dao层,所以要引用Dao层
private UserDao userDao;
public UserServiceImpl(){
userDao = new UserDaoImpl();
}
@Override
public User login(String userCode, String password) {
Connection connection = null;
User user = null;
try {
connection = BaseDao.gerConnection();
//通过业务层调用对应的具体数据库操作
user = userDao.getLoginUser(connection, userCode, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
BaseDao.closeResourse(connection, null, null);
}
return user;
}
注意这里的特定写法,业务层都会调用Dao层,因此要先引用Daoprivate UserDao userDao;
然后在无参构造器里初始化这个Dao对象,这样程序调用业务层时,无参构造里的Dao对象也就被实例化了。回到我们的登录功能的业务逻辑,输入参数需要前面获取到的用户名和密码,然后调用操作数据库的公共类BaseDao,调用里面的gerConnection方法连接上数据库,然后通过try/catch调用userDao的数据库操作方法getLoginUser,就可以查到是否有对应的用户了。
Dao层
Date Access Object 数据访问对象,用来对应数据库实体,负责与数据库进行联络的一些任务都封装在此。首先要一个访问数据库的公共类,在里面实现获取数据库连接、查询方法、增删改方法以及关闭连接释放资源的方法。
BaseDao.java
package com.tunan.dao;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @author Tunan
* @Date 2021/9/12 18:52
* 操作数据库的公共类
*/
public class BaseDao {
private static String driver;
private static String url;
private static String username;
private static String password;
//静态代码块,类加载时候就初始化了
static {
Properties properties = new Properties();
//通过类加载器读取对应的资源
InputStream is = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}
//获取数据库的链接
public static Connection gerConnection(){
Connection connection = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
//编写查询公共类
public static ResultSet execute(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet, String sql,Object[] params) throws SQLException {
//预编译的SQL,后面直接执行即可
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
//setObject占位符从1开始,但数组从0开始
preparedStatement.setObject(i+1,params[i]);
}
resultSet = preparedStatement.executeQuery();
return resultSet;
}
//编写增删改查公共方法
public static int execute(Connection connection, PreparedStatement preparedStatement, String sql,Object[] params) throws SQLException {
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
//setObject占位符从1开始,但数组从0开始
preparedStatement.setObject(i+1,params[i]);
}
int updataRows = preparedStatement.executeUpdate();
return updataRows;
}
//释放资源
public static boolean closeResourse(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
boolean flag = true;
if(resultSet != null) {
try {
resultSet.close();
//GC垃圾回收机制回收
resultSet = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
if(preparedStatement != null) {
try {
preparedStatement.close();
//GC垃圾回收机制回收
preparedStatement = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
if(connection != null) {
try {
connection.close();
//GC垃圾回收机制回收
connection = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
return flag;
}
}
然后是分模块的不同的Dao层,这里面就会有大量的代码重复
UserDaoImpl.java
public class UserDaoImpl implements UserDao{
//得到要登陆的用户
@Override
public User getLoginUser(Connection connection, String userCode, String userPassword) throws SQLException {
PreparedStatement pstm = null;
ResultSet rs = null;
User user = null;
if(connection != null) {
String sql = "select * from smbms_user where userCode=? and userPassword=?";
Object[] params = {userCode, userPassword};
rs = BaseDao.execute(connection, pstm, rs, sql, params);
if(rs.next()){
user = new User();
user.setId(rs.getInt("id"));
user.setUserCode(rs.getString("userCode"));
user.setUserName(rs.getString("userName"));
user.setUserPassword(rs.getString("userPassword"));
user.setGender(rs.getInt("gender"));
user.setBirthday(rs.getDate("birthday"));
user.setPhone(rs.getString("phone"));
user.setAddress(rs.getString("address"));
user.setUserRole(rs.getInt("userRole"));
user.setCreatedBy(rs.getInt("createdBy"));
user.setCreationDate(rs.getTimestamp("creationDate"));
user.setModifyBy(rs.getInt("modifyBy"));
user.setModifyDate(rs.getTimestamp("modifyDate"));
}
BaseDao.closeResourse(null, pstm, rs);
}
return user;
}
}
这里面的Sql使用了预编译类型的PreparedStatement ,通过占位符和BaseDao里面的preparedStatement.setObject方法进行传参,防止了Sql注入,但也使代码有些臃肿,后面学习的框架以及动态Sql就会解决这一点。查询到的rs是一个链表,将结果的属性全部获取,放入Session中预备着给网页后续再用。注意最后一步关闭连接释放资源。
总结
jsp和servlet虽然已经不那么常用,写起来也十分的冗余复杂,但是还是值得学习的,对后面框架学习的底层理解会有一定的帮助。通过梳理登录功能的MVC还是可以看出,每一块的功能十分明确,自己写一遍会增加印象和理解。但是开发的时候还是从底层向上开发,也就是先开发Dao层和业务层的代码,并且多做@Test测试!