Thymeleaf视图模板技术
Thymeleaf什么作用?
可以将数据呈现到静态页面上。比如可以将数据库查询的数据通过Thymeleaf动态的呈现到静态页面上。
使用Thymeleaf的步骤
我们的目标是在静态页面中动态展现从数据库中提取出来的数据。
首先要有DAO从数据库中拿数据。
还需要一个ViewBaseServlet。
自己创建的servlet和静态页面。
除thymeleaf之外的相关代码
BaseDAO
public abstract class BaseDAO { //增删改 public static int update(Connection connection, String sql, Object ...args){//可变形参 PreparedStatement preparedStatement = null; try{ //预编译sql preparedStatement = connection.prepareStatement(sql); //填充占位符 //遍历可变数组,数组的内容就是传进来填写占位符的数据 for (int i = 0;i < args.length;i++){ preparedStatement.setObject(i+1,args[i]); } //执行 return preparedStatement.executeUpdate(); }catch (Exception e){ e.printStackTrace(); }finally { JDBCUtils.closeResource(null,preparedStatement,null); } return 0; } //查询一个对象 public <T>T getInstance(Connection connection,Class<T> clazz,String sql,Object ...args){ PreparedStatement preparedStatement = null; ResultSet resultSet = null; try{ //预编译sql preparedStatement = connection.prepareStatement(sql); //填写占位符 for(int i = 0;i < args.length;i++){ preparedStatement.setObject(i+1,args[i]); } //执行并返回结果集 resultSet = preparedStatement.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); //获取列数 int columnCount = metaData.getColumnCount(); if(resultSet.next()){ //将传入的类创建实例 T t = clazz.newInstance(); //取的时候取多少次由要查询的字段的个数决定 for(int i = 0;i<columnCount;i++){ //这个地方就没法强转了,没有办法判断具体是什么类型,获取这一行中某一个字段的值 Object columnValue = resultSet.getObject(i + 1); //获取每个列的列名 String columnLable = metaData.getColumnLabel(i + 1); //给user对象指定的columnName属性复制columnValue //利用反射,先拿到叫columnName的属性 Field field = clazz.getDeclaredField(columnLable); //这个属性可能是私有的,设置能访问 field.setAccessible(true); //给这个属性赋值 field.set(t,columnValue); } return t; } }catch (Exception e){ e.printStackTrace(); }finally { JDBCUtils.closeResource(null,preparedStatement,resultSet); } return null; } //查询多个对象 public <T> List<T> getForList(Connection connection,Class<T> clazz, String sql, Object ...args){ PreparedStatement preparedStatement = null; ResultSet resultSet = null; try{ //预编译sql preparedStatement = connection.prepareStatement(sql); //填写占位符 for(int i = 0;i < args.length;i++){ preparedStatement.setObject(i+1,args[i]); } //执行并返回结果集 resultSet = preparedStatement.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); //获取列数 int columnCount = metaData.getColumnCount(); //创建集合 List<T> list = new ArrayList<T>(); while (resultSet.next()){ //将传入的类创建实例 T t = clazz.newInstance(); //处理每一行数据的每一列,给每一行看作一个对象,给字段(属性)赋值 for(int i = 0;i<columnCount;i++){ //这个地方就没法强转了,没有办法判断具体是什么类型,获取这一行中某一个字段的值 Object columnValue = resultSet.getObject(i + 1); //获取每个列的列名 String columnLable = metaData.getColumnLabel(i + 1); //给user对象指定的columnName属性复制columnValue //利用反射,先拿到叫columnName的属性 Field field = clazz.getDeclaredField(columnLable); //这个属性可能是私有的,设置能访问 field.setAccessible(true); //给这个属性赋值 field.set(t,columnValue); } list.add(t); } return list; }catch (Exception e){ e.printStackTrace(); }finally { JDBCUtils.closeResource(null,preparedStatement,resultSet); } return null; } //聚合函数的查询 //查最大值,最小值,数量 //有多种类型,比如可以查员工中最大的生日,就是Date类型 public <E>E getValue(Connection connection,String sql,Object ...args){ PreparedStatement preparedStatement = null; ResultSet resultSet = null; try{ preparedStatement = connection.prepareStatement(sql); for (int i = 0;i < args.length;i++){ preparedStatement.setObject(i+1,args[i]); } resultSet = preparedStatement.executeQuery(); if(resultSet.next()){ return (E) resultSet.getObject(1); } }catch (Exception e){ e.printStackTrace(); }finally { JDBCUtils.closeResource(null,preparedStatement,resultSet); } return null; } }
工具类-获取关闭连接
public class JDBCUtils { //静态方法不需要获取对象 public static Connection getConnection(){ try{ //加载配置文件 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); properties.load(is); //获取基本信息 String url = properties.getProperty("url"); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driverClass = properties.getProperty("driverClass"); //与数据库连接 Class.forName(driverClass); Connection connection = DriverManager.getConnection(url, user, password); return connection; }catch (Exception e){ e.printStackTrace(); } return null; } //关闭资源 public static void closeResource(Connection connection, Statement ps,ResultSet resultSet){ try{ if(connection!=null) connection.close(); if(ps!=null) ps.close(); if(resultSet!=null) resultSet.close(); }catch (Exception e){ e.printStackTrace(); } } }
DAO接口
public interface FruitDAO { List<Fruit> getFruitList(Connection connection); }
DAO实现类
public class FruitDAOImpl extends BaseDAO implements FruitDAO{ @Override public List<Fruit> getFruitList(Connection connection) { String sql = "select fid,fname,price,fcount,remark from fruits"; List<Fruit> list = getForList(connection,Fruit.class,sql); return list; } }
数据库中各条数据对应的对象
public class Fruit { private int fid; private String fname ; private int price; private int fcount; private String remark; public Fruit() { } public Fruit(int fid, String fname, int price, int fcount, String remark) { this.fid = fid; this.fname = fname; this.price = price; this.fcount = fcount; this.remark = remark; } public int getFid() { return fid; } public void setFid(int fid) { this.fid = fid; } public String getFname() { return fname; } public void setFname(String fname) { this.fname = fname; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public int getFcount() { return fcount; } public void setFcount(int fcount) { this.fcount = fcount; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } }
静态页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/css/index.css"> <title>Title</title> </head> <body> <div> <div> <table> <tr> <th class="w20">名称</th> <th class="w20">单价</th> <th class="w20">库存</th> <th>操作</th> </tr> <tr> <td>苹果</td> <td>5</td> <td>20</td> <td><img src=""></td> </tr> </table> </div> </div> </body> </html>
自己创建的Servlet
@WebServlet("/index") public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { try{ //Connection connection = JDBCUtils.getConnection(); Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo","root","niit"); System.out.println(connection); FruitDAO fruitDAO = new FruitDAOImpl(); List<Fruit> fruitList = fruitDAO.getFruitList(connection); //保存到session作用域 HttpSession session = request.getSession(); session.setAttribute("fruitList",fruitList); }catch (Exception e){ e.printStackTrace(); } } }
Step1:添加相关jar包
Step2:在web.xml文件中配置
<?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"> <!-- 配置上下文参数--> <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> </web-app>
Step3:创建ViewBaseServlet类
这个类继承了HttpServlet类,所以我们自己创建的Servlet继承ViewBaseServlet类即可,会用到里面的processTemplate()方法。调用这个方法时,当访问这个servlet,就会自动跳转到静态页面。这个方法有三个参数,"index",request,response,第一个参数加上配置文件中配置的前后缀会跳转到指定的页面,后两个参数会把request和response传过去。
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()); } }
Step4:继承ViewBaseServlet
@WebServlet("/index") public class IndexServlet extends ViewBaseServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { try{ //Connection connection = JDBCUtils.getConnection(); Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo","root","niit"); System.out.println(connection); FruitDAO fruitDAO = new FruitDAOImpl(); List<Fruit> fruitList = fruitDAO.getFruitList(connection); //保存到session作用域 HttpSession session = request.getSession(); session.setAttribute("fruitList",fruitList); //此处的视图名称为index,thymeleaf会将这个逻辑视图名称对应到物理视图名称上去 //逻辑视图名称:index //物理视图名称:view-prefix + 逻辑视图名称 + view-suffix //真实的视图名称: / + index + .html super.processTemplate("index",request,response); }catch (Exception e){ e.printStackTrace(); } } }
Step5:在静态页面中使用Thymeleaf,将从数据库中拿到的集合应用到页面上。
注意添加html标签中属性 xmlns:th="http://www.thymeleaf.org"
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/css/index.css"> <title>Title</title> </head> <body> <div> <div> <table> <tr> <th class="w20">名称</th> <th class="w20">单价</th> <th class="w20">库存</th> <th>操作</th> </tr> <!-- 判断session作用域的变量是否为空--> <tr th:if="${#lists.isEmpty(session.fruitList)}"> <td colspan="4">对不起,库存为空</td> </tr> <!-- 判断session作用域中fruitList是否为空--> <!-- unless相当于else--> <!-- each相当于while标签--> <!-- 每循环一次,就将fruitList中的一个数据存给fruit-->