目录
1.数据库准备
- user表结构
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
- user表数据
INSERT INTO `user` (`id`, `username`, `password`, `sex`, `age`, `address`) VALUES (1, '张三丰', '111', '男', 150, '湖南');
INSERT INTO `user` (`id`, `username`, `password`, `sex`, `age`, `address`) VALUES (2, '玛卡巴卡', '111', '未知', 30, '花园宝宝');
- 数据预览
2.三层架构
我们的开发架构一般都是基于两种形式,一种是 C/S 架构
,也就是客户端/服务器,另一种是 B/S 架构
,也就是浏览器服务器。在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发。
那么在 B/S 架构中,系统标准的三层架构包括:表现层、业务层、持久层。
三层架构中,每一层各司其职,接下来我们就说说每层都负责哪些方面:
- 表现层(web/servlet):
也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web 层,web 需要接收 http 请求,完成 http 响应。
表现层包括展示层
和控制层
:控制层负责接收请求,展示层负责结果的展示。
表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。
表现层的设计一般都使用 MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,
是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职:
- Model(模型):
通常指的就是我们的数据模型。作用一般情况下用于封装数据。
- View(视图):
通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。
通常视图是依据模型数据创建的。
- Controller(控制器):
是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。
- 业务层(service):
也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制)
- 持久层(dao):
也就是我们是常说的 dao 层
。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。通俗地讲,持久层就是和数据库交互,对数据库表进行增删改查的
。
3.项目搭建
3.1 项目结构
3.2 导入依赖
- pom.xml
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
</dependencies>
3.3 准备包结构
- servlet 控制层代码,主要由负责接收请求和结果的展示。
- service 服务层代码,主要用于处理业务逻辑
- dao 数据访问层,主要用户定义对于各个表格的CURD的方法
- pojo 实体类层,主要用于存放和数据库对应的实体类以及一些VO对象
- util 工具类包,主要用存放一些工具类
- filters 过滤器包,专门用于存放一些过滤器
3.4 准备工具类
3.4.1 JDBC工具类
Config
获取连接对象
package com.by.util;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class Config {
private static String username;
private static String password;
private static String url;
private static String driverClass;
static{
//特殊Hashtable
Properties prop = new Properties();
try {
//读取类路径下的配置文件
InputStream is = Config.class.getClassLoader().getResourceAsStream("jdbc.properties");
//InputStream in = new FileInputStream("javaweb\\user_mgr\\src\\main\\resources\\jdbc.properties");
//加载配置文件内容
prop.load(is);
// acquire the properties from file
username = prop.getProperty("username");
password = prop.getProperty("password");
url = prop.getProperty("url");
driverClass = prop.getProperty("driverClass");
// register the driver
if(driverClass != null && url != null && username != null && password != null){
//加载驱动
Class.forName(driverClass);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* @description: 获取连接
* @param
* @return: java.sql.Connection
*/
public static Connection getConnection() throws SQLException {
Connection con = null;
try{
con = DriverManager.getConnection(url,username,password);
}catch (Exception e){
e.printStackTrace();
}
return con;
}
/**
* @description: 释放资源
* @param con
* @param stat
* @param res
*/
public static void close(Connection con, Statement stat , ResultSet res) throws SQLException {
if(con != null){
con.close();
}
if (stat != null) {
stat.close();
}
if (res != null) {
res.close();
}
}
}
JdbcUtil
执行sql语句并返回数据
package com.by.util;
import java.sql.*;
/**
* @Description: jdbc封装工具类
* @Version: 1.0
*/
public class JdbcUtils {
private static Connection con = null;
private static Statement st = null;
private static ResultSet rs = null;
private static PreparedStatement ps = null;
/**
* @description: 通用更新封装
* @param sql sql语句
* @param params 传入多参,也可以以数组形式传入
* @return: java.sql.ResultSet
*/
public static int update(String sql,Object ...params) throws SQLException {
int flag = 0;
try {
con = Config.getConnection();
ps = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1,params[i]);
}
System.out.println(ps.toString());
flag = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
Config.close(con,ps,null);
}
return flag;
}
/**
* @description: 通用查询
* @param sql
* @param params
* @return: java.sql.ResultSet
*/
public static ResultSet query(String sql, Object ...params) throws SQLException {
try {
con = Config.getConnection();
ps = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]); //把?替换成实际的参数
}
System.out.println(ps.toString());
rs = ps.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
return rs;
}
}
- 在resources文件夹添加
jdbc.properties
配置文件
driverClass = com.mysql.jdbc.Driver
username = root
password = 123456
url = jdbc:mysql://localhost:3306/servlet?characterEncoding=UTF-8
3.5 准备接口和实现类
3.5.1 准备实体类
package com.by.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
private String sex;
private Integer age;
private String address;
public User(String username, String password, String sex, Integer age, String address) {
this.username = username;
this.password = password;
this.sex = sex;
this.age = age;
this.address = address;
}
}
3.5.2 Dao层
- UserDao接口
package com.by.dao;
import com.by.pojo.User;
import java.sql.SQLException;
import java.util.List;
public interface UserDao {
//登录
User login(String username, String password) throws SQLException, Exception;
//查询所有用户
List<User> selectUser() throws Exception;
//添加用户
boolean addUser(User user) throws Exception;
//根据id获取用户
User getUserById(int id) throws Exception;
//修改用户
boolean updateUser(User user) throws Exception;
//删除用户
boolean delUser(int id) throws Exception;
}
3.5.3 Service层
- UserService接口
package com.by.service;
import com.by.pojo.User;
import java.util.List;
public interface UserService {
//登录
User login(String username, String password) throws Exception;
//查询所有用户
List<User> selectUser() throws Exception;
//添加用户
boolean addUser(User user) throws Exception;
//根据id查询用户
User getUserById(int id) throws Exception;
//修改用户
boolean updateUser(User user) throws Exception;
//删除用户
boolean delUser(int id) throws Exception;
}
4.基础功能实现
4.1 登录功能
- 登录表单
<form action="addUser" method="post">
账户:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
性别:<input type="text" name="sex"><br>
年龄:<input type="text" name="age"><br>
地址:<input type="text" name="address"><br>
<input type="submit" value="新增">
</form>
- LoginServlet
//获取前台数据
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用service层
User user = userService.login(username,password);
//根据是否登录成功跳转页面
if (user != null){
//登录成功,跳转数据显示页
resp.sendRedirect("selectUser");
}else {
//登录失败,重新登录
resp.sendRedirect("login.jsp");
}
- Service层
@Override
public User login(String username, String password) throws Exception {
//无其他业务处理,直接调用dao
return userDao.login(username,password);
}
- Dao层
@Override
public User login(String username, String password) throws Exception {
//编写sql,根据用户名和密码查找用户
String sql = "select * from user where username=? and password=?";
ResultSet rs = JdbcUtils.query(sql,username,password);
if (rs.next()){
//getUser方法将结果集数据封装成一个user对象
return getUser(rs);
}
return null;
}
4.2 查询所有用户
-
页面预览
-
select_user.jsp
<%--
为搭配jsp,暂时使用jstl实现循环添加tb标签
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>芜湖起飞🚀</h1>
<a href="add_user.jsp">新增</a>
<table border="1" width="50%" cellspacing="0">
<thead>
<tr>
<%--表头--%>
<th>id</th>
<th>账户</th>
<th>密码</th>
<th>性别</th>
<th>年龄</th>
<th>地址</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<%--遍历用户集合,生成表格--%>
<c:forEach items="${userList}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<td>${user.sex}</td>
<td>${user.age}</td>
<td>${user.address}</td>
<td style="text-align: center">
<a href="toUpdateUser?id=${user.id}" >修改</a>
<a href="delUser?id=${user.id}" >删除</a>
</td>
</tr>
</c:forEach>
</tr>
</tbody>
</table>
</body>
</html>
- SelectUserServlet
显示所有用户信息
//将获取的所有用户对象存放到集合中
List<User> userList = userService.selectUser();
//将集合数据挂载到请求上
req.setAttribute("userList",userList);
//成功获取数据就跳转到显示页面
req.getRequestDispatcher("select_user.jsp").forward(req,resp);
- Service层
@Override
public List<User> selectUser() throws Exception {
//无其他业务处理,直接调用dao
return userDao.selectUser();
}
- Dao层
@Override
public List<User> selectUser() throws Exception {
//编写sql,查询所有用户并按id降序排序
String sql = "select * from user order by id desc";
ResultSet rs = JdbcUtils.query(sql);
List<User> userList = new ArrayList<>();
//将结果集记录依次取出,并封装成user对象添加到集合中
while (rs.next()) {
//将结果集rs当前指针所指记录封装成user对象
userList.add(getUser(rs));
}
return userList;
}
4.3 添加用户
- add_user.jsp
<form action="addUser" method="post">
账户:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
性别:<input type="text" name="sex"><br>
年龄:<input type="text" name="age"><br>
地址:<input type="text" name="address"><br>
<input type="submit" value="新增">
</form>
- AddUserServlet
添加新用户,id自增
//1.获取前台数据
String username = req.getParameter("username");
String password = req.getParameter("password");
String sex = req.getParameter("sex");
Integer age = Integer.valueOf(req.getParameter("age"));
String address = req.getParameter("address");
//2.调用service层添加用户
boolean flag = userService.addUser(new User(username,password,sex,age,address));
System.out.println("添加"+(flag?"成功":"失败"));
//3.跳转到select_User页面显示数据
resp.sendRedirect("selectUser");
- Service层
@Override
public boolean addUser(User user) throws Exception {
//无其他业务处理,直接调用dao
return userDao.addUser(user);
}
- Dao层
@Override
public boolean addUser(User user) throws Exception {
//编写sql,添加用户
String sql = "insert into user (username,password,sex,age,address) values (?,?,?,?,?)";
int nums = JdbcUtils.update(sql,user.getUsername(),user.getPassword(),user.getSex(),user.getAge(),user.getAddress());
//根据影响行数判断是否添加成功,大于0行表示成功
return nums > 0;
}
4.4 修改用户
4.4.1 数据回显
- ToUpdateUserServlet
点击修改后通过get方式携带用户id发送请求到ToUpdateUserServlet,然后将从数据库查到的用户数据转发到update_user.jsp页面,实现数据回显。
//获取用户id
int id=Integer.parseInt(req.getParameter("id"));
//通过id获取user对象
User user = userService.getUserById(id);
//将user对象挂载到请求中,转发到修改页
req.setAttribute("user",user);
req.getRequestDispatcher("update_user.jsp").forward(req,resp);
- service层
@Override
public User getUserById(int id) throws Exception {
//无其他业务处理,直接调用dao
return userDao.getUserById(id);
}
- dao层
根据id获取user对象
@Override
public User getUserById(int id) throws Exception {
String sql = "select * from user where id=?";
ResultSet rs = JdbcUtils.query(sql,id);
if (rs.next()){
return getUser(rs);
}
return null;
}
4.4.2 修改用户数据
- update_user.jsp
进入修改页面,显示servlet页面传来的该用户的数据
<h1>修改用户</h1>
<form action="updateUser" method="post">
账户:<input type="text" name="username" value="${user.getUsername()}"><br>
密码:<input type="text" name="password" value="${user.getPassword()}"><br>
性别:<input type="text" name="sex" value="${user.getSex()}"><br>
年龄:<input type="text" name="age" value="${user.getAge()}"><br>
地址:<input type="text" name="address" value="${user.getAddress()}"><br>
<input type="hidden" name="id" value="${user.getId()}">
<input type="submit" value="修改">
</form>
- UpdateUserServlet
用户提交修改后发送updateUser请求
//1.获取前台数据
Integer id = Integer.valueOf(req.getParameter("id"));
String username = req.getParameter("username");
String password = req.getParameter("password");
String sex = req.getParameter("sex");
Integer age = Integer.valueOf(req.getParameter("age"));
String address = req.getParameter("address");
//2.调用service层修改用户
boolean flag = userService.updateUser(new User(id,username,password,sex,age,address));
System.out.println("修改"+(flag?"成功":"失败"));
//3.跳转到select_User页面显示数据
resp.sendRedirect("selectUser");
- Service层
@Override
public boolean updateUser(User user) throws Exception {
//无其他业务处理,直接调用dao
return userDao.updateUser(user);
}
- Dao层
@Override
public boolean updateUser(User user) throws Exception {
//编写sql语句,修改指定id用户的数据
String sql = "update user set username=?,password=?,sex=?,age=?,address=? where id = ?";
int nums =JdbcUtils.update(sql,user.getUsername(),user.getPassword(),user.getSex(),user.getAge(),user.getAddress(),user.getId());
//根据影响行数判断是否修改成功,大于0行表示成功
return nums > 0;
}
4.5 删除用户
- DelUserServlet
//获取用户id
int id=Integer.parseInt(req.getParameter("id"));
//执行删除操作,并返回boolean结果
boolean flag = userService.delUser(id);
System.out.println("删除"+(flag?"成功":"失败"));
//跳转到select_User页面显示数据
resp.sendRedirect("selectUser");
- Service层
@Override
public boolean delUser(int id) throws Exception {
//无其他业务处理,直接调用dao
return userDao.delUser(id);
}
- Dao层
@Override
public boolean delUser(int id) throws Exception{
//编写sql语句,根据用户id删除指定用户
String sql = "delete from user where id=?";
int nums = JdbcUtils.update(sql,id);
//根据影响行数判断是否修改成功,大于0行表示成功
return nums>0;
}
5.拓展功能实现
5.1 判断登录状态
- 需求:
一些敏感数据和操作需要用户在登录状态才能查看和执行
- 实现思路:
- 用户登录后将user数据通过key-value形式保存在session中
- 过滤除登录请求外的所有请求,根据请求中session的参数进行判断
- 若session中有user参数,则表示已登录,放行请求
- 若session中没有user参数,则表示未登录,重定向到login.jsp页面
- 注意事项:
如果连登录页和登录请求一起过滤了,就会陷入无限重定向,因为登录页和登录请求的session中也没有user参数,也会重定向到ogin.jsp页面,但是重定向后也会发起请求,依旧会被过滤。仍没有user参数,就会无限循环下去。
- LoginFilter
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
//向下转型
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (req.getRequestURI().contains("login") || req.getRequestURI().endsWith("/")){
//请求包含login或以/结尾,放行
filterChain.doFilter(request,response);
System.out.println("请求包含login或以/结尾,放行");
return;
}
//获取session对象,获取user
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
//判断是否已登录
if (user != null){
//user有值,已登录,放行
filterChain.doFilter(request,response);
System.out.println("已登录,放行");
}else {
//未登录,重定向到login.jsp
resp.sendRedirect("login.jsp");
System.out.println("未登录,重定向到login.jsp");
}
}
@Override
public void destroy() {
}
}
- 配置Filter
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.by.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.2 网站浏览量统计
- 需求:
统计网站浏览量,并显示用户是第几个访问网站的用户
- 实现思路:
- ServletContext对象作为全局域对象,在整个服务器运行期间都保持存活,可持续记录网站访问量,通过监听器监听ServletContext对象,当ServletContext对象创建时,设置count参数,用于统计访问量。
- 每当一个浏览器第一次发送请求到服务器时,会自动生成一个session对象,可在每次创建session对象时算作一次用户访问,通过监听session的创建,动态增加浏览器。
- AccessCountListener
public class AccessCountListener implements HttpSessionListener, ServletContextListener {
/**
* session创建时调用(浏览器第一次访问服务器,或session失效后重新访问时)
* @param se the HttpSessionEvent containing the session
*/
@Override
public void sessionCreated(HttpSessionEvent se) {
//获取ServletContext对象
ServletContext servletContext = se.getSession().getServletContext();
Integer count = (Integer) servletContext.getAttribute("count");
//每次调用该方法,访问量count+1
servletContext.setAttribute("count",++count);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
}
/**
* 创建ServletContext对象时调用(服务器开启时)
* @param sce the ServletContextEvent containing the ServletContext
* that is being initialized
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
//获取ServletContext对象
ServletContext servletContext = sce.getServletContext();
//添加count参数,记录访问量
servletContext.setAttribute("count", 0);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
- 配置Listener
<listener>
<listener-class>com.by.Listener.AccessCountListener</listener-class>
</listener>