🌈 个人主页:十二月的猫-CSDN博客
🔥 系列专栏: 🏀系统学javaWeb开发_十二月的猫的博客-CSDN博客💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光
目录
1. 前言
在前面的学习中,我们已经了解到Thymeleaf技术能够用于页内渲染。
页面渲染包括:页面加载、页面内容加载两部分
页面加载:浏览器发送URL访问页面请求时,后端的servlet能够通过thymeleaf技术渲染出对应的页面,然后由Tomcat反馈给浏览器
页面内容加载:后端数据库中的数据是不确定数目(动态变化的),因此我们的html语句不能是死语句。而是类似循环结构的语句,从数据库中获取数据并动态展示在前端页面中。
本篇文章将通过写项目的形式带大家深入了解Thymeleaf机制与应用
2. 项目配置和MVC中的JDBC部分
一个项目可以分为两个部分:
1、由客户端(前端)向服务器端(后端)发送请求
2、由服务器端(后端)向客户端(前端)响应请求
这两部分中间需要经过MVC层,我们需要编写的就是MVC层
MVC层包括:Thymeleaf技术(前端渲染)、JDBC技术(后端数据获取)
2.1 创建数据库和表
为了与前面JDBC等文章内容相对应,这边使用的数据库、Util等都是相同的
如果前面的文章都是一一实现的,这边几乎不需要改动。仅仅需要增加一个html、修改web.xml,增加servlet三个操作即可
【javaWeb技术】·javaWeb项目结构(web开发必看)-CSDN博客
【javaWeb技术】·一篇搞定Servlet-CSDN博客
【javaWeb技术】·一篇搞定thymeleaf-CSDN博客
create table t_fruit
(
f_id int auto_increment comment '水果编号' primary key,
f_name varchar(100) not null comment '水果名字',
price double(6,2) not null comment '水果价格',
f_count int not null comment '水果库存',
remark VARCHAR(100) comment '水果备注'
);
insert into t_fruit (f_name,price,f_count,remark)
values ('apple',100.00,500,'China'),
('banana',56.00,600,'China'),
('orange',121.00,300,'China'),
('watermelon',23.00,900,'China')
由于个人测试时对数据库表内容有所修改,最终数据如下:
2.2 部署环境
1、首先需要有前面四篇文章中所配置的所有环境
2、在此基础上,增加Thymeleaf所需要的jar包,如图:
关键点:
1、在于这个lib包放哪个位置???
一开始我在思考放在javaWeb项目下的lib包中和放在web下的WEB-INF(如上图)中会存在区别吗?
在查阅资料后,我们可以发现:lib对模块是共享的(一个模块可以共享模块内的lib)
这里我的项目只有一个模块(项目自身),所以我这个项目中的lib都是共享的
2、lib包需要配置给模块
虽然lib包是模块共享的,但是仍然是需要配置的
这里,我自己的演示项目选择将Thymeleaf所需要的所有包都存放在一个项目lib中,如下:
2.3 pojo实体:数据库表的实体类
package com.javaWebEx.fruitPro.fruit.pojo;
public class Fruit {
//数据库中列名用下划线,这里用驼峰
private Integer fId;//f_id
private String fName;//f_name
private Double price;//price
private Integer fCount;//f_count
private String remark;//remark
public Fruit(Integer fId, String fName, Double price, Integer fCount, String remark) {
this.fId = fId;
this.fName = fName;
this.price = price;
this.fCount = fCount;
this.remark = remark;
}
public Fruit() {this(0,"",0.0,0,"");}
public Integer getfId() {
return fId;
}
public void setfId(Integer fId) {
this.fId = fId;
}
public String getfName() {
return fName;
}
public void setfName(String fName) {
this.fName = fName;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getfCount() {
return fCount;
}
public void setfCount(Integer fCount) {
this.fCount = fCount;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Fruit fruit = (Fruit) o;
return fId == fruit.fId && fCount == fruit.fCount && fName.equals(fruit.fName) && price == fruit.price && remark==fruit.remark;
}
@Override
public String toString() {
return "Fruit{" +
"fId=" + fId +
", fName='" + fName + '\'' +
", price=" + price +
", fCount=" + fCount +
", remark=" + remark +
'}';
}
}
2.4 resources配置:编写配置文件
JDBC连接数据库这边采用的是druid的连接池
所以需要写db.properties文件,内容如下
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///atguigu
username=root
password=1557304
initialSize=10
maxActive=20
2.5 util根据:创建JDBC工具类
package com.javaWebEx.util;
/*
* JDBC工具类
* 1、维护一个连接池对象,维护一个线程绑定变量的threadlocal对象
* 2、对外提供在TreadLocal中获取连接的方法
* 3、对外提供回收连接的方法,将要回收的连接从threadlocal中移除
* */
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtil {
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();//connection设定为TheadLocal使得线程中connection是唯一的
static {
Properties properties=new Properties();//配置对象
InputStream inputStream=JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");//加载db.properties到类中(以数据流形式)
try {
properties.load(inputStream);//设置配置对象
dataSource= DruidDataSourceFactory.createDataSource(properties);//利用配置对象创建连接池
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection(){
try {
//从threadlocal中获取连接
Connection connection=threadLocal.get();//从TheadLocal中获取连接
//threadlocal中没有连接(第一次获取),则从连接池中加载连接到TheadLocal中
if(connection==null){
connection= dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void release(){
try {
//归还连接对象是以线程为单位的(因为一个线程只有一个连接对象)
Connection connection=threadLocal.get();
//线程中
if(connection!=null){
//从threadLocal中移除当前已经存储的Connection对象
threadLocal.remove();
//归还connection后要修改回自动提交
connection.setAutoCommit(true);
//将Connection对象归还连接池
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
工具类的出现是因为:
- 每个JDBC的操作都要获取连接
- 每个JDBC的操作也都要释放连接
- 连接池通过druid来获取
2.6 编写baseDAO层
DAO层用来实现和数据库操作——增删改查
这些操作存在共性可以提取:
- 查询类的可以有一个baseDAO
- 更新类的可以有一个baseDAO
package com.javaWebEx.fruitPro.fruit.dao;
import com.javaWebEx.util.JDBCUtil;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class BaseDAO {
/**
* 通用的增删改方法
* @param sql 请求者要执行的SQL语句
* @param params SQL语句中的占位符要赋值的参数
* @return 受影响的行数
*/
public int executeUpdate(String sql,Object...params) throws SQLException {
//1、获取驱动+获取连接
Connection connection= JDBCUtil.getConnection();
//2、预编译SQL语句
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//3、确定SQL语句并处理
if(params!=null&¶ms.length>0){
for(int i=0;i<params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
//4、处理结果
int row=preparedStatement.executeUpdate();
//5、释放资源
preparedStatement.close();
//如果开启了自动提交,才自动释放连接;否则,手动提交情况下不释放连接
if(connection.getAutoCommit()){
JDBCUtil.release();
}
return row;
}
/**
* 通用的查询方法
* @param clazz 查询结果返回的对象
* @param sql 查询的预编译sql语句
* @param params 确定sql语句的占位符
* @return 单行单列、单行多列、多行多列都有可能
* @param <T> 与Class<T>合起来共同表示一个通用类
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
/*
通用查询结果:单行单列、单行多列、多行多列
单行单列:一个int结果
单行多列:一个对象(例如employee)
多行多列:list(object)
封装过程
1、返回的类型:泛型。类型不确定,使用时将结果类型提供给baseDAO
2、返回的结果:通用list。可以存储多个结果,也可以存储单个结果
3、结果的封装:反射!!要求调用者告诉baseDAO要封装对象的类对象
*/
//Class<T>:类的类型
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object...params) throws Exception{
//1:获取连接
Connection connection=JDBCUtil.getConnection();
//2、获取预编译sql
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//3、确定sql
if(params!=null && params.length>0){
for(int i=0;i<params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
//4、获取并处理结果(只有得到对象的属性,才能对对象的属性赋值)
ResultSet resultSet=preparedStatement.executeQuery();
//处理结果:获取数据库表的元数据(元数据中有:列数、列名)
ResultSetMetaData resultSetMetaData=resultSet.getMetaData();
//处理结果:根据数据库表的元数据得到数据库表的列的数量
int columCount=resultSetMetaData.getColumnCount();
List<T> list=new ArrayList<>();
//处理结果(while循环一次处理完一个对象,也就是一条记录)
while(resultSet.next()){
//循环一次,代表有一个记录,创建一个对象
T t=clazz.newInstance();
for(int i=1;i<=columCount;i++){
//通过getObject操作获得列的值
Object value = resultSet.getObject(i);
//获得到的列的value值,就是t对象中的一个属性值
//获取t对象中的一个属性名
String fieldName=resultSetMetaData.getColumnLabel(i);
//通过对象的属性名来得到对象的属性
Field field=clazz.getDeclaredField(fieldName);
//为属性突破封装的private
field.setAccessible(true);
//为属性赋值
field.set(t,value);
}
list.add(t);
}
//5、释放资源
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
JDBCUtil.release();
}
return list;
}
/**
* 通用的查询方法————简化仅用于单行数据的查询
* @param clazz 查询结果返回的对象
* @param sql 查询的预编译sql语句
* @param params 确定sql语句的占位符
* @return
* @param <T>
* @throws Exception
*/
public <T> T executeQueryBean(Class<T> clazz,String sql,Object...params) throws Exception{
List<T> list=this.executeQuery(clazz,sql,params);
if(list==null || list.size()==0)
return null;
return list.get(0);
}
}
baseDAO更详细的理解见:
2.7 编写fruitDAO
接口:
package com.javaWebEx.fruitPro.fruit.dao;
import com.javaWebEx.fruitPro.fruit.pojo.Fruit;
import java.sql.SQLException;
import java.util.List;
public interface FruitDAO {
/**
* 查询数据库中水果的所有数据
* @return 表中所有的数据
*/
List<Fruit> selectAll() throws Exception;
/**
* 根据水果id查询一个水果对象
* @param fId
* @return 一个水果对象
*/
Fruit selectByFId(Integer fId) throws Exception;
/**
* 新增数据库对应的新增一条水果数据
* @param fruit
* @return 受影响行数
*/
int insert(Fruit fruit) throws SQLException;
/**
* 数据库对应的修改一条水果数据
* @param fruit 数据库中想要修改的水果对象
* @return 受影响的行数
*/
int update(Fruit fruit) throws SQLException;
/**
* 数据库对应的删除一条员工数据
* @param fruit 数据库中想要删除的水果对象
* @return 受影响的行数
*/
int delete(Fruit fruit) throws SQLException;
}
实现:
package com.javaWebEx.fruitPro.fruit.dao;
import com.javaWebEx.fruitPro.fruit.pojo.Fruit;
import java.sql.SQLException;
import java.util.List;
public class FruitDaoImpl extends BaseDAO implements FruitDAO{
@Override
public List<Fruit> selectAll() throws Exception {
String sql="select f_Id as fId,f_name as fName,price as price,f_count as fCount,remark as remark from t_fruit";
return executeQuery(Fruit.class,sql,null);
}
@Override
public Fruit selectByFId(Integer fId) throws Exception {
String sql="select f_Id as fId,f_name as fName,price as price,f_count as fCount,remark as remark from t_fruit where f_id=?";
return executeQueryBean(Fruit.class,sql,1);
}
@Override
public int insert(Fruit fruit) throws SQLException {
String sql="insert into t_fruit(f_name,price,f_count,remark) values(?,?,?,?)";
return executeUpdate(sql,fruit.getfName(),fruit.getPrice(),fruit.getfCount(),fruit.getRemark());
}
@Override
public int update(Fruit fruit) throws SQLException {
String sql="UPDATE t_fruit set price=?,f_count=? ,remark=? WHERE f_name=?";
return executeUpdate(sql,fruit.getPrice(),fruit.getfCount(),fruit.getRemark(),fruit.getfName());
}
@Override
public int delete(Fruit fruit) throws SQLException {
String sql="delete from t_fruit where f_id=?";
return executeUpdate(sql,fruit.getfId());
}
}
实现此时只要依靠baseDAO中的操作即可
baseDAO中的实现都是通用的
2.8 包和类的结构
3. MVC中的Thymeleaf部分(前端渲染)
3.1 配置上下文参数
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- <servlet>
<servlet-name>AddServlet</servlet-name>
<servlet-class>com.javaWebEx.servlets.AddServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>-->
<!-- <servlet>
<servlet-name>Demo02Servlet</servlet-name>
<servlet-class>com.javaWebEx.servlets.Demo02Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo02Servlet</servlet-name>
<url-pattern>/demo02</url-pattern>
</servlet-mapping>-->
<!-- <servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.javaWebEx.servlets.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/demo03</url-pattern>
</servlet-mapping>-->
<!-- 配置上下文参数 -->
<context-param>
<!-- 配置前缀-->
<param-name>view-prefix</param-name>
<!--WEB根目录-->
<param-value>/</param-value>
</context-param>
<context-param>
<!-- 配置后缀-->
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
</web-app>
3.2 编写servlet层
3.2.1 servlet基类
package com.javaWebEx.thymeleaf.servlet;
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.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
该基类提供processTemplate函数用来实现Thymeleaf技术的页面渲染
3.2.2 编写servlet
package com.javaWebEx.fruitPro.servlets;
import com.javaWebEx.fruitPro.fruit.dao.FruitDAO;
import com.javaWebEx.fruitPro.fruit.dao.FruitDaoImpl;
import com.javaWebEx.fruitPro.fruit.pojo.Fruit;
import com.javaWebEx.thymeleaf.servlet.ViewBaseServlet;
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.ArrayList;
import java.util.List;
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
FruitDAO fruitDAO=new FruitDaoImpl();
//利用DAO从数据库中获取数据
List<Fruit>fruitList= null;
try {
fruitList = fruitDAO.selectAll();
} catch (Exception e) {
throw new RuntimeException(e);
}
//将水果放入请求的session作用域中
HttpSession httpSession=req.getSession();
httpSession.setAttribute("fruit",fruitList);
//每个客户端都是带着自己的ID号发出申请的,所以通过req能够获取其ID号
//利用thymeleaf技术渲染页面
super.processTemplate("index",req,res);
}
}
使用了几个之前没有用过的技术:
- @WebServlet()完成web.xml配置
- 调用Dao获取数据库数据存放在会话的保存域中
- 根据index去渲染index.html页面,从而将页面加载工作移交给servlet来完成
3.3 编写页面
html:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div id="div_container">
<div id="div_fruit_list">
<p class="center f30">欢迎使用水果库存后台管理系统</p>
<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
</div>
<table id="tbl_fruit">
<tr>
<th class="w20">名称</th>
<th class="w20">单价</th>
<th class="w20">库存</th>
<th>操作</th>
</tr>
<!-- 如果链表为空-->
<tr th:if="${#lists.isEmpty(session.fruit)}">
<td colspan="4">对不起,库存为空!</td>
</tr>
<!-- 如果链表不为空,取出数据-->
<tr th:unless="${#lists.isEmpty(session.fruit)}" th:each="fruit : ${session.fruit}">
<td th:text="${fruit.fName}">苹果</td>
<td th:text="${fruit.price}">4</td>
<td th:text="${fruit.fCount}">20</td>
<!-- <td><img src="imgs/del.jpg" class="delImg" th:onclick="'delFruit('+${fruit.fid}+')'"/></td> -->
<td><img src="imgs/del.jpg" class="delImg"/></td>
</tr>
</table>
</div>
</div>
</body>
</html>
css:
*{
color: threeddarkshadow;
}
a{
text-decoration: none;
}
body{
margin:0;
padding:0;
background-color:#808080;
}
div{
position:relative;
float:left;
}
#div_container{
width:80%;
height:100%;
border:0px solid blue;
margin-left:10%;
float:left;
background-color: honeydew;
border-radius:8px;
}
#div_fruit_list{
width:100%;
border:0px solid red;
}
#tbl_fruit{
width:60%;
line-height:28px;
margin-top:16px;
margin-left:20%;
}
#tbl_fruit , #tbl_fruit tr , #tbl_fruit th , #tbl_fruit td{
border:1px solid gray;
border-collapse:collapse;
text-align:center;
font-size:16px;
font-family:"黑体";
font-weight:lighter;
}
.w20{
width:20%;
}
.delImg{
width:24px;
height:24px;
}
.btn{
border:1px solid lightgray;
width:80px;
height:24px;
}
.center{
text-align:center;
}
.f30{
font-size: 30px;
}
提供图片:
4. 项目结果展示
可以看到页面已经把数据库中的所有信息体现在页面上
完整项目结构展示:
图片中存在一些其他的文件并不是用在该项目中的
该项目中的文件都已经在上面一一说明
总结
如果觉得对你有帮助,辛苦友友点个赞,收个藏呀~~~
猫猫未来一定会产出更多优质文章的!!