Web学习笔记():超市管理系统的搭建
1. 项目前瞻
整合整个JavaWeb所学习的知识做一个超市管理系统小项目。该管理系统分为四个功能:
-
登录注销
-
用户管理
-
订单管理
-
供应商管理
每个部分都需要连接数据库,实现增删改查功能。
2.项目实现
2.1 准备工作
首先创建一套模板式的项目结构:
-
创建Maven中的web-app项目模板
-
修改Web-inf中的xml文件,导入项目依赖
-
配置Tomcat,IDEA连接数据库
-
在main目录下建Java、resources两个包,在java包中创建项目包结构
- pojo数据库实体类
- dao持久化操作数据库
- service业务层
- servlet
- filter过滤器层
- util工具类
-
为了操作数据库,在resources包下新建properties文件,写入以下键值对:
driver=com.mysql.jdbc.Driver url=jdbd:mysql://localhost:3306/smbms?userUnicode=true&characterEncoding=utf8 username=root password=123456
-
在pojo目录创建数据库对应的实体类,类中的每个变量对应表单的各个属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwD02idK-1597284538511)(C:\Users\hasee\Desktop\XIKAIJAVA\JavaWeb\smbmgProject\1596163713199.png)]
-
在dao中创建操作数据库的公共类
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 stream = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(stream);
} catch (IOException e) {
e.printStackTrace();
}
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}
public static Connection getConnection(){
Connection connection = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
//编写查询公共类,传入statement、resultSet是为了方便关闭
public static ResultSet execute(Connection connection,String sql,Object[] params,ResultSet resultSet,PreparedStatement statement) throws SQLException {
statement = connection.prepareStatement(sql);
//开始赋值
for (int i = 0; i < params.length; i++) {
//statement占位符从1开始,数组从0开始
statement.setObject(i+1,params[i]);
}
resultSet = statement.executeQuery();
return resultSet;
}
//编些增删改公共类
public static int execute(Connection connection,String sql,Object[] params,PreparedStatement statement) throws SQLException {
statement = connection.prepareStatement(sql);
//开始赋值
for (int i = 0; i < params.length; i++) {
//statement占位符从1开始,数组从0开始
statement.setObject(i+1,params[i]);
}
int update = statement.executeUpdate();
return update;
}
//关闭连接,释放资源
public static boolean closeResource(Connection connection,ResultSet resultSet,PreparedStatement preparedStatement){
boolean flag = false;
if (resultSet!= null){
try {
resultSet.close();
resultSet = null;
} catch (SQLException e) {
e.printStackTrace();
flag = false;
}
}
if (connection!= null){
try {
connection.close();
connection = null;
} catch (SQLException e) {
e.printStackTrace();
flag = false;
}
}
if (preparedStatement!= null){
try {
preparedStatement.close();
preparedStatement = null;
} catch (SQLException e) {
e.printStackTrace();
flag = false;
}
}
return flag;
}
}
- 在filter下创建字符编码过滤器,并在web.xml中注册
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
-
将前端静态资源导入webapp目录
至此准备工作已经全部完毕,都是一些固定的流程,几乎在每个项目通用。
2.2 登录功能的实现
登录功能的实现思路为:网页发送请求到servlet,servlet将数据发送到服务层,服务层将数据传递到持久层,持久层调用数据库查询数据,将查询结果一层层返回。编写的顺序推荐从下到上,即先编写持久层查询数据的代码,再编写服务层,最后完成servlet的编写。
2.2.1 持久层的实现
持久层并不是上来直接就撸代码,我们需要先实现一个登录功能的持久层接口,确定需要传输进入的参数为数据库Connection和传入的用户名userCode:
public interface UserDao {
public User getLoginUser(Connection connection,String userCode) throws SQLException;
}
接着实现这个接口,需要调用dao层的公共类BaseDao,得到的是userCode对应的整个User对象数据,将其放入pojo实现类中:
public class UserDaoImpl implements UserDao {
@Override
public User getLoginUser(Connection connection, String userCode) throws SQLException {
ResultSet rs = null;
PreparedStatement preparedStatement = null;
User user = null;
if (connection != null) {
System.out.println(userCode);
String sql = "select * from smbms_user where userCode=?";
Object[] params = {userCode};
rs = BaseDao.execute(connection, sql, params, rs, preparedStatement);
if (rs.next()){
user = new User();
user.setId(rs.getInt("id"));
user.setUserCode(rs.getString("userCode"));
System.out.println(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.closeResource(connection,rs,preparedStatement);
}
return user;
}
}
2.2.2 服务层的实现
通过观察服务层的代码可以看到,服务层的作用仅仅是将Servlet请求的用户名转发到持久层,接收持久层返回的user对象和判断密码是否正确,但是user对象可以直接传送给Servlet,而且判断密码也可以在Servlet层完成 ,所以个人感觉这一层有些鸡肋,也许这是因为在小项目里面所以作用不甚明显吧。代码的实现有很多种,最主要的还是需要学会这种分层的思想。
以下为服务层的代码,和持久层相同,都是先创建一个接口,再通过实现接口来完成功能:
public interface UserService {
public User login(String userCode,String userPassword) throws SQLException;
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl() {
userDao = new UserDaoImpl();
}
//调用Dao层
@Override
public User login(String userCode, String userPassword){
Connection connection =null;
User user = null;
try {
connection = BaseDao.getConnection();
user = userDao.getLoginUser(connection, userCode);
} catch (SQLException e) {
e.printStackTrace();
}finally {
BaseDao.closeResource(connection,null,null);
}
if (null != user) {
if (!user.getUserPassword().equals(userPassword))
user = null;
}
return user;
}
}
2.2.3 Servlet层的实现
Servlet层的操作就很常规化了,接收用户名和密码后向服务层传递,再接收服务层的返回数据,登录成功则进入系统,失败则返回登录界面。
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收用户名和密码
String userCode = req.getParameter("userCode");
String userPassword = req.getParameter("userPassword");
//获取数据库信息
UserServiceImpl userService = new UserServiceImpl();
User user = userService.login(userCode, userPassword);
//判断是否登录成功
if (user!= null){
//登录成功,重定向到主页
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层在xml中映射即可。
2.3 注销功能的实现
注销功能在Servlet中直接实现,简直不要太简单,直接上代码:
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//移除Session
req.getSession().removeAttribute(Constants.USER_SESSION);
resp.sendRedirect(req.getContextPath()+"/login.jsp");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
不要忘记在xml中配置映射。
2.4 对非法访问进行过滤
非法访问是指在未登录的情况下访问网页内部,需要使用过滤器,也很简单:
public class SysFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse)servletResponse;
HttpServletRequest req = (HttpServletRequest)servletRequest;
User user = (User) req.getSession().getAttribute(Constants.USER_SESSION);
if (user == null){
resp.sendRedirect(req.getContextPath()+"/error.jsp");
}else {
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
当然,这个也要在xml中配置映射。
2.5 修改密码功能的实现
修改密码功能有两个部分需要注意:
- 后台修改新密码
- 前端通过ajax完成旧密码输入是否正确,即判断是否本人操作
2.5.1 dao层的操作
dao层就是将输入的新密码更新到数据库中,没啥好说的。
public int updataPwd(Connection connection, int id, String password) throws SQLException {
PreparedStatement preparedStatement = null;
int execute = 0;
if (connection != null) {
String sql = "update smbms_user set userPassword = ? where id = ?";
Object[] params = {password, id};
execute = BaseDao.execute(connection, sql, params, preparedStatement);
BaseDao.closeResource(connection,null,preparedStatement);
}
return execute;
}
2.5.2 Service层的操作
service层除了转发功能还验证了密码修改是否成功。
public Boolean updatePwd(int id, String pwd){
Connection connection = null;
boolean flag = false;
int i = 0;
try {
connection = BaseDao.getConnection();
i = userDao.updataPwd(connection, id, pwd);
if (i > 0){
flag = true;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
BaseDao.closeResource(connection,null,null);
}
return flag;
}
2.5.3 Servlet层的工作
servlet层里会先读取前端发送来的数据,先判断进行的是验证旧密码还是修改新密码两项工作中的哪一项,再继续执行。
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getParameter("method");
if (method.equals("savepwd")&&method!=null){
this.updatePwd(req,resp);
}else if (method.equals("pwdmodify")&&method!= null){
this.pwdModify(req,resp);
}
}
public void updatePwd(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object attribute = req.getSession().getAttribute(Constants.USER_SESSION);
String newpassword = req.getParameter("newpassword");
if (attribute != null && newpassword!= null && newpassword.length()!= 0){
UserServiceImpl service = new UserServiceImpl();
Boolean flag = service.updatePwd(((User) attribute).getId(), newpassword);
System.out.println(((User) attribute).getId());
System.out.println(newpassword);
if (flag){
req.setAttribute("message","密码修改成功,请使用新密码登录");
req.getSession().removeAttribute(Constants.USER_SESSION);
}else {
req.setAttribute("message","密码修改失败");
}
}else {
req.setAttribute("message","新密码格式错误");
}
req.getRequestDispatcher("pwdmodify.jsp").forward(req,resp);
}
public void pwdModify(HttpServletRequest req, HttpServletResponse resp){
Object attribute = req.getSession().getAttribute(Constants.USER_SESSION);
String oldpassword = req.getParameter("oldpassword");
//创建一个结果集传递给ajax
HashMap<String, String> map = new HashMap<>();
if (attribute == null){ //session过期了
map.put("result","sessionerror");
}else if (oldpassword == null && oldpassword.length() == 0){
map.put("result","error");
}else {
if (((User)attribute).getUserPassword().equals(oldpassword)){
map.put("result","true");
}else {
map.put("result","false");
}
}
try {
resp.setContentType("application/json");
PrintWriter writer = resp.getWriter();
writer.write(JSONArray.toJSONString(map));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.6 用户管理底层实现
用户管理层可以说是目前所有功能里最难的一部分了。首先通过用户管理层界面我们可以看到:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TLmbEV0p-1597284538530)(C:\Users\hasee\Desktop\XIKAIJAVA\JavaWeb\smbmgProject\1596340405700.png)]
该部分内容由三个部分组成:
-
用户角色
-
通过条件查询到的用户和分页
-
用户总数
这意味着我们需要完成三条线的设计,每条线都有自己的Service和dao去调用数据库获取数据,如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZ6eHTqk-1597284538533)(C:\Users\hasee\Desktop\XIKAIJAVA\JavaWeb\smbmgProject\1596340558771.png)]
该项目的重难点在dao层和Servlet层的实现。
在dao层中,因为进行条件搜索需要参数用户名、用户角色,如果使用静态sql,即每条sql只用来查询用户名或者角色的话那么就需要三个dao层进行处理;而使用动态sql,即先验证传入的参数是否为空来确定搜索的条件,通过StringBuffer的append方法进行sql语句的拼接,就可以动态的确定sql语句的搜索范围,减少代码量。
在Servlet层中,需要同时处理三条线的数据并对前端数据的准确性进行确认。因为前端传入数据的特性,后台需要再次进行验证,之后就可以调用serviceimpl方法进行数据操作,再将数据返回给前端就可以了。
2.6.1 用户总数线
1.从dao层写起,sql语句使用了连表查询和 count(1) ,这是专门用于查询结果数量的语句,效率远远高于 count(*) 。同时利用了StringBuffer和List,是为了针对该功能的搜索查询,即可以根据搜索的用户名或角色显示相应结果相应的数量。
public int getUserCount(Connection connection, String username, int userRole) throws SQLException {
//查询所有相关的用户
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
int count = 0;
if (connection != null){
StringBuffer sql = new StringBuffer();
ArrayList<Object> list = new ArrayList<>();
sql.append("select count(1) as count from smbms_user u,smbms_role r where u.userRole = r.id");
//查询名字对应的用户
if (!StringUtils.isNullOrEmpty(username)){
sql.append(" and u.username like ?");
list.add("%"+username+"%");
}
if (userRole > 0){
sql.append(" and u.userRole like ?");
list.add(userRole);
}
Object[] params = list.toArray();
resultSet = BaseDao.execute(connection,String.valueOf(sql),params,resultSet,preparedStatement);
if (resultSet.next()){
count = resultSet.getInt("count");
}
BaseDao.closeResource(null,resultSet,preparedStatement);
}
return count;
}
2.service层方面进行数据的转发:
public int getUserCount(String username, int userRole) {
Connection connection = null;
int count = 0;
try {
connection = BaseDao.getConnection();
count = userDao.getUserCount(connection,username,userRole);
} catch (SQLException e) {
e.printStackTrace();
}finally {
BaseDao.closeResource(connection,null,null);
}
return count;
}
2.6.2 条件查询结果和分页线
1. 条件查询的dao层使用连表查询,同时将结果进行分页,通过传入的页数参数返回相应页数的结果。
public List<User> getUserList(Connection connection, String username, int userRole, int currentPage, int pageSize) throws SQLException {
ResultSet rs = null;
PreparedStatement preparedStatement = null;
ArrayList<User> userList = new ArrayList<>();
int count = 0;
if (connection != null){
StringBuffer sql = new StringBuffer();
ArrayList<Object> list = new ArrayList<>();
sql.append("select u.*,r.roleName as userRoleName from smbms_user u,smbms_role r where u.userRole = r.id");
//查询名字对应的用户
if (!StringUtils.isNullOrEmpty(username)){
sql.append(" and u.username like ?");
list.add("%"+username+"%");
}
if (userRole > 0){
sql.append(" and u.userRole like ?");
list.add(userRole);
}
//将查询结果分页
sql.append(" order by creationDate DESC limit ?,?");
currentPage = (currentPage-1)*pageSize;
list.add(currentPage);
list.add(pageSize);
Object[] params = list.toArray();
rs = BaseDao.execute(connection,String.valueOf(sql),params,rs,preparedStatement);
while (rs.next()){
User _user = new User();
_user.setId(rs.getInt("id"));
_user.setUserCode(rs.getString("userCode"));
_user.setUserName(rs.getString("userName"));
_user.setGender(rs.getInt("gender"));
_user.setBirthday(rs.getDate("birthday"));
_user.setPhone(rs.getString("phone"));
_user.setUserRole(rs.getInt("userRole"));
_user.setUserRoleName(rs.getString("userRoleName"));
userList.add(_user);
}
BaseDao.closeResource(null,rs,preparedStatement);
}
return userList;
}
2.service层方面进行数据的转发:
public List<User> getUserList(String queryUserName, int queryUserrRole, int currentPage, int pageSize) {
List<User> list = null;
Connection connection = null;
try {
connection = BaseDao.getConnection();
list = userDao.getUserList(connection,queryUserName,queryUserrRole,currentPage,pageSize);
} catch (SQLException e) {
e.printStackTrace();
}finally {
BaseDao.closeResource(connection,null,null);
}
return list;
}
2.6.3 角色查询
因为角色属于Role而非User,故需要重新在dao包和service包下创建role类。
dao层代码:
public List<Role> getRoleList(Connection connection) {
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
ArrayList<Role> roles = null;
if (connection != null) {
try {
roles = new ArrayList<>();
Object[] params = {};
String sql = "select * from smbms_role";
resultSet = BaseDao.execute(connection, sql, params, resultSet, preparedStatement);
while (resultSet.next()){
Role _role = new Role();
_role.setRoleName(resultSet.getString("roleName"));
_role.setId(resultSet.getInt("id"));
_role.setRoleCode(resultSet.getString("roleCode"));
roles.add(_role);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
BaseDao.closeResource(null,resultSet,preparedStatement);
}
}
return roles;
}
service层代码:
public class RoleServcieImpl implements RoleService {
private RoleDao roleDao;
public RoleServcieImpl() {
roleDao = new RoleDaoImpl();
}
@Override
public List<Role> getRoleList() {
Connection connection = null;
List<Role> roleList = null;
connection = BaseDao.getConnection();
roleList = roleDao.getRoleList(connection);
BaseDao.closeResource(connection,null,null);
return roleList;
}
}
2.6.4 Servlet层
servlet层的功能是将三条线汇总,处理三条线的功能。建议先直接调用service层的方法,然后根据参数来补充数据。需要注意的是要对前端传输进来的数据进行校验。
public void query(HttpServletRequest req, HttpServletResponse resp){
//从前端获取数据
String queryname = req.getParameter("queryname");
//因为下拉框的特性是未选择的话传入的值为null,因此需要赋初值0,把前端接收的变量用temp代替
String temp = req.getParameter("queryUserRole");
int queryUserRole = 0;
//页面也是同理
String pageIndex = req.getParameter("pageIndex");
int currentPageNo = 1;
//定义页面显示的数据大小
int pageSize = 5;
UserServiceImpl userService = new UserServiceImpl();
if (queryname == null){
queryname = "";
}
//确认前端传输的值不是null或空才将结果赋值给queryUserRole
//否则结果为默认值0
if (temp != null && !temp.equals("")){
queryUserRole = Integer.parseInt(temp);
}
//确认前端传输的值不是null或空才将结果赋值给currentPageNo
//否则结果为默认值1
if (pageIndex != null){
currentPageNo = Integer.parseInt(pageIndex);
}
//获取用户总数
int total = userService.getUserCount(queryname, queryUserRole);
List<User> userList = null;
//总页数支持
PageSupport pageSupport = new PageSupport();
pageSupport.setCurrentPageNo(currentPageNo);
pageSupport.setPageSize(pageSize);
pageSupport.setTotalCount(total);
//设置总页数
int totalPageCount = ((int)(total/pageSize))+1;
//控制首页和尾页
if (currentPageNo < 1){
currentPageNo = 1;
}else if (currentPageNo > totalPageCount){
currentPageNo = totalPageCount;
}
//获取用户列表展示
userList = userService.getUserList(queryname, queryUserRole, currentPageNo, pageSize);
req.setAttribute("userList",userList);
//获取角色列表
RoleServcieImpl roleServcie = new RoleServcieImpl();
List<Role> roleList = roleServcie.getRoleList();
req.setAttribute("roleList",roleList);
req.setAttribute("totalCount",total);
req.setAttribute("currentPageNo",currentPageNo);
req.setAttribute("totalPageCount",totalPageCount);
req.setAttribute("queryUserName",queryname);
req.setAttribute("queryUserRole",queryUserRole);
try {
req.getRequestDispatcher("userlist.jsp").forward(req,resp);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}