SMBMS超市訂單管理系統(一)項目搭建准備工作
1 項目架構
2 數據庫設計
3 項目搭建
3.1 項目如何搭建?
-
是否使用maven?
- 使用maven要去網上找依賴
- 不使用maven要自己手動導jar包
為了方便,這個項目使用maven搭建
3.2 創建項目
- 使用maven模板創建一個maven項目
3.2.1 補全maven項目結構
3.2.2 更新WEB.XML的配置版本
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
</web-app>
3.2.3 清理pom.xml
只需要保留GAV+項目的打包方式即可
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.thhh</groupId>
<artifactId>smbms</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
</project>
3.3 配置Tomcat
4 測試項目是否搭建成功
測試通過!項目結構搭建完成!!!
5 導入依賴
用什么導入什么,不一定要一次性全部導入
<dependencies>
<!--servlet依賴-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!--JSP依賴-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
</dependency>
<!--數據庫連接依賴-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--JSTL標簽-->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!--JSTL標簽的依賴-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
6 創建項目包結構
- 首先創建一個總的包,命名"com.公司名"
- 再創建這個項目會使用的包
7 創建實體類/JavaBean/ORM映射
- 使用IDEA連接數據庫,按照數據庫中的表的結構來創建實體類
數據庫中有5張表,但是地址表沒有什么用處,所以我們暫時不創建地址表對用的實體類
8 編寫數據庫操作的基礎公共類BaseDao
- 數據庫配置文件,這是一個資源文件,應該創建在maven項目的resources文件中
DRIVER=com.mysql.jdbc.Driver
URL=jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=utf-8
USERNAME=root
PASSWORD=123
- 使用靜態代碼塊實現初始化參數
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
static {//靜態代碼塊,在調用這個類的地方優先自動執行
//讀取配置文件
//1、創建properties對象
Properties properties = new Properties();
//2、通過類加載器加載資源文件為字節輸入流
InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
USERNAME = properties.getProperty("USERNAME");
PASSWORD = properties.getProperty("PASSWORD");
}
- 編寫數據庫操作的公共方法
package com.thhh.dao;
/**
* 注意理解這個類中的方法之所以要傳入這些數據庫操縱對象是因為為了統一的關閉資源
* 而傳入的對象中可以都是null,具體的對象獲取在方法里面進行;也可以只有conn實例化,其他對象的實例化同樣放在具體的方法里進行
*/
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//操作數據庫的公共類
public class BaseDao {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
//靜態代碼塊用於初始化JDBC4大參數,且靜態代碼塊只會在第一次調用這個類的時候執行一次
static {//靜態代碼塊,在調用這個類的地方優先自動執行
//讀取配置文件
//1、創建properties對象
Properties properties = new Properties();
//2、通過類加載器加載資源文件為字節輸入流
InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
USERNAME = properties.getProperty("USERNAME");
PASSWORD = properties.getProperty("PASSWORD");
}
//1、編寫獲取數據庫的連接對象的公共方法
public static Connection getConnection(){
Connection conn= null;
try {
//1、加載驅動類
Class.forName(DRIVER);
//2、獲取連接對象
conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//2、編寫查詢公共方法 —— 注意查詢的結果返回為ResultSet結果集
/**
* 用於查詢數據的公共方法,注意:使用發送SQL語句的對象為PreparedStatement
* @param sql:查詢的sql語句,由前端傳遞
* @param params:sql語句中占位符的值
*
*===============這下面的3個參數之所以在調用的時候傳遞原因就在於這3個都是資源,我們需要關閉,如果我們直接在這個方法里獲取資源對象的話,那么我們就應該在這個方法中關閉資源===============
*===============但是調用處還在等待這個方法返回結果集,所以我們不應該在這個地方獲取這3個對象,而應該由調用出傳遞,這樣可以統一管理和關閉資源===============
*
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @param rs:返回的結果集,和pstmt只是傳入null的引用。這個對象真正的實例化放在這個方法里面
*
* @return:返回查詢到的結果集
*/
public static ResultSet executeQuery(String sql,Object[] params,Connection conn,PreparedStatement pstmt,ResultSet rs){
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
rs = pstmt.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return rs;
}
//3、編寫修改公共方法
/**
* 用於修改數據的公共方法,注意:使用發送SQL語句的對象為PreparedStatement
* @param sql:修改數據的sql語句模板
* @param params:模板中占位符對應的值
*
* =====下面兩個對象需要調用出傳入也是為了統一管理和關閉資源=====
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
*
* @return 返回受影響的行數
*/
public static int executeUpdate(String sql,Object[] params,Connection conn,PreparedStatement pstmt){
int result = 0;
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i< params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
result = pstmt.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return result;
}
//4、編寫關閉資源公共方法
/**
* 關閉資源
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @param rs:返回的結果集,和pstmt只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @return:返回關閉資源的結果
*
* 注意:關閉資源的時候要倒着關
*/
public static boolean close(Connection conn,PreparedStatement pstmt,ResultSet rs){
boolean flag = true;
if (rs!=null){
try {
rs.close();
rs = null;//讓這個變量為null,gc就會自動對其進行回收
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;//關閉失敗就將flag設置false
}
}
if (pstmt!=null){
try {
pstmt.close();
pstmt = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
if (conn!=null){
try {
conn.close();
conn = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
return flag;//返回關閉結果
}
}
9 編寫字符編碼過濾器
//編寫過濾器
package com.thhh.filter;
import javax.servlet.*;
import java.io.IOException;
public class CharacterEncoding implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
chain.doFilter(request,response);
}
public void destroy() {
}
}
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<!--注冊字符編碼過濾器-->
<filter>
<filter-name>CharacterEncoding</filter-name>
<filter-class>com.thhh.filter.CharacterEncoding</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
10 導入靜態資源
包括HTML、CSS、JS等,注意:這些資源都是網站的,所以我們應該將這些資源放在webapp下面
通過以上的步驟,一個WEB項目的搭建工作就算基本完成了
SMBMS(二)登陸功能實現
頁面實現邏輯/流程
1 編寫前端頁面
前端頁面直接使用已有的,我們主要編寫網頁背后的執行代碼
<%@ 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"/>
<script type="text/javascript">
/* if(top.location!=self.location){
top.location=self.location;
} */
</script>
</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" name="actionForm" id="actionForm"
method="post">
<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>
2 設置首頁
<!--設置歡迎頁/首頁-->
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
3 編寫Dao層用戶登陸的接口
- 創建一個UserDao接口,然后我們按照"面向接口編程"的原則去接口UserDaoImpl中實現這個接口中的方法
- 使用面向接口編程的好處就在與我們在接口中只需要定義方法,而不需要實現方法,整個結構和思路都很清晰
- 其次就是將設計和實現分離,保證了設計專注於設計,實現專注於實現
package com.thhh.dao.user;
import com.thhh.pojo.User;
import java.sql.Connection;
public interface UserDao {
/**
* 得到要進行登陸的用戶
* @param conn:數據庫連接對象
* @param userCode:通過用戶的用戶名userCode查詢用戶數據
* @return
*/
public User getLoginUserInfo(Connection conn,String userCode);
}
4 編寫Dao接口的實現類
package com.thhh.dao.user;
import com.thhh.dao.BaseDao;
import com.thhh.pojo.User;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDaoImpl implements UserDao{
@Override
public User getLoginUserInfo(Connection conn, String userCode) {
PreparedStatement pstmt = null;
ResultSet rs = null;
User user = null;
if (conn!=null){
String sql = "SELECT * FROM smbms_user WHERE userCode = ?";
Object[] params = {userCode};
rs = BaseDao.executeQuery(sql,params,conn,pstmt,rs);//調用項目搭建階段准備的公共查詢方法
try {
while (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"));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.close(null,pstmt,rs);//因為數據庫的連接可能不只是這一個操作,所以我們不應該做完一件事就把數據庫連接對象銷毀,所以conn處傳的null
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return user;
}
}
5 編寫業務層接口
package com.thhh.service.user;
import com.thhh.pojo.User;
public interface UserService {
/**
* 用戶登陸身份驗證
* @param userCode:用戶賬號
* @param userPassword:用戶密碼,注意,密碼判斷我們在service層進行;
* 在Dao層只是簡單的操作數據庫,沒有其他的邏輯代碼;在servlet層中只是接收和轉發請求以及控制視圖跳轉
* 而對於業務層(service)就是用來實現業務邏輯代碼的
* @return
*/
public User login(String userCode,String userPassword);
}
6 業務層實現類
package com.thhh.service.user;
/**
* 業務層主要就是編寫業務代碼,在編寫業務代碼的時候經常會調用數據庫
* 所以在業務層中需要使用到我們一開始編寫好的DAO的代碼
*/
import com.thhh.dao.BaseDao;
import com.thhh.dao.user.UserDao;
import com.thhh.pojo.User;
import org.junit.Test;
import java.sql.Connection;
public class UserServiceImpl implements UserService{
private UserDao userDao;//業務層需要使用Dao,所以直接將Dao作為一個成員變量來使用
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;//在業務層被實例化的時候就讓它得到Dao對象,后面就可以直接去用
}
@Override
public User login(String userCode, String userPassword) {
Connection conn = null;
User user = null;
conn = BaseDao.getConnection();//獲取數據庫連接對象
//通過業務層調用Dao層
user = userDao.getLoginUserInfo(conn,userCode);//調用userDao中的獲取用戶信息的方法
BaseDao.close(conn,null,null);
return user;
}
}
測試是否能夠成功運行
@Test
public void test(){
UserServiceImpl userService = new UserServiceImpl();
User admin = userService.login("admin","12bfasbka");
System.out.println(admin.getUserPassword());
}
7 編寫servlet
首先導入我們也用到的JSP頁面
其中frame.jsp是登陸成功之后跳轉的頁面,而common中是頁面的頭部和底部
- frame.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@include file="/jsp/common/head.jsp"%>
<div class="right">
<img class="wColck" src="${pageContext.request.contextPath }/images/clock.jpg" alt=""/>
<div class="wFont">
<h2>${userSession.userName }</h2>
<p>歡迎來到超市訂單管理系統!</p>
</div>
</div>
</section>
<%@include file="/jsp/common/foot.jsp" %>
- head.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>超市訂單管理系統</title>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/style.css" />
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/public.css" />
</head>
<body>
<!--頭部-->
<header class="publicHeader">
<h1>超市訂單管理系統</h1>
<div class="publicHeaderR">
<p><span>下午好!</span><span style="color: #fff21b"> ${userSession.userName }</span> , 歡迎你!</p>
<a href="${pageContext.request.contextPath }/jsp/logout.do">退出</a>
</div>
</header>
<!--時間-->
<section class="publicTime">
<span id="time">2015年1月1日 11:11 星期一</span>
<a href="#">溫馨提示:為了能正常瀏覽,請使用高版本瀏覽器!(IE10+)</a>
</section>
<!--主體內容-->
<section class="publicMian ">
<div class="left">
<h2 class="leftH2"><span class="span1"></span>功能列表 <span></span></h2>
<nav>
<ul class="list">
<li ><a href="${pageContext.request.contextPath }/jsp/bill.do?method=query">訂單管理</a></li>
<li><a href="${pageContext.request.contextPath }/jsp/provider.do?method=query">供應商管理</a></li>
<li><a href="${pageContext.request.contextPath }/jsp/user.do?method=query">用戶管理</a></li>
<li><a href="${pageContext.request.contextPath }/jsp/pwdmodify.jsp">密碼修改</a></li>
<li><a href="${pageContext.request.contextPath }/jsp/logout.do">退出系統</a></li>
</ul>
</nav>
</div>
<input type="hidden" id="path" name="path" value="${pageContext.request.contextPath }"/>
<input type="hidden" id="referer" name="referer" value="<%=request.getHeader("Referer")%>"/>
- foot.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<footer class="footer">
超市訂單管理系統
</footer>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/time.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/common.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/calendar/WdatePicker.js"></script>
</body>
</html>
注意:我們在編寫servlet和注冊servlet的時候一定要注意我們使用的前端頁面上面寫的地址,為了不出錯最后時與前端頁面上寫的地址保持一致;其次就是注意前端使用的JSP內置對象和EL表達式,我們在后端需要使用對應的變量名,否則是取不到值的
7.1 servlet編寫
package com.thhh.servlet.user;
import com.thhh.pojo.User;
import com.thhh.service.user.UserService;
import com.thhh.service.user.UserServiceImpl;
import com.thhh.utils.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 控制層調用業務層
*/
public class LoginServlet extends HttpServlet{
private UserService userService = null;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、獲取前端傳過來的用戶名+密碼
String userCode = req.getParameter("userCode");
String userPassword = req.getParameter("userPassword");
//2、調用業務層,將數據庫中查出來的密碼和用戶的輸入進行對比
this.userService = new UserServiceImpl();
User user = userService.login(userCode,userPassword);//已經把用戶查到了
//3、判斷返回是否為null
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);
}
/* if ()
user.getUserPassword()*/
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
8 注冊servlet
<!--注冊登陸的servlet-->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.thhh.servlet.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login.do</url-pattern>
</servlet-mapping>
注意:這里的映射與我們使用的前端頁面表單提交的action要保持一致
9 測試功能,確保上面的代碼正確
測試完成!
SMBMS(三) 登陸功能優化
1 注銷功能
思路:移除session對象+返回登陸頁面
1.1 編寫servlet
package com.thhh.servlet.user;
import com.thhh.utils.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().removeAttribute(Constants.USER_SESSION);//移除用戶session
resp.sendRedirect(req.getContextPath()+"/login.jsp");//重定向回登陸頁面
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
1.2 注冊servlet
<!--注冊注銷servlet-->
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>com.thhh.servlet.user.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/jsp/logout.do</url-pattern>
</servlet-mapping>
2 登陸攔截優化
- 編寫過濾器
package com.thhh.filter;
import com.thhh.pojo.User;
import com.thhh.utils.Constants;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SysFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;//父類強轉子類,用於獲取session
HttpServletResponse response = (HttpServletResponse) resp;//父類強轉子類,用於重定向
User user = (User) request.getSession().getAttribute(Constants.USER_SESSION);
if (user==null){//用戶處於未登陸狀態
response.sendRedirect(request.getContextPath()+"/error.jsp");//重定向到登陸頁面
}else{
chain.doFilter(req,resp);//過濾器放行
}
}
@Override
public void destroy() {
}
}
- 注冊過濾器
<!--注冊未登錄時請求后台頁面過濾器-->
<filter>
<filter-name>SysFilter</filter-name>
<filter-class>com.thhh.filter.SysFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SysFilter</filter-name>
<url-pattern>/jsp/*</url-pattern>
<!--注意:我們將所有需要登陸之后才能訪問的功能性頁面給都放在了/jsp文件夾下,所以過濾器應該過濾這個文件夾下的請求-->
</filter-mapping>
- 導入error頁面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%--你要訪問的頁面,已經飛往火星!--%>
<%--<a href="javascript:window.history.back(-1);">返回</a>--%>
<h1>請登錄后再訪問該頁面!</h1>
<a href="login.jsp">返回</a>
</body>
</html>
- 測試功能
測試完成!
SMBMS (四) 修改密碼
分析:很明顯,要修改用戶密碼我們還是需要和數據庫交互,那么就還是前面我們寫登陸功能的代碼編寫步驟 —— DAO層、service層、servlet層,前端頁面直接使用現成的,但是注意servlet中使用的地址和servlet的地址映射注意和前端頁面保持一致
為什么要按照DAO層、service層、servlet層,JSP頁面的順序來編寫呢?
原因在上圖展示的很清楚,開發JSP需要填寫servlet在服務器上的映射路徑,開發servlet需要調用service中的方法完成業務邏輯,開發service需要調用Dao中對數據庫的操作來操作數據庫,而只有Dao中使用的JDBC我們是數據庫廠商實現了的,所以我們可以直接使用;所以為了開發的完整性,我們就應該從Dao開始-->service-->servlet-->JSP
分析實現步驟/模塊功能划分(很重要)
只有我們先想好了怎么做,然后再去編寫代碼才會快,且有條不紊,切忌看完要求之后馬上開始寫代碼
1 導入前端素材
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="/jsp/common/head.jsp"%>
<div class="right">
<div class="location">
<strong>你現在所在的位置是:</strong>
<span>密碼修改頁面</span>
</div>
<div class="providerAdd">
<form id="userForm" name="userForm" method="post" action="${pageContext.request.contextPath }/jsp/user.do">
<input type="hidden" name="method" value="savepwd">
<!--div的class 為error是驗證錯誤,ok是驗證成功-->
<div class="info">${message}</div>
<div class="">
<label for="oldPassword">舊密碼:</label>
<input type="password" name="oldpassword" id="oldpassword" value="">
<font color="red"></font>
</div>
<div>
<label for="newPassword">新密碼:</label>
<input type="password" name="newpassword" id="newpassword" value="">
<font color="red"></font>
</div>
<div>
<label for="newPassword">確認新密碼:</label>
<input type="password" name="rnewpassword" id="rnewpassword" value="">
<font color="red"></font>
</div>
<div class="providerAddBtn">
<!--<a href="#">保存</a>-->
<input type="button" name="save" id="save" value="保存" class="input-button">
</div>
</form>
</div>
</div>
</section>
<%@include file="/jsp/common/foot.jsp" %>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/pwdmodify.js"></script>
2 Dao接口
package com.thhh.dao.user;
import com.thhh.pojo.User;
import java.sql.Connection;
public interface UserDao {
/**
* 得到要進行登陸的用戶
* @param conn:數據庫連接對象
* @param userCode:通過用戶的用戶名userCode查詢用戶數據
* @return
*/
public User getLoginUserInfo(Connection conn,String userCode);
/**
* 修改用戶密碼
* @param conn:數據庫連接對象
* @param id:修改密碼的用戶的ID
* @param newPwd:新密碼
* @return:影響行數
*/
public int updatePwd(Connection conn,String newPwd,int id);
}
只需要看方法2
3 Dao接口實現
package com.thhh.dao.user;
import com.thhh.dao.BaseDao;
import com.thhh.pojo.User;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDaoImpl implements UserDao{
//1、獲取要進行登陸的用戶對象
@Override
public User getLoginUserInfo(Connection conn, String userCode) {
PreparedStatement pstmt = null;
ResultSet rs = null;
User user = null;
if (conn!=null){
String sql = "SELECT * FROM smbms_user WHERE userCode = ?";
Object[] params = {userCode};
rs = BaseDao.executeQuery(sql,params,conn,pstmt,rs);//調用項目搭建階段准備的公共查詢方法
try {
while (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"));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.close(null,pstmt,rs);//因為數據庫的連接可能不只是這一個操作,所以我們不應該做完一件事就把數據庫連接對象銷毀,所以conn處傳的null
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return user;
}
//2、修改用戶密碼
@Override
public int updatePwd(Connection conn, String newPwd, int id) {
PreparedStatement pstmt = null;
int rs = 0;
User user = null;
if (conn!=null){
String sql = "UPDATE smbms_user SET userPassword = ? WHERE id = ?";
Object[] params = {newPwd,id};//按照sql語句的占位符的順序來傳遞數據,使用的時候需要注意
rs = BaseDao.executeUpdate(sql,params,conn,pstmt);
BaseDao.close(null,pstmt,null);//把這次使用的sql語句發送器關掉,連接不要關,service還可能有其他用
}
return rs;
}
}
4 service接口
package com.thhh.service.user;
import com.thhh.pojo.User;
import java.sql.Connection;
public interface UserService {
/**
* 1、獲取登陸用戶對象,對用戶登陸身份進行驗證
* @param userCode:用戶賬號
* @param userPassword:用戶密碼,注意,密碼判斷我們在service層進行;
* 在Dao層只是簡單的操作數據庫,沒有其他的邏輯代碼;在servlet層中只是接收和轉發請求以及控制視圖跳轉
* 而對於業務層(service)就是用來實現業務邏輯代碼的
* @return
*/
public User login(String userCode,String userPassword);
/**
* 2、根據用戶ID修改用戶密碼
* @param newPwd:新密碼
* @param id:用戶ID
* @return
*/
public boolean updatePwd(String newPwd, int id);
}
只看方法2
5 service接口實現
package com.thhh.service.user;
/**
* 業務層主要就是編寫業務代碼,在編寫業務代碼的時候經常會調用數據庫
* 所以在業務層中需要使用到我們一開始編寫好的DAO的代碼
*/
import com.thhh.dao.BaseDao;
import com.thhh.dao.user.UserDao;
import com.thhh.dao.user.UserDaoImpl;
import com.thhh.pojo.User;
import java.sql.Connection;
public class UserServiceImpl implements UserService{
private UserDao userDao;//業務層需要使用Dao,所以直接將Dao作為一個成員變量來使用
public UserServiceImpl() {
this.userDao = new UserDaoImpl();//在業務層被實例化的時候就讓它得到Dao對象,后面就可以直接去用
}
/**
* 1、判斷登陸用戶的用戶名+密碼是否合法,並將用戶對象返回
* @param userCode:用戶賬號
* @param userPassword:用戶密碼,注意,密碼判斷我們在service層進行;
* 在Dao層只是簡單的操作數據庫,沒有其他的邏輯代碼;在servlet層中只是接收和轉發請求以及控制視圖跳轉
* 而對於業務層(service)就是用來實現業務邏輯代碼的
* @return
*/
@Override
public User login(String userCode, String userPassword) {
Connection conn = null;
User user = null;
User error = null;
conn = BaseDao.getConnection();//獲取數據庫連接對象
//通過業務層調用Dao層
user = userDao.getLoginUserInfo(conn,userCode);//調用userDao中的獲取用戶信息的方法
BaseDao.close(conn,null,null);
if (user.getUserPassword().equals(userPassword)){
return user;
}
return error;
}
/**
* 2、通過已經登陸用戶的ID修改新密碼,並將數據庫中受影響的行數返回
* @param newPwd:新密碼
* @param id:用戶ID
* @return
*/
@Override
public boolean updatePwd(String newPwd, int id) {
Connection conn = null;
int rs = 0;
boolean flag = false;
conn = BaseDao.getConnection();//獲取數據庫連接對象
//通過業務層調用Dao層
if (userDao.updatePwd(conn,newPwd,id)>0){//數據庫修改成功
flag = true;
}
BaseDao.close(conn,null,null);
return flag;
}
}
只看方法2
6 servlet編寫
package com.thhh.servlet.user;
import com.mysql.jdbc.StringUtils;
import com.thhh.pojo.User;
import com.thhh.service.user.UserService;
import com.thhh.service.user.UserServiceImpl;
import com.thhh.utils.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//實現servlet復用
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
boolean flag = false;
Object user = req.getSession().getAttribute(Constants.USER_SESSION);
String newpassword = req.getParameter("newpassword");
if (user!=null && !StringUtils.isNullOrEmpty(newpassword)){//獲取到了這個用戶對象且獲取到的新密碼不為空
UserService userService = new UserServiceImpl();
flag = userService.updatePwd(newpassword,((User)user).getId());//servlet調用業務層
if (flag){//修改成功
req.setAttribute("message","密碼修改成功!請使用新密碼重新登陸");
//移除用戶的session,利用過濾器阻止用戶再進行操作,直接跳轉error.jsp頁面
req.getSession().removeAttribute(Constants.USER_SESSION);
}else{
req.setAttribute("message","密碼修改失敗");
}
}else {
//用戶可以進行密碼修改,則user一定不是null,所以跳入這個分支的原因一定是newpassword = NULL
req.setAttribute("message","密碼設置有誤,請重新輸入!");
}
//無論是修改成功還是失敗,都重定向到密碼修改頁面,就是在刷新頁面,否則我們設置在req中的message屬性不會被前端讀到
req.getRequestDispatcher("pwdmodify.jsp").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
7 注冊servlet
<!--注冊用戶修改密碼的servlet-->
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.thhh.servlet.user.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>/jsp/user.do</url-pattern>
</servlet-mapping>
8 測試
bug 1:
//1、在編寫servlet的時候,要判斷前端傳過來的新密碼是否為空,這里ZB用了一個工具類,但是這個工具類是isNullOrEmpty,即它的作用判斷"是空",所以使用的時候注意在前面加上一個"!"
//或者我們就是要常見的方法:newpassword!=null&&newpassword.length!=0
if (user!=null && !StringUtils.isNullOrEmpty(newpassword))
bug 2:
//2、在編寫BaseDao即基本公共數據庫操作方法的時候,設置PreparedStatement對象中sql占位符的值時要注意
//PreparedStatement的占位符index從1開始,而數組的下標從0開始,所以我們使用的i=1,但是要注意控制循環次數的時候使用的是params.length,所以我們需要取"=",否則數組中的參數是取不完的,取不完就會出現SQL錯誤
for (int i=1;i<= params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
bug 3:
//前端頁面上,編寫的時候要求輸入舊密碼,但是實際測試的時候輸入舊密碼有BUG,我們直接不使用輸入舊密碼,使用新密碼+重復新密碼輸入框來修改密碼
//但是前端使用的JS控制了提交表單的按鈕,即需要3個輸入框輸入都滿足要求的時候才能提交表單數據,所以我們需要把判斷舊密碼輸入框的判斷語句注釋了
//這樣才能只通過新密碼+重復新密碼實現密碼修改
saveBtn.on("click",function(){
oldpassword.blur();
newpassword.blur();
rnewpassword.blur();
// oldpassword.attr("validateStatus") == "true"
// &&
if( newpassword.attr("validateStatus") == "true"
&& rnewpassword.attr("validateStatus") == "true"){
if(confirm("確定要修改密碼?")){
$("#userForm").submit();
}
}
});
9 優化servlet代碼,實現servlet復用
通過測試,功能完全相同,且修改密碼正確!
SMBMS (五) 使用Ajax優化密碼修改功能
在前面的密碼修改中,我們避開使用了前端素材中驗證舊密碼的Ajax功能,是因為要把Ajax單獨拿出來講
前面實現的密碼修改功能是直接輸入兩遍新密碼進行的修改,這顯然是不安全的,所以我們應該在修改密碼的時候加入驗證舊密碼的操作,而這個操作根據前端素材就需要使用到Ajax了
1 什么是Ajax
- AJAX = Asynchronous JavaScript and XML(異步的 JavaScript 和 XML)
- AJAX 不是新的編程語言,而是一種使用現有標准的新方法
- AJAX 是在不重新加載整個頁面的情況下,與服務器交換數據並更新部分網頁的藝術
- 通過在后台與服務器進行少量數據交換,AJAX 可以使網頁實現異步更新
- 這意味着可以在不重新加載整個網頁的情況下,對網頁的某部分進行更新
- 傳統的網頁(不使用 AJAX)如果需要更新內容,必需重載整個網頁面
2 怎么實現舊密碼驗證?
直接的做法就是去數據庫中查找,但是這樣的畫我們又要寫一條線:Dao開始-->service-->servlet-->JSP
我們可以采取一種很簡單的方式進行:在用戶登陸的時候我們將整個用戶對象都存儲到session中了,所以我們可以利用Ajax的異步請求+session中的user對象實現舊密碼的驗證
做法就是在后端復用的servlet中再定義一個方法,這個方法專門負責對比Ajax傳遞給后端的舊密碼是否和session中User對象的密碼一致,並通過對比的情況和結果來返回JSON數據給前端
3 編寫servlet
oldpassword.on("blur",function(){
$.ajax({
type:"GET",
url:path+"/jsp/user.do",//Ajax提交的URL,可見和我們修改密碼的JSP頁面的表單提交的URL一樣,所以我們應該復用servlet
data:{method:"pwdmodify",oldpassword:oldpassword.val()},//提交的參數
//上面兩句話等價於 = path+/jsp/user.do ? method=pwdmodify & oldpassword=oldpassword.val()
dataType:"json",
success:function(data){
if(data.result == "true"){//舊密碼正確
validateTip(oldpassword.next(),{"color":"green"},imgYes,true);
}else if(data.result == "false"){//舊密碼輸入不正確
validateTip(oldpassword.next(),{"color":"red"},imgNo + " 原密碼輸入不正確",false);
}else if(data.result == "sessionerror"){//當前用戶session過期,請重新登錄
validateTip(oldpassword.next(),{"color":"red"},imgNo + " 當前用戶session過期,請重新登錄",false);
}else if(data.result == "error"){//舊密碼輸入為空
validateTip(oldpassword.next(),{"color":"red"},imgNo + " 請輸入舊密碼",false);
}
},
error:function(data){
//請求出錯
validateTip(oldpassword.next(),{"color":"red"},imgNo + " 請求錯誤",false);
}
});
//2.驗證舊密碼
//直接與session中的user對象的密碼進行對比即可,不用再去查數據庫
public void pwdModify(HttpServletRequest req, HttpServletResponse resp){
Object user = req.getSession().getAttribute(Constants.USER_SESSION);
String oldpassword = req.getParameter("oldpassword");
//使用Map集合存儲返回給前端的數據
Map<String,String> resultMap = new HashMap<String, String>();
if (user==null){//session中的user對象失效了
resultMap.put("result","sessionerror");
}else if (StringUtils.isNullOrEmpty(oldpassword)){//輸入的密碼為空
resultMap.put("result","error");
}else {
String userPassword = ((User)user).getUserPassword();
if (userPassword.equals(oldpassword)){//輸入的密碼匹配
resultMap.put("result","true");
}else {//輸入的密碼不匹配
resultMap.put("result","false");
}
}
//將Map集合中的數據轉為JSON格式傳輸給前端
try {
resp.setContentType("application/JSON");//設置返回數據是一個JSON,這樣瀏覽器拿到這個數據之后就會按照JSON的數據格式來解析它
PrintWriter writer = resp.getWriter();//獲取輸出流
writer.write(JSONArray.toJSONString(resultMap));//使用阿里巴巴提供的一個JSON工具類,直接將其他數據格式的數據轉為JSON數據格式,然后直接將其寫出去
writer.flush();//刷新緩沖區
writer.close();//關閉資源
} catch (IOException e) {
e.printStackTrace();
}
}
4 測試
(依舊愛ZS)
SMBMS (六) 用戶管理功能實現
1 導入分頁工具類
查看一下這個工具類的源碼
package com.lvcong.util;
public class PageSupport {
//當前頁碼-來自於用戶輸入
private int currentPageNo = 1;
//總數量(表)
private int totalCount = 0;
//頁面容量
private int pageSize = 0;
//總頁數-totalCount/pageSize(+1)
private int totalPageCount = 1;
public int getCurrentPageNo() {
return currentPageNo;
}
public void setCurrentPageNo(int currentPageNo) {
if (currentPageNo > 0) {
this.currentPageNo = currentPageNo;
}
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
if (totalCount > 0) {
this.totalCount = totalCount;
//設置總頁數
this.setTotalPageCountByRs();
}
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
if (pageSize > 0) {
this.pageSize = pageSize;
}
}
public int getTotalPageCount() {
return totalPageCount;
}
public void setTotalPageCount(int totalPageCount) {
this.totalPageCount = totalPageCount;
}
public void setTotalPageCountByRs() {
if (this.totalCount % this.pageSize == 0) {
this.totalPageCount = this.totalCount / this.pageSize;
} else if (this.totalCount % this.pageSize > 0) {
this.totalPageCount = this.totalCount / this.pageSize + 1;
} else {
this.totalPageCount = 0;
}
}
}
OOP的3大特性:封裝、繼承、多態,其中封裝 = 屬性私有+屬性的get/set() + 在set中限制一些不安全的賦值操作(這一步可以留到service層再做,但是在封裝的時候做更好,這樣減少了service層的代碼,且體現了封裝的特性)
2 用戶列表頁面導入
首先是HTML文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
</script>
</head>
<body>
<div class="page-bar">
<ul class="page-num-ul clearfix">
<li>共${param.totalCount }條記錄 ${param.currentPageNo }/${param.totalPageCount }頁</li>
<c:if test="${param.currentPageNo > 1}">
<a href="javascript:page_nav(document.forms[0],1);">首頁</a>
<a href="javascript:page_nav(document.forms[0],${param.currentPageNo-1});">上一頁</a>
</c:if>
<c:if test="${param.currentPageNo < param.totalPageCount }">
<a href="javascript:page_nav(document.forms[0],${param.currentPageNo+1 });">下一頁</a>
<a href="javascript:page_nav(document.forms[0],${param.totalPageCount });">最后一頁</a>
</c:if>
</ul>
<span class="page-go-form"><label>跳轉至</label>
<input type="text" name="inputPage" id="inputPage" class="page-key" />頁
<button type="button" class="page-btn" onClick='jump_to(document.forms[0],document.getElementById("inputPage").value)'>GO</button>
</span>
</div>
</body>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/rollpage.js"></script>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="/jsp/common/head.jsp"%>
<div class="right">
<div class="location">
<strong>你現在所在的位置是:</strong>
<span>用戶管理頁面</span>
</div>
<div class="search">
<form method="get" action="${pageContext.request.contextPath }/jsp/user.do">
<input name="method" value="query" class="input-text" type="hidden">
<span>用戶名:</span>
<input name="queryname" class="input-text" type="text" value="${queryUserName }">
<span>用戶角色:</span>
<select name="queryUserRole">
<c:if test="${roleList != null }">
<option value="0">--請選擇--</option>
<c:forEach var="role" items="${roleList}">
<option <c:if test="${role.id == queryUserRole }">selected="selected"</c:if>
value="${role.id}">${role.roleName}</option>
</c:forEach>
</c:if>
</select>
<input type="hidden" name="pageIndex" value="1"/>
<input value="查 詢" type="submit" id="searchbutton">
<a href="${pageContext.request.contextPath}/jsp/useradd.jsp" >添加用戶</a>
</form>
</div>
<!--用戶-->
<table class="providerTable" cellpadding="0" cellspacing="0">
<tr class="firstTr">
<th width="10%">用戶編碼</th>
<th width="20%">用戶名稱</th>
<th width="10%">性別</th>
<th width="10%">年齡</th>
<th width="10%">電話</th>
<th width="10%">用戶角色</th>
<th width="30%">操作</th>
</tr>
<c:forEach var="user" items="${userList }" varStatus="status">
<tr>
<td>
<span>${user.userCode }</span>
</td>
<td>
<span>${user.userName }</span>
</td>
<td>
<span>
<c:if test="${user.gender==1}">男</c:if>
<c:if test="${user.gender==2}">女</c:if>
</span>
</td>
<td>
<span>${user.age}</span>
</td>
<td>
<span>${user.phone}</span>
</td>
<td>
<span>${user.userRoleName}</span>
</td>
<td>
<span><a class="viewUser" href="javascript:;" userid=${user.id } username=${user.userName }><img src="${pageContext.request.contextPath }/images/read.png" alt="查看" title="查看"/></a></span>
<span><a class="modifyUser" href="javascript:;" userid=${user.id } username=${user.userName }><img src="${pageContext.request.contextPath }/images/xiugai.png" alt="修改" title="修改"/></a></span>
<span><a class="deleteUser" href="javascript:;" userid=${user.id } username=${user.userName }><img src="${pageContext.request.contextPath }/images/schu.png" alt="刪除" title="刪除"/></a></span>
</td>
</tr>
</c:forEach>
</table>
<input type="hidden" id="totalPageCount" value="${totalPageCount}"/>
<c:import url="rollpage.jsp">
<c:param name="totalCount" value="${totalCount}"/>
<c:param name="currentPageNo" value="${currentPageNo}"/>
<c:param name="totalPageCount" value="${totalPageCount}"/>
</c:import>
</div>
</section>
<!--點擊刪除按鈕后彈出的頁面-->
<div class="zhezhao"></div>
<div class="remove" id="removeUse">
<div class="removerChid">
<h2>提示</h2>
<div class="removeMain">
<p>你確定要刪除該用戶嗎?</p>
<a href="#" id="yes">確定</a>
<a href="#" id="no">取消</a>
</div>
</div>
</div>
<%@include file="/jsp/common/foot.jsp" %>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/userlist.js"></script>
上述代碼分別rollpage.jsp和userlist.jsp
分析:
- Dao層使用聚合函數COUNT(*)查詢用戶表一共多少條記錄
- service層用於統計總共有多少條數據
- servlet層用於向前端返回總共的數據條數
3 獲取用戶數量
3.1 UserDao接口
因為是獲取用戶的數量,所以和用戶表有關,那這個接口方法就放在前面已經創建好的UserDao中
方法定義的時候需要哪些參數?
查看前端素材:
可見,查詢界面我們需要實現按照用戶名查詢、按照角色名稱查詢和整表查詢
再去看看數據庫中的數據表
聯表查詢的基本SQL語句
SELECT COUNT(1)
FROM smbms_user u,smbms_role r
WHERE u.userRole = r.id;
這是MYSQL的方言版內連接查詢,內連接兩張表的地位平等,只要我們加了WHERE過濾笛卡爾積,那么輸出的結果就只包含有主外鍵關聯的字段
但是從前面的需求我們可以看出,前端素材提供了按照姓名查詢、按照職位查詢和整表查詢,所以我們需要在上面聯表查詢的基礎上再添加一些篩選條件,添加的手段就是使用"AND 條件"來實現
在Java中實現我們可以使用StringBuffer來實現sql語句的拼接
package com.thhh.dao.user;
import com.thhh.pojo.User;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface UserDao {
/**
* 1、得到要進行登陸的用戶
* @param conn:數據庫連接對象
* @param userCode:通過用戶的用戶名userCode查詢用戶數據
* @return
*/
public User getLoginUserInfo(Connection conn,String userCode);
/**
* 2、修改用戶密碼
* @param conn:數據庫連接對象
* @param id:修改密碼的用戶的ID
* @param newPwd:新密碼
* @return:影響行數
*/
public int updatePwd(Connection conn,String newPwd,int id);
/**
* 3、用於獲取數據庫中用戶總數
* @param conn:數據庫連接對象
* @param userName:用戶名,這個參數主要用於按照用戶名稱查詢用戶信息
* @param userRole:用戶角色,這個參數主要用於按照用戶角色查詢用戶信息
* @return :查詢到的行數
*/
public int getUserCount(Connection conn, String userName, int userRole) throws SQLException;
}
只看方法3
3.2 UserDaoImpl接口實現
//3、根據用戶名/角色名獲取用戶總數
@Override
public int getUserCount(Connection conn, String userName, int userRole) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
int count = 0;
if (conn!=null){
StringBuffer sql = new StringBuffer();//使用字符串緩沖區,這樣就可以動態的在sql后面追加AND條件了
sql.append("SELECT COUNT(1) COUNT FROM smbms_user u,smbms_role r WHERE u.userRole = r.id");//基本的聯表查詢SQL語句
List<Object> list = new ArrayList<Object>();//創建一個list來存儲我們要拼接的篩選條件,由於我們不能限制傳入的參數的數據類型,所以泛型指定為object
if (!StringUtils.isNullOrEmpty(userName)){//判斷傳入的用戶名是不是空,如果不是空表明前端指定了按照姓名查詢的參數
sql.append("AND userName like ?");
list.add("%"+userName+"%");
}
if (userRole>0){
sql.append("AND userRole = ?");
list.add(userRole);
}
Object[] params = list.toArray();//獲得BaseDao中為pstmt對象設置參數的參數列表
rs = BaseDao.executeQuery(sql.toString(), params, conn, pstmt, rs);//調用查詢定義好的查詢方法
if (rs.next()){//獲取查詢結果
count = rs.getInt("COUNT");//COUNT是在SQL語句中為查詢結果取的別名
}
BaseDao.close(null,pstmt,rs);//關閉資源
}
return count;
}
注意回顧知識點:
- StringBuffer類:一個專門用來彌補String內容不可修改的類,調用這個類對象的append()可以實現在字符串的末尾加上新字符串且不會產生新的字符串對象(String對象的拼接操作其實在底層就是產生了新的String對象)
- 使用List集合來存儲參數,這樣做的好處就在於我們可以動態的向集合中添加參數,而不是像前面使用數組那樣固定了數組長度;其實集合也是用來彌補數組長度不可修改的缺陷而出現的,使用集合存儲數據即使數據很多也不會產生新的集合對象,而數組不一樣,數組一經創建長度就固定了;而針對上面要實現的需求我們不知道到底前端要進行哪一種操作,所以我們不能定義一個定長的數組,即使按照最大需求量定義數組,雖然可以滿足要求,但是在不能充分使用的時候就是資源的浪費
- 上面使用的動態的拼接SQL語句的做法很明智:既使用了StringBuffer對象控制SQL語句在提交之前可變,又使用了List集合來存儲參數,在提交的時候才將其轉為數組
3.3 UserService接口
package com.thhh.service.user;
import com.thhh.pojo.User;
public interface UserService {
/**
* 1、獲取登陸用戶對象,對用戶登陸身份進行驗證
* @param userCode:用戶賬號
* @param userPassword:用戶密碼,注意,密碼判斷我們在service層進行;
* 在Dao層只是簡單的操作數據庫,沒有其他的邏輯代碼;在servlet層中只是接收和轉發請求以及控制視圖跳轉
* 而對於業務層(service)就是用來實現業務邏輯代碼的
* @return
*/
public User login(String userCode,String userPassword);
/**
* 2、根據用戶ID修改用戶密碼
* @param newPwd:新密碼
* @param id:用戶ID
* @return
*/
public boolean updatePwd(String newPwd, int id);
/**
* 3、獲取用戶總數
* @param userName:按照用戶姓名查,查到的用戶總數
* @param userRole:按照用戶角色查,查到的用戶總數
* @return:返回對應查詢條件查到的用戶總數
*/
public int getUserCount(String userName, int userRole) ;
}
只看方法3
3.4 UserServiceImpl接口實現
/**
* 3、按照條件查詢符合條件的用戶總數
* @param userName:按照用戶姓名查,查到的用戶總數
* @param userRole:按照用戶角色查,查到的用戶總數
* @return
*/
@Override
public int getUserCount(String userName, int userRole) {
Connection conn = null;
int rs = 0;
try {
conn = BaseDao.getConnection();//獲取數據庫連接對象
rs = userDao.getUserCount(conn,userName,userRole);//業務層調用Dao層獲取業務結果
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
BaseDao.close(conn,null,null);//關閉資源
}
return rs;//業務層將業務結果返回給servlet
}
3.5 測試
bug 1:
(獨愛ZS)
4 獲取用戶列表
4.1 UserDao接口
/**
* 4、用戶獲取用戶列表
* @param conn:數據庫連接對象
* ===========后面兩個參數用於條件查詢用戶數據
* @param userName:按照用戶名查找
* @param userRole:按照角色名稱查找
* ===========后面兩個參數用於對按照上面條件查詢出來的結果進行分頁處理
* @param currentPageNo:翻到第多少頁
* @param pageSize:每一頁多少條數據
* @return:返回滿足條件的user對象集合
*/
public List<User> getUserList(Connection conn, String userName, int userRole, int currentPageNo, int pageSize) throws SQLException;
4.2 UserDaoImpl接口實現
//4、獲取滿足條件的用戶對象集合
@Override
public List<User> getUserList(Connection conn, String userName, int userRole, int currentPageNo, int pageSize) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
List<User> userList = null;
if (conn!=null){
userList = new ArrayList<User>();
StringBuffer sql = new StringBuffer();
sql.append("SELECT u.*,r.roleName as userRoleName FROM smbms_user u,smbms_role r WHERE u.userRole = r.id");
List<Object> temp = new ArrayList<Object>();
if (userName!=null){
sql.append(" AND u.userName LIKE ?");
temp.add("%"+userName+"%");
}
if (userRole>0){
sql.append(" AND u.userRole = ?");
temp.add(userRole);
}
sql.append(" ORDER BY u.creationDate DESC LIMIT ?,?");//在sql最后追加一個排序和分頁
//5
//1 5
//2 10
//3 15
currentPageNo = (currentPageNo-1)*pageSize;//減一的原因就是MYSQL分頁的index從0開始
temp.add(currentPageNo);//從哪一個下標開始
temp.add(pageSize);//從currentPageNo連續取幾個
Object[] params = temp.toArray();
rs = BaseDao.executeQuery(sql.toString(),params,conn,pstmt,rs);
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"));//這個屬性是在POJO中新加入的,數據表中沒有
userList.add(_user);//將查到的這個對象分裝為對象並存入List集合中
}
BaseDao.close(null,pstmt,rs);
}
return userList;
}
4.3 UserService接口
/**
* 4、根據用戶名/用戶角色名稱來查詢數據,返回一個User對象集合,而currentPageNo+pageSize用於前端做分頁操作
* @param userName
* @param userRole
* @param currentPageNo
* @param pageSize
* @return:滿足條件+limit的User對象集合
*/
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize);
4.4 Userservice接口實現
/**
* 4、根據用戶名/用戶角色名稱來查詢數據,返回一個User對象集合,而currentPageNo+pageSize用於前端做分頁操作
* @param userName
* @param userRole
* @param currentPageNo
* @param pageSize
* @return:滿足條件+limit的User對象集合
*/
@Override
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize) {
Connection conn = null;
List<User> userList = null;
try {
conn = BaseDao.getConnection();//獲取連接
userList = userDao.getUserList(conn,userName,userRole,currentPageNo,pageSize);//業務層調用Dao層獲取業務結果
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
BaseDao.close(conn,null, null);//關閉資源
}
return userList;//業務層將業務結果返回給servlet
}
4.5 測試
@Test
public void test(){
List userList = new UserServiceImpl().getUserList(null,0,2,5);
//(3-1)*5 = 10,所以展示的是10~14條數據,但是一共只有12條,注意:MYSQL中結果index從0開始
for (Object o : userList) {
System.out.println(((User)o).getUserName());
}
}
測試完成!
SMBMS(七)獲取角色列表
1. RoleDao接口
package com.thhh.dao.role;
import com.thhh.pojo.Role;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public interface RoleDao {
/**
* 1、獲取角色列表
* @param conn:數據庫連接對象
* @return:返回的結構集
* @throws SQLException
*/
public List<Role> getRoleList(Connection conn) throws SQLException;
}
2 RoleDaoImpl接口實現
package com.thhh.dao.role;
import com.thhh.dao.BaseDao;
import com.thhh.pojo.Role;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class RoleDaoImpl implements RoleDao{
//1、獲取角色列表
@Override
public List<Role> getRoleList(Connection conn) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
List<Role> list = null;//存儲角色對象的集合
if (conn!=null){
list = new ArrayList<Role>();
String sql = "SELECT * FROM smbms_role";//直接寫死,不用參數
Object[] params = {};
rs = BaseDao.executeQuery(sql,params,conn,pstmt,rs);
while (rs.next()){
Role role = new Role();
role.setId(rs.getInt("id"));
role.setRoleCode(rs.getString("roleCode"));
role.setRoleName(rs.getString("roleName"));
list.add(role);
}
BaseDao.close(null,pstmt,rs);
}
return list;
}
}
3 RoleService接口
package com.thhh.service.role;
import com.thhh.pojo.Role;
import java.util.List;
public interface RoleService {
//1、獲取角色列表
public List<Role> getRoleList();
}
4 RoleService接口實現
package com.thhh.service.role;
import com.thhh.dao.BaseDao;
import com.thhh.dao.role.RoleDao;
import com.thhh.dao.role.RoleDaoImpl;
import com.thhh.pojo.Role;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class RoleServiceImpl implements RoleService{
private RoleDao roleDao = null;
public RoleServiceImpl() {
this.roleDao = new RoleDaoImpl(); //servlet中一旦調用這個service,就會實例化該Dao對象
}
@Override
public List<Role> getRoleList() {
Connection conn = null;//獲取連接
List<Role> roleList = null;
try {
conn = BaseDao.getConnection();
roleList = roleDao.getRoleList(conn);//服務層調用Dao層方法
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
BaseDao.close(conn,null,null);//關閉連接
}
return roleList;
}
}
5 測試
@Test
public void test(){
RoleService roleService = new RoleServiceImpl();
List<Role> list = roleService.getRoleList();
for (Role role:list) {
System.out.println(role.getId()+"\t"+role.getRoleName()+"\t"+role.getRoleCode());
}
}
6 編寫servlet
href="${pageContext.request.contextPath }/jsp/user.do?method=query"
/jsp/user.do
//這個URL映射的servlet我們在前面已經注冊過了,所以區別就是它后面跟的參數
method=query
這個參數就是在傳遞指定servlet應該調用的方法
這一次編寫的 servlet 比較的復雜,我們把它單獨拎出來看
//3、按照用戶名/職位名稱查詢用戶列表或整表查詢
//【重點&難點】
public void quary(HttpServletRequest req, HttpServletResponse resp){
//1、從前端獲取數據
String queryname = req.getParameter("queryname");
String queryUserRole = req.getParameter("queryUserRole");
String pageIndex = req.getParameter("pageIndex");//通過隱藏域進行的提交,默認 = 1
int UserRole = 0;//我們先讓UserRole = 0,因為從前端接收到的queryUserRole可能就是一個NULL,此時我們就需要將其指定為0
int pageSize = 5;//這個數字最好是寫在配置文件中,這樣以后想要修改一頁上面顯示的行數,我們就不用再從新編譯代碼和測試了
int currentPageNo = 1;//先給當前頁設置一個默認的值
//2、通過判斷參數決定哪些請求需要處理
if (queryname == null){
queryname = "";//如果前端沒有按照用戶名查詢,我們就將用戶名設置""
}
if (queryUserRole!=null && queryUserRole!=""){
UserRole = Integer.parseInt(queryUserRole);//當前端傳過來的queryUserRole有數據的時候我們才對其進行解析
}
if (pageIndex!=null){
currentPageNo = Integer.parseInt(pageIndex);
}
//3、為了實現分頁,需要使用工具類PageSupport並傳入總用戶數,計算出totalPageCount
//參數totalCount由getUserCount得出;pageSize是固定死了的;currentPageNo默認設為1
UserService userService = new UserServiceImpl();
int totalCount = userService.getUserCount(queryname,UserRole);
//使用最開始導入的工具類
PageSupport pageSupport = new PageSupport();
pageSupport.setPageSize(pageSize);//設置一頁多少行數據
pageSupport.setTotalCount(totalCount);//設置總頁數
pageSupport.setCurrentPageNo(currentPageNo);//設置當前頁數
int totalPageCount = 0;
totalPageCount = pageSupport.getTotalPageCount();
//4、控制翻頁
if (currentPageNo<1){//在第一頁的時候還想點擊上一頁
currentPageNo = 1;
}else if(currentPageNo>pageSupport.getTotalPageCount()) {//在第最后一頁的時候還想點擊下一頁
currentPageNo = totalPageCount;
}
//5、用戶列表展示
List<User> userList = userService.getUserList(queryname,UserRole,currentPageNo,pageSize);
//將集合返回給前端進行解析顯示
req.setAttribute("userList",userList);
//6、角色列表展示
List<Role> roleList = new RoleServiceImpl().getRoleList();
req.setAttribute("roleList",roleList);
//7、將參數返回前端
req.setAttribute("queryUserName",queryname);
req.setAttribute("queryUserRole",queryUserRole);
req.setAttribute("totalPageCount",totalPageCount);
req.setAttribute("totalCount",totalCount);
req.setAttribute("currentPageNo",currentPageNo);
//8、重定向刷新頁面
try {
System.out.println("=======================進入到servlet,且i調用method = query");
req.getRequestDispatcher("userlist.jsp").forward(req,resp);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
首先,這個servlet直接和前端的用戶管理頁面進行交互,且用戶管理頁面所有展示的數據都是由這個servlet的query()返回的,所以我們應該按照前端素材來編寫servlet的query()
通過上面的分析我們可以發現:我們需要從前端頁面獲取3個參數,並給前端返回7個參數
- 獲取前端參數
- queryname:按照姓名查詢的姓名
- queryUserRole:按照職位查詢的職位名稱
- pageIndex:當前的頁面index
- 傳遞給前端的參數
- userList:存儲前端展示的用戶(user)對象集合
- roleList:存儲前端展示的角色(role)對象集合
- queryname:再將這個參數傳遞回去是為了前端搜索之后搜索框中還保留着用戶搜索的值
- queryUserRole:作用同queryname
- totalCount:總共多少條用戶記錄
- currentPageNo:當前所在頁數
- totalPageCount:總頁數參數,注意:這個參數來自於工具類pageSupport
//1、從前端獲取數據
String queryname = req.getParameter("queryname");
String queryUserRole = req.getParameter("queryUserRole");
String pageIndex = req.getParameter("pageIndex");//通過隱藏域進行的提交,默認 = 1
int UserRole = 0;//我們先讓UserRole = 0,因為從前端接收到的queryUserRole可能就是一個NULL,此時我們就需要將其指定為0
int pageSize = 5;//這個數字最好是寫在配置文件中,這樣以后想要修改一頁上面顯示的行數,我們就不用再從新編譯代碼和測試了
int currentPageNo = 1;//先給當前頁設置一個默認的值
//2、通過判斷參數決定哪些請求需要處理
if (queryname == null){
queryname = "";//如果前端沒有按照用戶名查詢,我們就將用戶名設置""
}
if (queryUserRole!=null && queryUserRole!=""){
UserRole = Integer.parseInt(queryUserRole);//當前端傳過來的queryUserRole有數據的時候我們才對其進行解析
}
if (pageIndex!=null){
currentPageNo = Integer.parseInt(pageIndex);
}
//3、為了實現分頁,需要使用工具類PageSupport並傳入總用戶數,計算出totalPageCount
//參數totalCount由getUserCount得出;pageSize是固定死了的;currentPageNo默認設為1
UserService userService = new UserServiceImpl();
int totalCount = userService.getUserCount(queryname,UserRole);
//使用最開始導入的工具類
PageSupport pageSupport = new PageSupport();
pageSupport.setPageSize(pageSize);//設置一頁多少行數據
pageSupport.setTotalCount(totalCount);//設置總頁數
pageSupport.setCurrentPageNo(currentPageNo);//設置當前頁數
int totalPageCount = 0;
totalPageCount = pageSupport.getTotalPageCount();
//4、控制翻頁
if (currentPageNo<1){//在第一頁的時候還想點擊上一頁
currentPageNo = 1;
}else if(currentPageNo>pageSupport.getTotalPageCount()) {//在第最后一頁的時候還想點擊下一頁
currentPageNo = totalPageCount;
}
//5、用戶列表展示
List<User> userList = userService.getUserList(queryname,UserRole,currentPageNo,pageSize);
//將集合返回給前端進行解析顯示
req.setAttribute("userList",userList)
//6、角色列表展示
List<Role> roleList = new RoleServiceImpl().getRoleList();
req.setAttribute("roleList",roleList);
//7、將參數返回前端
req.setAttribute("queryUserName",queryname);
req.setAttribute("queryUserRole",queryUserRole);
req.setAttribute("totalPageCount",totalPageCount);
req.setAttribute("totalCount",totalCount);
req.setAttribute("currentPageNo",currentPageNo);
//8、重定向刷新頁面
try {
System.out.println("=======================進入到servlet,且i調用method = query");
req.getRequestDispatcher("userlist.jsp").forward(req,resp);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
7 測試
測試完成!
SMBMS (八)其它模塊
其它還沒有開發的模塊包含用戶管理中的增刪改,以及訂單管理模塊,供應商管理模塊的功能
其實有時間我們只需要把用戶管理模塊開發完整就行了,其它兩個模塊其實都是一個原理,只是查詢的表不同
項目暫時完結!
SMBMS (九)項目感悟
項目亮點/技巧/學習點
1 在前端使用EL表達式取值提示用戶賬號/密碼錯誤
在登陸界面使用的EL表達式(
${error }
)取后端傳回來的參數,用於提示用戶輸入錯誤
- 由於在用戶登陸之前,這個div中的EL表達式取不到值,所以它不會顯示,而一旦用戶輸入錯誤,后端就會向前端響應參數(req.setAttribute("error","用戶名或密碼錯誤")😉 + 重定向前端視圖,這就使用div中的EL表達式取到了error的值,從而在登陸界面上進行顯示
2 使用session存儲用於的全部信息
在用戶登陸上的時候,將從數據庫中將用戶的全部信息封裝的pojo中,並將pojo存到session中,這就便於后面我們需要使用這個用戶的某些信息的時候,不用再去查詢數據庫,而直接從session中獲取對應的pojo對象的屬性即可
- 比如修改密碼的時候使用Ajax與session中User對象的passwor做對比
- 比如我們使用過濾器,在每一次用戶發起請求的時候都去查看用戶是否登陸,保證數據安全
- 比如我們用session控制用戶最長在線時間,只要長時間沒操作將自動注銷登陸
- 比如注銷功能,直接將session中用戶對象屬性刪除即可配合過濾器將用戶注銷
- ...
3 SQL語句的動態拼接
- 在實現用戶管理功能的模塊中,首次用到了StringBuffer+集合List的組合來實現動態拼接SQL語句和存儲pstmt對象所需要的參數數組的功能
- StringBuffer提供動態拼接SQL語句的功能,使得我們可以在基礎的SQL語句上加上獨特的篩選條件
- 集合List通過存儲pstmt對象所需要的參數數組的功能,使得不管我們要在后面添加多少篩選條件都只是用一個集合List存儲,在最后傳輸的時候調用List.toArray()即可將其轉為數組
4 某一功能的開發流程
-
要在前端實現一個功能,我們需要按照Dao開始-->service-->servlet-->JSP的順序來開發
並在進行寫代碼之前分析實現步驟/模塊功能划分(很重要)
只有我們先想好了怎么做,然后再去編寫代碼才會快,且有條不紊,切忌看完要求之后馬上開始寫代碼
5 在項目中多復用,少創建
本項目中,我們復用了servlet
- 通過前端傳遞的指定參數名稱的參數的值,調用同一個servlet中不同的方法(將實現多個功能的servlet獨立封裝為一個個的方法)
在本項目中我們復用了SQL
- 前面第3點我們提到了使用"StringBuffer+集合List的組合來實現動態拼接SQL語句和存儲pstmt對象所需要的參數數組的功能",這個功能給我們帶來的效果就是通過判斷傳遞的參數是否為NULL,我們可以動態的決定要在基礎的SQL語句后面添加什么篩選條件,這些條件中的參數又是什么
- 復用的效果就是:使用這一條基本SQL語句+StringBuffer+集合List我們可以實現前端按照用戶名查詢、按照職位名稱查詢和整表查詢
6 合理使用隱藏域
在本項目中隱藏域的使用是配合servlet復用使用的,我們就是通過隱藏域來讓前端向后端提交method參數的值,然后通過判斷method的值決定到底調用哪一個封裝在復用servlet中的方法
7 合理使用過濾器
通過合理的使用過濾器我們可以為后端服務器程序攔截許多的非法請求、垃圾請求、無效請求,也可以解決中文亂碼問題
注意:在使用過濾器的時候,正常情況下我們只需要實現doFilter(),並且最重要的就是在方法的最后放行,否則這個請求是不能到達服務器程序並被響應的
chain.doFilter(req,resp);//過濾器放行
8 合理的編寫公用的Dao
在本項目中,在最開始我們就編寫了一個BaseDao類,這個類不針對某一張表進行CRUD,內部沒有綁定任何的SQL語句
這個類我們一共只定義了1個static代碼塊+4個方法
- 靜態代碼塊用於初始化JDBC4大參數
- 4個方法分別為
- 獲取數據庫連接對象
- 查詢數據
- 修改數據
- 關閉資源
- 注意,這些方法之間不要出現相互調用的情況,我們應該在具體的對某一張表進行操作的時候再來調用這個類中的某些方法;且這些方法使用的數據庫對象全部都需要從外部傳遞
package com.thhh.dao;
/**
* 注意理解這個類中的方法之所以要傳入這些數據庫操縱對象是因為為了統一的關閉資源
* 而傳入的對象中可以都是null,具體的對象獲取在方法里面進行;也可以只有conn實例化,其他對象的實例化同樣放在具體的方法里進行
*/
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//操作數據庫的公共類
public class BaseDao {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
//靜態代碼塊用於初始化JDBC4大參數,且靜態代碼塊只會在第一次調用這個類的時候執行一次
static {//靜態代碼塊,在調用這個類的地方優先自動執行
//讀取配置文件
//1、創建properties對象
Properties properties = new Properties();
//2、通過類加載器加載資源文件為字節輸入流
InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
USERNAME = properties.getProperty("USERNAME");
PASSWORD = properties.getProperty("PASSWORD");
}
//1、編寫獲取數據庫的連接對象的公共方法
public static Connection getConnection(){
Connection conn= null;
try {
//1、加載驅動類
Class.forName(DRIVER);
//2、獲取連接對象
conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//2、編寫查詢公共方法 —— 注意查詢的結果返回為ResultSet結果集
/**
* 用於查詢數據的公共方法,注意:使用發送SQL語句的對象為PreparedStatement
* @param sql:查詢的sql語句,由前端傳遞
* @param params:sql語句中占位符的值
*
*===============這下面的3個參數之所以在調用的時候傳遞原因就在於這3個都是資源,我們需要關閉,如果我們直接在這個方法里獲取資源對象的話,那么我們就應該在這個方法中關閉資源===============
*===============但是調用處還在等待這個方法返回結果集,所以我們不應該在這個地方獲取這3個對象,而應該由調用出傳遞,這樣可以統一管理和關閉資源===============
*
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @param rs:返回的結果集,和pstmt只是傳入null的引用。這個對象真正的實例化放在這個方法里面
*
* @return:返回查詢到的結果集
*/
public static ResultSet executeQuery(String sql,Object[] params,Connection conn,PreparedStatement pstmt,ResultSet rs){
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
rs = pstmt.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return rs;
}
//3、編寫修改公共方法
/**
* 用於修改數據的公共方法,注意:使用發送SQL語句的對象為PreparedStatement
* @param sql:修改數據的sql語句模板
* @param params:模板中占位符對應的值
*
* =====下面兩個對象需要調用出傳入也是為了統一管理和關閉資源=====
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
*
* @return 返回受影響的行數
*/
public static int executeUpdate(String sql,Object[] params,Connection conn,PreparedStatement pstmt){
int result = 0;
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
result = pstmt.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return result;
}
//4、編寫關閉資源公共方法
/**
* 關閉資源
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @param rs:返回的結果集,和pstmt只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @return:返回關閉資源的結果
*
* 注意:關閉資源的時候要倒着關
*/
public static boolean close(Connection conn,PreparedStatement pstmt,ResultSet rs){
boolean flag = true;
if (rs!=null){
try {
rs.close();
rs = null;//讓這個變量為null,gc就會自動對其進行回收
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;//關閉失敗就將flag設置false
}
}
if (pstmt!=null){
try {
pstmt.close();
pstmt = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
if (conn!=null){
try {
conn.close();
conn = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
return flag;//返回關閉結果
}
}
9 各層功能單一,使得項目維護更高效
這一點其實可以和第4點寫在一起,但是為了加深印象就分開寫了
第4點里面說了:項目開步驟應該按照Dao開始-->service-->servlet-->JSP的順序來開發
這里補充說明:在開發的時候我們應該保證開發的每一層只是在完成自己這一層應該有的功能,應該其他層完成的功能,堅決不要混到其他層來開發
各層的功能遵循SpringMVC的划分:
- DAO只專注於與數據庫交互
- service專注於完成業務邏輯編寫
- servlet專注獲取前端請求、調用service、和跳轉視圖
- JSP專注於頁面展示
各層之間的功能不要混用,各層之間的聯系通過項目調用來構建
10 多利用"面向接口編程"的思想
在本項目中,不管是開發Dao層還是開發service層,我們都使用了面向接口編程的思想
首先編寫接口定義,再去實現接口定義;這樣我們開發過程中就實現了設計和實現分離,整個項目的接口也就很清晰
11 多測試
在開發中,我們每寫完一個功能之后,最好就使用junit對剛剛寫的方法進行測試,這保證了開發的穩步前進
在Dao層有SQL語句的地方我們最好是寫一句輸出SQL語句的代碼,這樣每次執行的時候我們都可以看到SQL語句具體是什么樣的,也容易進行排錯
12 細心
開發過程中往往出現bug的地方都很微小,但是就是微小才不容易發現,從而導致了程序報錯
- 比如:SQL語句拼接時的空格問題
- 比如:for循環時等號取不取的問題
- 比如:sql.append(" AND u.userRole = ?");中將u.userRole寫成r.roleCode
13 小黃鴨調試法
傳說中程序大師隨身攜帶一只小黃鴨,在調試代碼的時候會在桌上放上這只小黃鴨,然后詳細地向鴨子解釋每行代碼
一邊闡述代碼的意圖一邊觀察它實際上的意圖並做調試,這兩者之間的任何不協調會變得很明顯,並且更容易發現自己的錯誤
類似的,有一種現象叫做cone of answers,這是一個常見的現象。你的朋友跑來問你一個問題,但是當他自己把問題說完,或者說到一半的時候就想出了答案走了,留下一臉茫然的你。是的,這個時候你就起到了那只小黃鴨的作用
總的來說,在你試圖表述自己的想法的過程中,自然地在促使自己去整理思路,重新考慮問題
14 一些簡寫(背下來)
- CRUD:crud是指在做計算處理時的增加(Create)、檢索(Retrieve)、更新(Update)和刪除(Delete)幾個單詞的首字母簡寫,crud主要被用在描述軟件系統中數據庫或者持久層的基本操作功能
- ACID:ACID,指數據庫事務正確執行的四個基本要素的縮寫,包含:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability);一個支持事務(Transaction)的數據庫,必須要具有這四種特性,否則在事務過程(Transaction processing)當中無法保證數據的正確性,交易過程極可能達不到交易方的要求
15 實際開發中,多用事務
在實際的開發中,CRUD操作應該都是用事務操作來提交,這是因為這4種操作在執行的時候都可能失敗,所以為了安全,最好的解決方案就是使用事務機制編寫代碼
注意事務的使用位置:在try開始的時候開啟事務,在業務邏輯完成的時候提交事務,在catch中回滾事務
16 開發手冊
推薦<阿里巴巴開發手冊>,主要就是規約我們開發過程中的規范問題,寫代碼的結構問題 —— 很重要
17 想要了解java底層的可以看看深入理解java虛擬機這本書
說明:本文大部分轉載自Jobito