??作者 .29. 的?博客主頁?
??記錄JavaWeb學(xué)習(xí)的專欄:Web專欄
??拼搏起來吧,未來會(huì)給你開出一個(gè)無法拒絕的條件…您的
點(diǎn)贊
,收藏
以及關(guān)注
是對作者最大的鼓勵(lì)喔 ~~
前言
在JDBC專欄中,我們完成了水果庫存系統(tǒng)功能的設(shè)計(jì),實(shí)現(xiàn)以及優(yōu)化,但總得來看,整個(gè)項(xiàng)目都是后端的代碼,系統(tǒng)的頁面也不過是通過控制臺模擬而來的,而非真正的前端頁面。
而在這篇文章開始,我們會(huì)對水果庫存系統(tǒng)的客戶端頁面功能進(jìn)行設(shè)計(jì)與實(shí)現(xiàn),讓庫存系統(tǒng)可以在網(wǎng)頁中使用。
項(xiàng)目中我們主要依賴Tomcat
部署,使用Servlet
組件,過程中通過JDBC
連接MySQL數(shù)據(jù)庫
獲取數(shù)據(jù),將數(shù)據(jù)在瀏覽器頁面中展現(xiàn)出來。
一、Thymeleaf - 視圖模板技術(shù)
在開始,我們需要先了解一下Thymeleaf - 視圖模板技術(shù)
的使用,這是我們在使用Servlet
時(shí)需要用到的技術(shù)。
接下來,我們通過 Thymeleaf 的使用來了解視圖模板技術(shù)到底是什么。
1.導(dǎo)入 jar 包
需要使用Thymeleaf - 視圖模板技術(shù),第一步就是導(dǎo)入相關(guān)的 jar 包了,具體的步驟大家應(yīng)該都了解,不了解的可以按照這篇文章的思路來嘗試導(dǎo)入jar包:druid數(shù)據(jù)庫連接池的使用。
Thymeleaf - 視圖模板技術(shù) jar包資源:
鏈接:https://pan.baidu.com/s/1NOucl2A8nEAIzg-rT4GqGg
提取碼:leaf
2.創(chuàng)建Servlet類,設(shè)置相關(guān)屬性與方法。
我們需要?jiǎng)?chuàng)建一個(gè)Servlet類,我將這個(gè)類命名為ViewBaseServlet
,說其是Servlet類是因?yàn)樗枰^承HttpServlet類
。ViewBaseServlet類
中設(shè)置的是與使用Thymeleaf 技術(shù)相關(guān)的屬性以及方法,我們可以通過代碼來了解其功能:
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import javax.servlet.ServletContext;
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 ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
// 1.獲取ServletContext對象
ServletContext servletContext = this.getServletContext();
// 2.創(chuàng)建Thymeleaf解析器對象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.給解析器對象設(shè)置參數(shù)
// ①HTML是默認(rèn)模式,明確設(shè)置是為了代碼更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②設(shè)置前綴
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③設(shè)置后綴
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④設(shè)置緩存過期時(shí)間(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤設(shè)置是否緩存
templateResolver.setCacheable(true);
// ⑥設(shè)置服務(wù)器端編碼方式
templateResolver.setCharacterEncoding("utf-8");
// 4.創(chuàng)建模板引擎對象
templateEngine = new TemplateEngine();
// 5.給模板引擎對象設(shè)置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.設(shè)置響應(yīng)體內(nèi)容類型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.創(chuàng)建WebContext對象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.處理模板數(shù)據(jù)
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
3.添加web.xml文件配置
web.xml
文件是一個(gè)Web項(xiàng)目的配置文件.
上文提到的ViewBaseServlet類
代碼中,有兩個(gè)重要的部分:
- 添加前綴 view-prefix
- 添加后綴 view-suffix
除了ViewBaseServlet類
,我們還需要在web.xml文件中添加前綴與后綴相關(guān)的參數(shù)配置,參數(shù)配置代碼如下:
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
參數(shù)配置中,我們將前綴設(shè)置為/
;同時(shí)后綴設(shè)置為.html
。
這樣的用意在于我們可以使用Thymeleaf - 視圖模板技術(shù),通過邏輯視圖名稱獲取到對應(yīng)的物理視圖名稱,例如:
//假設(shè)此時(shí)的視圖名稱是index
//那么Thymeleaf會(huì)將這個(gè) 邏輯視圖名稱 對應(yīng)到 物理視圖名稱上去
//邏輯視圖名稱 : index
//物理視圖名稱 : view-prefix + 邏輯視圖名稱 + view-suffix
//所以真實(shí)的視圖名稱是 / + index + .html
我們先了解一下使用的效果即可,至于為什么這樣,我們會(huì)在后續(xù)代碼的實(shí)現(xiàn)中體現(xiàn)出來。
4.使用Thymeleaf技術(shù)相關(guān)的HTML標(biāo)簽屬性
在HTML中,需要使用Thymeleaf技術(shù),我們需要在html標(biāo)簽
中添加相關(guān)屬性:
<html xmlns:th="http://www.thymeleaf.org">
</html>
而后,當(dāng)我們使用Thymeleaf技術(shù)的屬性時(shí),都需要在屬性前添加th:
的標(biāo)志。
二、瀏覽器頁面實(shí)現(xiàn)
1.獲取數(shù)據(jù)庫數(shù)據(jù)
我們需要在瀏覽器頁面中顯示數(shù)據(jù)庫中存放的數(shù)據(jù),那么我們就首先要連接數(shù)據(jù)庫來獲取數(shù)據(jù),這時(shí)候正好就需要連接數(shù)據(jù)庫執(zhí)行更新或查詢操作的功能。
相關(guān)功能的通用方法,已經(jīng)在我們的JDBC專欄:JDBC實(shí)戰(zhàn)系列文章中完成,直接拿過來使用即可:
BaseDAO類代碼
:
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author .29.
* @create 2022-09-25 19:30
*/
public abstract class BaseDAO<T> {
public final String DRIVER = "com.mysql.cj.jdbc.Driver";
public final String URL = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&CharacterEncoding=utf-8";
public final String USER = "root" ;
public final String PSW = "" ;
protected Connection connection;
protected PreparedStatement pstm;
protected ResultSet rs;
//獲取T的對象
private Class entityClass;
//構(gòu)造方法
public BaseDAO(){
//getClass() 獲取Class對象,我們當(dāng)前創(chuàng)建的是FruitDAOImpl對象,new FruitDAOImpl();
//那么子類的構(gòu)造方法內(nèi)部首先調(diào)用父類(BaseDAO)的空參構(gòu)造器,
//因此此處的getCalss()會(huì)被執(zhí)行,但是獲取的是子類FruitDAOImpl的Class
//所以getGenericSuperclass()獲取的是BaeDAO的class
Type genericType = getClass().getGenericSuperclass();//獲取泛型父類類型
//強(qiáng)轉(zhuǎn)成 ParameterizedType 參數(shù)化類型
//getActualTypeArguments 獲取實(shí)際的類型參數(shù)
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
//只傳入了一個(gè)參數(shù),數(shù)組首位就是我們需要獲取的<T>的真實(shí)類型
Type actualType = actualTypeArguments[0];
//actualType.getTypeName();獲取類型名
try {
entityClass = Class.forName(actualType.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//將加載驅(qū)動(dòng),連接數(shù)據(jù)庫的操作包裝成方法,減少代碼復(fù)用率
protected Connection conn(){
try {
//加載驅(qū)動(dòng)
Class.forName(DRIVER);
//數(shù)據(jù)庫管理器,連接數(shù)據(jù)庫
connection = DriverManager.getConnection(URL, USER, PSW);
return connection;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//將關(guān)閉資源的操作包裝成方法
protected void close(ResultSet rs,PreparedStatement pstm,Connection connection){
try {
if(rs != null)
rs.close();
if(pstm != null)
pstm.close();
if(connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//執(zhí)行更新,返回影響行數(shù)的方法(如果是插入操作,返回自增列主鍵值)
protected int executeUpdate(String sql,Object... params){//... params不確定數(shù)量的參數(shù)
boolean insertFlag = false;
insertFlag = sql.trim().toUpperCase().startsWith("INSERT");
try {
connection = conn();
//(sql語句不通用,靠參數(shù)傳遞進(jìn)來)
//sql語句
//String sql = "update t_fruit set fcount = ? where fname like ?";
//預(yù)處理對象
if(insertFlag){
pstm = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
}else{
pstm = connection.prepareStatement(sql);
}
setParams(pstm,params);
int count = pstm.executeUpdate();
rs = pstm.getGeneratedKeys();
if(rs.next()){
return ((Long)rs.getLong(1)).intValue();
}
//參數(shù)填充也不通用,也靠參數(shù)傳遞進(jìn)來
if(params != null && params.length > 0){
for(int i = 0;i < params.length;++i){
pstm.setObject(i+1,params[i]);
}
}
return count;//執(zhí)行更新,返回影響行數(shù)
} catch (SQLException e) {
e.printStackTrace();
}finally{
close( rs, pstm, connection);
}
return 0;//影響行數(shù)為0
}
//通過反射技術(shù),給obj對象的property屬性賦propertyValue值
protected void setValue(Object obj, String property,Object propertyValue){
Class clazz = obj.getClass();
try {
//獲取property這個(gè)字符串對應(yīng)的屬性名,比如fid去找obj對象中對應(yīng)的fid屬性值
Field field = clazz.getDeclaredField(property);
if(field != null){
field.setAccessible(true);//強(qiáng)制訪問(即使private屬性也能訪問),防止屬性為private
field.set(obj,propertyValue);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//執(zhí)行復(fù)雜查詢,返回... 例如統(tǒng)計(jì)結(jié)果
protected Object[] executeComplexQuery(String sql,Object... params){
try {
//加載驅(qū)動(dòng),連接數(shù)據(jù)庫的方法
connection = conn();
//預(yù)處理對象
pstm = connection.prepareStatement(sql);
//執(zhí)行查詢,返回結(jié)果集
rs = pstm.executeQuery();
//通過rs可以獲取結(jié)果集的元數(shù)據(jù)
//元數(shù)據(jù):描述結(jié)果集信息的數(shù)據(jù)(有哪些列,什么類型。。。)
ResultSetMetaData rsmd = rs.getMetaData();
//獲取元數(shù)據(jù)列數(shù)
int columnCount = rsmd.getColumnCount();
Object[] columnValueArr = new Object[columnCount];
if(rs.next()){
for(int i = 0;i < columnCount;++i){
Object columValue = rs.getObject(i + 1);
columnValueArr[i] = columValue;
}
return columnValueArr;
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
close( rs, pstm, connection);
}
return null;
}
//執(zhí)行查詢,返回單個(gè)實(shí)體對象
protected T load(String sql,Object... params){
try {
//加載驅(qū)動(dòng),連接數(shù)據(jù)庫的方法
connection = conn();
//預(yù)處理對象
pstm = connection.prepareStatement(sql);
setParams(pstm,params);
//執(zhí)行查詢,返回結(jié)果集
rs = pstm.executeQuery();
//通過rs可以獲取結(jié)果集的元數(shù)據(jù)
//元數(shù)據(jù):描述結(jié)果集信息的數(shù)據(jù)(有哪些列,什么類型。。。)
ResultSetMetaData rsmd = rs.getMetaData();
//獲取元數(shù)據(jù)列數(shù)
int columnCount = rsmd.getColumnCount();
if(rs.next()){
T entity = (T) entityClass.newInstance();
for(int i = 0;i < columnCount;++i){
String columnName = rsmd.getColumnName(i + 1);
Object columValue = rs.getObject(i + 1);
setValue(entity,columnName,columValue);
}
return entity;
}
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally{
close( rs, pstm, connection);
}
return null;
}
//給預(yù)處理參數(shù)設(shè)置參數(shù)
protected void setParams(PreparedStatement psmt,Object... params) throws SQLException {
if(params != null && params.length > 0 ){
for(int i = 0;i < params.length;++i){
psmt.setObject(i+1,params[i]);
}
}
}
// 執(zhí)行查詢,返回結(jié)果集并輸出
protected List<T> executeQuery(String sql,Object... params){
List<T> list = new ArrayList<>();
try {
//加載驅(qū)動(dòng),連接數(shù)據(jù)庫的方法
connection = conn();
//預(yù)處理對象
pstm = connection.prepareStatement(sql);
setParams(pstm,params);
//執(zhí)行查詢,返回結(jié)果集
rs = pstm.executeQuery();
//通過rs可以獲取結(jié)果集的元數(shù)據(jù)
//元數(shù)據(jù):描述結(jié)果集信息的數(shù)據(jù)(有哪些列,什么類型。。。)
ResultSetMetaData rsmd = rs.getMetaData();
//獲取元數(shù)據(jù)列數(shù)
int columnCount = rsmd.getColumnCount();
while(rs.next()){
T entity = (T) entityClass.newInstance();
for(int i = 0;i < columnCount;++i){
String columnName = rsmd.getColumnName(i + 1);
Object columValue = rs.getObject(i + 1);
setValue(entity,columnName,columValue);
}
list.add(entity);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally{
close( rs, pstm, connection);
}
return list;
}
}
這時(shí)候,我們還需要有一個(gè)方法來調(diào)用
BaseDAO類
中的通用方法,從而完成查詢數(shù)據(jù)的需求,在這里我們依舊創(chuàng)建一個(gè)FruitDAO接口
,并創(chuàng)建其實(shí)現(xiàn)類FruitDAOImpl類
來重寫方法,完成功能。
FruitDAO接口
:
我們需要在瀏覽器頁面中顯示數(shù)據(jù)庫的數(shù)據(jù),就需要一個(gè)獲取數(shù)據(jù)庫所有信息的方法。
import com.haojin.fruit.pojo.Fruit;
import java.util.List;
/**
* @author .29.
* @create 2022-10-04 16:39
*/
public interface FruitDAO {
//獲取所有的庫存信息
List<Fruit> getFruitList();
}
FruitDAOImpl類
:
實(shí)現(xiàn)類中調(diào)用了BaseDAO類中查詢數(shù)據(jù)的通用方法,將SQL語句傳入,將獲取到的數(shù)據(jù)存入集合中返回。
import com.haojin.fruit.pojo.Fruit;
import com.haojin.myssm.basedao.BaseDAO;
import java.util.List;
/**
* @author .29.
* @create 2022-10-04 16:39
*/
public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO{
@Override
public List<Fruit> getFruitList() {
return super.executeQuery("SELECT * FROM t_fruit");
}
}
2.使用Servlet組件渲染頁面
當(dāng)我們打開對應(yīng)的瀏覽器頁面,就會(huì)向客戶端中的Servlet組件發(fā)起一次請求,我們這時(shí)候?qū)@取到的數(shù)據(jù)庫數(shù)據(jù)保存到session保存作用域中,然后在HTML文件中進(jìn)行渲染,之后將頁面響應(yīng)給客戶端的瀏覽器中,如此一來就在瀏覽器中顯示出數(shù)據(jù)庫數(shù)據(jù)。
①Servlet組件
import com.haojin.fruit.pojo.Fruit;
import com.haojin.fruit.pojo.dao.FruitDAO;
import com.haojin.fruit.pojo.dao.FruitDAOImpl;
import com.haojin.myssm.basedao.myspringmvc.ViewBaseServlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
/**
* @author .29.
* @create 2022-10-04 16:42
*/
//Servlet從3.0版本開始,支持注解方式的注冊
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getFruitList();
//保存到session作用域
HttpSession session = request.getSession();
session.setAttribute("fruitList",fruitList);
//此處的視圖名稱是index
//那么Thymeleaf會(huì)將這個(gè) 邏輯視圖名稱 對應(yīng)到 物理視圖名稱上去
//邏輯視圖名稱 : index
//物理視圖名稱 : view-prefix + 邏輯視圖名稱 + view-suffix
//所以真實(shí)的視圖名稱是 / + index + .html
super.processTemplate("index",request,response);
}
}
②HTML頁面
這是我們水果庫存系統(tǒng)的瀏覽器頁面代碼,在頁面中我們會(huì)設(shè)置一個(gè)標(biāo)題以及一個(gè)表格,表格中展示的就是我們數(shù)據(jù)庫中保存的水果庫存數(shù)據(jù)。
因?yàn)槲覀儷@取到的數(shù)據(jù)是一個(gè)集合,所以在HTML文件中需要用到 Thymeleaf 技術(shù)的標(biāo)簽屬性:
- th:if
- th:unless
- th:each
- th:text
循環(huán)遍歷上文保存在session保存作用域中的數(shù)據(jù),如果數(shù)據(jù)為空,顯示庫存危機(jī)為空:
<tr th:if="${#lists.isEmpty(session.fruitList)}">
<td colspan = 4>對不起,庫存為空!</td>
</tr>
…
如果保存的數(shù)據(jù)不為空,獲取數(shù)據(jù)中每一行數(shù)據(jù)的信息(水果,價(jià)格,庫存),第四列的刪除標(biāo)志后續(xù)功能完善后可以通過點(diǎn)擊實(shí)現(xiàn)刪除操作。
<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit:${session.fruitList}">
<td th:text="${fruit.fname}"></td>
<td th:text="${fruit.price}"></td>
<td th:text="${fruit.fcount}"></td>
<td><img src="del.jpg" width="24px"/></td>
</tr>
完整html代碼
:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<style type = "text/css">
</style>
<link rel = "stylesheet" href = "index.css">
</head>
<body>
<div id = "div_container">
<div id = "list">
<p class="center f30">歡迎使用水果庫存后臺系統(tǒng)</p>
<table id = "tbl">
<tr>
<th class = "w20">名稱</th>
<th class = "w20">單價(jià)</th>
<th class = "w20">庫存</th>
<th>操作</th>
</tr><br/>
<tr th:if="${#lists.isEmpty(session.fruitList)}">
<td colspan = 4>對不起,庫存為空!</td>
</tr>
<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit:${session.fruitList}">
<td th:text="${fruit.fname}"></td>
<td th:text="${fruit.price}"></td>
<td th:text="${fruit.fcount}"></td>
<td><img src="del.jpg" width="24px"/></td>
</tr>
</table>
</div>
</div>
</body>
</html>
css效果代碼
:
#div_container{
width:80%;
height:100%;
border:1px solid white;
margin-left:10%;
float:left;
}
#list{
width:100%;
border:0px solid white;
}
#tbl , #tbl tr,#tbl th,#tbl td{
border:1px solid gray;
text-align:center;
}
#tbl{
margin-top:120px;
margin-left:20%;
width:60%;
}
.w20{
width:20%;
}
.center{
text-align: center;
}
.f30{
font-size: 30px;
}
body{
padding:0;
margin:0;
background-color:aquamarine;
}
div{
position:relative;
float:left;
}
.input{
border:1px solid lightgray;
width:90%;
}
三、效果
總結(jié)
這篇文章中,我們將獲取到的數(shù)據(jù)庫數(shù)據(jù)渲染到了瀏覽器頁面上,當(dāng)然這只不過是開始,我們的水果庫存系統(tǒng)還有許多功能沒有渲染到頁面中,比如新增庫存的功能、修改庫存的內(nèi)容、刪除指定水果信息的功能、頁面的分頁功能等。
我將在后續(xù)的文章中為客戶端頁面增添上述提到的內(nèi)容,逐步完善系統(tǒng),在完善系統(tǒng)項(xiàng)目的同時(shí),掌握好相關(guān)的知識。文章來源:http://www.zghlxwxcb.cn/news/detail-787218.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-787218.html
到了這里,關(guān)于【W(wǎng)eb實(shí)戰(zhàn)-Tomcat-Servlet-Thymeleaf -JDBC-MySQL】瀏覽器頁面顯示數(shù)據(jù)庫數(shù)據(jù)(水果庫存系統(tǒng))的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!