2022版Maven教程 - 第六章 单一架构案例

  • 一、创建工程,引入依赖
    • 1、架构
      • ①架构的概念
      • ②单一架构
    • 2、创建工程
    • 3、引入依赖
      • ①搜索依赖信息的网站
        • [1]到哪儿找?
        • [2]怎么选择?
      • ②持久化层所需依赖
      • ③表述层所需依赖
      • ④辅助功能所需依赖
      • ⑤最终完整依赖信息
    • 4、建包
  • 二、搭建环境:持久化层
    • 1、数据建模
      • ①物理建模[sql语句]
      • ②逻辑建模
        • [1] Emp 实体类
        • [2] Memorials 实体类
    • 2、数据库连接信息
    • 3、获取数据库连接
      • ①创建 JDBCUtils 工具类
      • ②创建 javax.sql.DataSource 对象
      • ③创建 ThreadLocal 对象
        • [1]提出需求
          • (1)在一个方法内控制事务
          • (2)将重复代码抽取到 Filter
          • (3)数据的跨方法传递
        • [2] ThreadLocal 对象的功能
        • [3] Java 代码
      • ④声明方法:获取数据库连接
      • ⑤声明方法:释放数据库连接
      • ⑥初步测试
      • ⑦完整代码
    • 4、BaseDao
      • ①泛型的说明
      • ②创建 QueryRunner 对象
      • ③通用增删改查方法
      • ④测试
    • 5、子类 Dao
  • 三、搭建环境:事务控制
    • 1、总体思路
    • 2、TransactionFilter
      • ①创建 Filter 类
      • ②TransactionFilter 完整代码
      • ③配置 web.xml
      • ④注意点
        • [1]确保异常回滚
        • [2]谨防数据库连接提前释放
  • 四、搭建环境:表述层
    • 1、视图模板技术 Thymeleaf
      • ①服务器端渲染
      • ②Thymeleaf 简要工作机制
        • [1]初始化阶段
        • [2]请求处理阶段
      • ③逻辑视图与物理视图
      • ④ViewBaseServlet 完整代码
      • ⑤声明初始化参数
      • ⑥Thymeleaf 的页面语法
    • 2、ModelBaseServlet
      • ①提出问题
        • [1]我们的需求
        • [2]HttpServlet 的局限
      • ②解决方案
      • ③ModelBaseServlet 完整代码
      • ④继承关系
  • 五、搭建环境:辅助功能
    • 1、常量类
    • 2、MD5 加密工具方法
    • 3、日志配置文件
  • 六、业务功能:登录
    • 1、显示首页
      • ①流程图
      • ②创建 PortalServlet
        • [1]创建 Java 类
        • [2]注册
      • ③在 index.html 中编写登录表单
      • ④测试 index.html 页面
    • 2、登录操作
      • ①流程图
      • ②创建 EmpService
      • ③创建登录失败异常
      • ④增加常量声明
      • ⑤创建 AuthServlet
        • [1]创建 Java 类
        • [2]注册
      • ⑥EmpService 方法
      • ⑦EmpDao 方法
      • ⑧临时页面
      • ⑨测试
    • 3、退出登录
      • ①在临时页面编写超链接
      • ②在 AuthServlet 编写退出逻辑
  • 七、业务功能:显示奏折列表
    • 1、流程图
    • 2、创建组件
      • ①创建 WorkServlet
        • [1]创建 Java 类
        • [2]注册
      • ②创建 MemorialsService
        • [1]接口
        • [2]实现类
    • 3、WorkServlet 方法
    • 4、MemorialsService 方法
    • 5、MemorialsDao 方法
    • 6、页面显示
      • ①页面上的样式声明
      • ②用户登录信息部分
      • ③数据展示信息部分
    • 7、和登录成功对接
  • 八、业务功能:显示奏折详情
    • 1、流程图
    • 2、调整奏折列表页面的超链接
    • 3、WorkServlet 方法
    • 4、MemorialsService 方法
    • 5、MemorialsDao 方法
    • 6、详情页
    • 7、更新状态
      • ①业务逻辑规则
      • ②WorkServlet 方法
      • ③MemorialsService 方法
      • ④MemorialsDao 方法
      • ⑤测试
  • 九、业务功能:批复奏折
    • 1、本质
    • 2、WorkServlet 方法
    • 3、MemorialsService 方法
    • 4、MemorialsDao 方法
  • 十、业务功能:登录检查
    • 1、流程图
    • 2、创建 LoginFilter
      • ①创建 Java 类
      • ②注册
  • 十一、 打包部署
    • 1、适配部署环境
    • 2、跳过测试打包
    • 3、部署执行
      • ①上传 war 包
      • ②启动 Tomcat
      • ③访问测试

视频连接
资料连接

一、创建工程,引入依赖

1、架构

①架构的概念

架构』其实就是『项目的结构』,只是因为架构是一个更大的词,通常用来形容比较大规模事物的结构。

②单一架构

单一架构也叫『all-in-one』结构,就是所有代码、配置文件、各种资源都在同一个工程。

  • 一个项目包含一个工程
  • 导出一个 war
  • 放在一个 Tomcat 上运行

2、创建工程

3、引入依赖

①搜索依赖信息的网站

[1]到哪儿找?

https://mvnrepository.com/

[2]怎么选择?

  • 确定技术选型:确定我们项目中要使用哪些技术

  • mvnrepository 网站搜索具体技术对应的具体依赖信息

  • 确定这个技术使用哪个版本的依赖

    • 考虑因素1:看是否有别的技术要求这里必须用某一个版本

    • 考虑因素2:如果没有硬性要求,那么选择较高版本或下载量大的版本


  • 在实际使用中检验所有依赖信息是否都正常可用

    确定技术选型、组建依赖列表、项目划分模块……等等这些操作其实都属于架构设计的范畴。

    • 项目本身所属行业的基本特点
    • 项目具体的功能需求
    • 项目预计访问压力程度
    • 项目预计将来需要扩展的功能
    • 设计项目总体的体系结构

②持久化层所需依赖

  • mysql:mysql-connector-java:5.1.37
  • com.alibaba:druid:1.2.8
  • commons-dbutils:commons-dbutils:1.6

③表述层所需依赖

  • javax.servlet:javax.servlet-api:3.1.0
  • org.thymeleaf:thymeleaf:3.0.11.RELEASE

④辅助功能所需依赖

  • junit:junit:4.12
  • ch.qos.logback:logback-classic:1.2.3

⑤最终完整依赖信息

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
<dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf</artifactId><version>3.0.11.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version><scope>test</scope>
</dependency>

pro05-demo-all-in-one项目的pom.xml文件:

<?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.atgugui.maven</groupId><artifactId>pro05-demo-all-in-one</artifactId><version>1.0-SNAPSHOT</version><dependencies><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils --><dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.6</version></dependency><!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf --><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf</artifactId><version>3.0.11.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/junit/junit --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency></dependencies></project>

4、建包

package 功能 package 名称
主包 com.atguigu.imperial.court
子包[实体类] com.atguigu.imperial.court.entity
子包[Servlet基类/父类包] com.atguigu.imperial.court.servlet.base
子包[Servlet模块包] com.atguigu.imperial.court.servlet.module
子包[Service接口包] com.atguigu.imperial.court.service.api
子包[Service实现类包] com.atguigu.imperial.court.service.impl
子包[Dao接口包] com.atguigu.imperial.court.dao.api
子包[Dao实现类包] com.atguigu.imperial.court.dao.impl
子包[Filter] com.atguigu.imperial.court.filter
子包[异常类包] com.atguigu.imperial.court.exception
子包[工具类] com.atguigu.imperial.court.util
子包[测试类] com.atguigu.imperial.court.test

二、搭建环境:持久化层

1、数据建模

①物理建模[sql语句]

# 创建数据库
create database db_imperial_court;# 进入数据库
use db_imperial_court;# 创建表 t_emp
create table t_emp
(emp_id         int primary key auto_increment,emp_name       varchar(100) not null,emp_position   varchar(100) not null,login_account  varchar(100) not null unique,login_password varchar(100) not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;# 插入数据
insert into t_emp(emp_name, emp_position, login_account, login_password)
values ('爱新觉罗·玄烨', 'emperor', 'xiaoxuanzi1654', '25325C896624D444B2E241807DCAC98B'), # 16540504('纳兰明珠', 'minister', 'brightball1635', 'A580D0EF93C22036C859E194C14CB777'),   # 16351119('赫舍里·索额图', 'minister', 'tutu1636', 'E40FD7D49B8B7EF46F47407D583C3538'); # 17030921# 创建表 t_memorials
create table t_memorials
(memorials_id          int primary key auto_increment,memorials_title       char(100) ,memorials_content     varchar(5000),memorials_emp         int,memorials_create_time char(100),feedback_time       char(100),feedback_content    varchar(1000),memorials_status      int           not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;# 插入数据
insert into t_memorials(memorials_title,memorials_content,memorials_emp,memorials_create_time,feedback_time,feedback_content,memorials_status)
values ('浙江巡抚奏钱塘堤决口疏', '皇上啊,不好啦!钱塘江发大水啦!堤坝冲毁啦!您看这咋弄啊!', 2, '1690-05-07', null, null, 0),('左都御史参鳌拜圈地疏', '皇上啊,鳌拜这厮不是东西呀!占老百姓的地哇!还打人呀!您看咋弄啊!', 3, '1690-04-14', null, null, 0),('都察院劾吴三桂不臣疏', '皇上啊,不得了啦!吴三桂那孙子想造反呀!', 2, '1693-11-18', null, null, 0),('兵部奏准噶尔犯境疏', '皇上啊,不得了啦!葛尔丹要打过来了呀!', 3, '1693-11-18', null, null, 0),('朝鲜使臣朝拜事宜呈皇上御览', '皇上啊!朝鲜国的人要来啦!咱们请他们吃猪肉炖粉条子吧!', 2, '1680-06-11', null, null, 0),('英吉利炮舰购买事宜疏', '皇上啊!英国的小船船咱们买多少啊?', 3, '1680-06-12', null, null, 0),('劾杭州织造贪墨疏', '皇上啊!杭州织造有问题啊!', 2, '1680-06-13', null, null, 0),('禀畅春园落成疏', '皇上啊!畅春园修好了哇!您啥时候过来看看呀!', 3, '1680-06-14', null, null, 0),('请旨木兰秋狝疏', '皇上啊!秋天到啦,又该打猎啦!', 2, '1680-06-15', null, null, 0),('核准西北军饷银两疏', '皇上啊!您看看这钱数算的对不对呀!', 3, '1680-06-16', null, null, 0),('请旨裁撤三藩疏', '皇上啊!咱们不裁撤三藩就芭比Q了哇!', 2, '1680-06-17', null, null, 0),('蒙古王公进京朝拜疏', '皇上啊!蒙古王公要来啦!咱们请他们吃猪肉炖粉条子吧!', 3, '1680-06-18', null, null, 0),('礼部请旨九阿哥赐名疏', '皇上啊!您看九阿哥该叫什么名字呀?', 2, '1680-06-19', null, null, 0),('户部尚书请旨告老还乡疏', '皇上啊!臣想回家养老啦!您看看啥时候给臣把俸禄结一下啊!', 3, '1680-06-20', null, null, 0),('查江宁织造贪墨疏', '皇上啊!江宁织造有问题啊!', 2, '1680-06-21', null, null, 0);

②逻辑建模

[1] Emp 实体类

package com.atguigu.imperial.court.entity;public class Emp {private Integer empId;private String empName;private String empPosition;private String loginAccount;private String loginPassword;//构造器 get/set 方法...
}

[2] Memorials 实体类

package com.atguigu.imperial.court.entity;public class Memorials {private Integer memorialsId;private String memorialsTitle;private String memorialsContent;// 奏折摘要数据库没有,这里是为了配合页面显示private String memorialsContentDigest;private Integer memorialsEmp;// 员工姓名数据库没有,这里是为了配合页面显示private String memorialsEmpName;private String memorialsCreateTime;private String feedbackTime;private String feedbackContent;private Integer memorialsStatus;//构造器 get/set 方法...
}

2、数据库连接信息

说明:这是我们第一次用到 Maven 约定目录结构中的 resources 目录,这个目录存放各种配置文件。

jdbc.properties文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_imperial_court
username=root
password=123456
initialSize=10
maxActive=20
maxWait=10000

3、获取数据库连接

①创建 JDBCUtils 工具类

②创建 javax.sql.DataSource 对象

package com.atguigu.imperial.court.util;import javax.sql.DataSource;
import java.io.InputStream;
import java.util.Properties;
import com.alibaba.druid.pool.DruidDataSourceFactory;/*** 功能1:从数据源获取数据库连接* 功能2:从数据库获取到数据库连接后,绑定到本地线程(借助 ThreadLocal)* 功能3:释放线程时和本地线程解除绑定*/
public class JDBCUtils {// 数据源成员变量设置成静态资源,保证对象的单例性;同时保证静态方法中可以访问private static DataSource dataSource;// 在静态代码块中初始化数据源static {//操作思路分析:// 从 jdbc.properties 文件中读取连接数据库的信息// 为了保证程序代码的可移植性,需要基于一个确定的基准来读取这个文件// 确定的基准: 类路径的根目录。resources 目录下的内容经过构建操作中打包操作后确定放在//  WEB-INFO/classes 目录下。 WEB-INFO/classes : 目录存放编译好的 *.class 字节码文件,//  所以这个目录我们就称之为类路径。// 类路径无论在本地运行还是在服务器端运行都是一个确定的基准。//操作具体实现:try {//1、获取当前类的类加载器ClassLoader classLoader = JDBCUtils.class.getClassLoader();//2、通过类加载器对象从类路径根目录下读取文件InputStream stream = classLoader.getResourceAsStream("jdbc.properties");//3、使用 Properties 类封装属性文件中的数据Properties properties = new Properties();properties.load(stream);//4、根据 Properties 对象(已经封装了数据库连接信息)来创建数据源对象dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();//为了避免在真正抛出异常后, catch 块捕获到异常从而掩盖问题,//这里将所捕获到的异常封装为运行异常继续抛出throw new RuntimeException(e);}}
}

③创建 ThreadLocal 对象

[1]提出需求

(1)在一个方法内控制事务

如果在每一个 Service 方法中都写下面代码,那么代码重复性就太高了:

try{// 1、获取数据库连接// 重要:要保证参与事务的多个数据库操作(SQL 语句)使用的是同一个数据库连接Connection conn = JDBCUtils.getConnection();// 2、核心操作// ...// 3、核心操作成功结束,可以提交事务conn.commit();}catch(Exception e){// 4、核心操作抛出异常,必须回滚事务conn.rollBack();
}finally{// 5、释放数据库连接JDBCUtils.releaseConnection(conn);
}
(2)将重复代码抽取到 Filter

所谓『当前请求覆盖的 Servlet 方法、Service 方法、Dao 方法』其实就是 chain.doFilter(request, response) 间接调用的方法。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){try{// 1、获取数据库连接// 重要:要保证参与事务的多个数据库操作(SQL 语句)使用的是同一个数据库连接Connection conn = JDBCUtils.getConnection();// 重要操作:关闭自动提交功能connection.setAutoCommit(false);// 2、核心操作:通过 chain 对象放行当前请求// 这样就可以保证当前请求覆盖的 Servlet 方法、Service 方法、Dao 方法都在同一个事务中。// 同时各个请求都经过这个 Filter,所以当前事务控制的代码在这里只写一遍就行了,// 避免了代码的冗余。chain.doFilter(request, response);// 3、核心操作成功结束,可以提交事务conn.commit();}catch(Exception e){// 4、核心操作抛出异常,必须回滚事务conn.rollBack();}finally{// 5、释放数据库连接JDBCUtils.releaseConnection(conn);}}
(3)数据的跨方法传递

通过 JDBCUtils 工具类获取到的 Connection 对象需要传递给 Dao 方法,让事务涉及到的所有 Dao 方法用的都是同一个 Connection 对象。

但是 Connection 对象无法通过 chain.doFilter() 方法以参数的形式传递过去。

所以从获取到 Connection 对象到使用 Connection 对象中间隔着很多不是我们自己声明的方法——我们无法决定它们的参数。

[2] ThreadLocal 对象的功能

  • 全类名:java.lang.ThreadLocal<T>
  • 泛型 T:要绑定到当前线程的数据的类型
  • 具体三个主要的方法:
方法名 功能
set(T value) 将数据绑定到当前线程
get() 从当前线程获取已绑定的数据
remove() 将数据从当前线程移除

[3] Java 代码

// 由于 ThreadLocal 对象需要作为绑定数据时 k-v 对中的 key,所以要保证唯一性
// 加 static 声明为静态资源即可保证唯一性
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

④声明方法:获取数据库连接

JDBCUtils类添加getConnection()方法

 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();/*** 工具方法:获取数据库连接并返回* @return*/public static Connection getConnection(){Connection connection = null;try {// 1、尝试从当前线程检查是否存在已经绑定的 Connection 对象connection = threadLocal.get();// 2、检查 Connection 对象是否为 nullif (connection == null) {// 3、如果为 null,则从数据源获取数据库连接connection = dataSource.getConnection();// 4、获取到数据库连接后绑定到当前线程threadLocal.set(connection);}} catch (SQLException e) {e.printStackTrace();// 为了调用工具方法方便,编译时异常不往外抛// 为了不掩盖问题,捕获到的编译时异常封装为运行时异常抛出throw new RuntimeException(e);}return connection;}

⑤声明方法:释放数据库连接

JDBCUtils类添加releaseConnection()方法

/*** 释放数据库连接*/
public static void releaseConnection(Connection connection) {if (connection != null) {try {// 在数据库连接池中将当前连接对象标记为空闲connection.close();// 将当前数据库连接从当前线程上移除threadLocal.remove();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e);}}
}

⑥初步测试

package com.atguigu.maven;import com.atguigu.imperial.court.util.JDBCUtils;
import org.junit.Test;import java.sql.Connection;public class ImperialCourtTest {@Testpublic void testGetConnection() {Connection connection = JDBCUtils.getConnection();System.out.println("connection = " + connection);JDBCUtils.releaseConnection(connection);}
}

⑦完整代码

package com.atguigu.imperial.court.util;import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import com.alibaba.druid.pool.DruidDataSourceFactory;/*** 功能1:从数据源获取数据库连接* 功能2:从数据库获取到数据库连接后,绑定到本地线程(借助 ThreadLocal)* 功能3:释放线程时和本地线程解除绑定*/
public class JDBCUtils {// 数据源成员变量设置成静态资源,保证对象的单例性;同时保证静态方法中可以访问private static DataSource dataSource;// 由于 ThreadLocal 对象需要作为绑定数据时 k-v 对中的 key,所以要保证唯一性// 加 static 声明为静态资源即可保证唯一性private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();// 在静态代码块中初始化数据源static {//操作思路分析:// 从 jdbc.properties 文件中读取连接数据库的信息// 为了保证程序代码的可移植性,需要基于一个确定的基准来读取这个文件// 确定的基准: 类路径的根目录。resources 目录下的内容经过构建操作中打包操作后确定放在//  WEB-INFO/classes 目录下。 WEB-INFO/classes : 目录存放编译好的 *.class 字节码文件,//  所以这个目录我们就称之为类路径。// 类路径无论在本地运行还是在服务器端运行都是一个确定的基准。//操作具体实现:try {//1、获取当前类的类加载器ClassLoader classLoader = JDBCUtils.class.getClassLoader();//2、通过类加载器对象从类路径根目录下读取文件InputStream stream = classLoader.getResourceAsStream("jdbc.properties");//3、使用 Properties 类封装属性文件中的数据Properties properties = new Properties();properties.load(stream);//4、根据 Properties 对象(已经封装了数据库连接信息)来创建数据源对象dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();//为了避免在真正抛出异常后, catch 块捕获到异常从而掩盖问题,//这里将所捕获到的异常封装为运行异常继续抛出throw new RuntimeException(e);}}/*** 工具方法:获取数据库连接并返回* @return*/public static Connection getConnection(){Connection connection = null;try {// 1、尝试从当前线程检查是否存在已经绑定的 Connection 对象connection = threadLocal.get();// 2、检查 Connection 对象是否为 nullif (connection == null) {// 3、如果为 null,则从数据源获取数据库连接connection = dataSource.getConnection();// 4、获取到数据库连接后绑定到当前线程threadLocal.set(connection);}} catch (SQLException e) {e.printStackTrace();// 为了调用工具方法方便,编译时异常不往外抛// 为了不掩盖问题,捕获到的编译时异常封装为运行时异常抛出throw new RuntimeException(e);}return connection;}/*** 释放数据库连接*/public static void releaseConnection(Connection connection) {if (connection != null) {try {// 在数据库连接池中将当前连接对象标记为空闲connection.close();// 将当前数据库连接从当前线程上移除threadLocal.remove();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e);}}}
}

4、BaseDao

①泛型的说明

②创建 QueryRunner 对象

// DBUtils 工具包提供的数据库操作对象
private QueryRunner runner = new QueryRunner();

③通用增删改查方法

特别说明:在 BaseDao 方法中获取数据库连接但是不做释放,因为我们要在控制事务的 Filter 中统一释放。

package com.atguigu.imperial.court.dao;import com.atguigu.imperial.court.util.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;/*** BaseDao 类:所有 Dao 实现类的基类/父类* @param <T> 实体类的类型*/
public class BaseDao<T> {// DBUtils 工具包提供的数据库操作对象private QueryRunner runner = new QueryRunner();/*** 查询单个对象* @param sql 执行查询的 sql 语句* @param entityClass 实体类对应的 Class 对象* @param parameters 传给 SQL 语句的参数* @return 查询到的实体类对象*/public T getSingleBean(String sql,Class<T> entityClass,Object ... parameters){try {// 获取数据库的连接Connection connection = JDBCUtils.getConnection();return runner.query(connection,sql,new BeanHandler<T>(entityClass),parameters);}catch (Exception e){e.printStackTrace();// 如果真的抛出异常,则将编译异常封装为运行异常抛出new RuntimeException(e);}return null;}/*** 查询返回多个对象的方法* @param sql 执行查询操作的 SQL 语句* @param entityClass 实体类的 Class 对象* @param parameters SQL 语句的参数* @return 查询结果*/public List<T> getBeanList(String sql, Class<T> entityClass, Object ... parameters) {try {// 获取数据库连接Connection connection = JDBCUtils.getConnection();return runner.query(connection, sql, new BeanListHandler<T>(entityClass), parameters);} catch (SQLException e) {e.printStackTrace();// 如果真的抛出异常,则将编译时异常封装为运行时异常抛出new RuntimeException(e);}return null;}/*** 通用的增删改方法,insert、delete、update 操作都可以用这个方法* @param sql 执行操作的 SQL 语句* @param parameters SQL 语句的参数* @return 受影响的行数*/public int update(String sql, Object ... parameters) {try {Connection connection = JDBCUtils.getConnection();int affectedRowNumbers = runner.update(connection, sql, parameters);return affectedRowNumbers;} catch (SQLException e) {e.printStackTrace();// 如果真的抛出异常,则将编译时异常封装为运行时异常抛出new RuntimeException(e);return 0;}}}

④测试

ImperialCourtTest类下进行测试

    private BaseDao<Emp> baseDao = new BaseDao<Emp>();@Testpublic void testGetSingleBean() {String sql = "select emp_id empId,emp_name empName,emp_position empPosition,login_account loginAccount,login_password loginPassword from t_emp where emp_id=?";Emp emp = baseDao.getSingleBean(sql, Emp.class, 1);System.out.println("emp = " + emp);}@Testpublic void testGetBeanList() {String sql = "select emp_id empId,emp_name empName,emp_position empPosition,login_account loginAccount,login_password loginPassword from t_emp";List<Emp> empList = baseDao.getBeanList(sql, Emp.class);for (Emp emp : empList) {System.out.println("emp = " + emp);}}@Testpublic void testUpdate() {String sql = "update t_emp set emp_position=? where emp_id=?";String empPosition = "emperor";String empId = "3";int affectedRowNumber = baseDao.update(sql, empPosition, empId);System.out.println("affectedRowNumber = " + affectedRowNumber);}

5、子类 Dao

创建接口和实现类如下:

三、搭建环境:事务控制

1、总体思路

2、TransactionFilter

①创建 Filter 类

②TransactionFilter 完整代码

package com.atguigu.imperial.court.filter;import com.atguigu.imperial.court.util.JDBCUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;public class TransactionFilter implements Filter {// 声明集合保存静态资源扩展名private static Set<String> staticResourceExtNameSet;static {staticResourceExtNameSet = new HashSet<String>();staticResourceExtNameSet.add(".png");staticResourceExtNameSet.add(".jpg");staticResourceExtNameSet.add(".css");staticResourceExtNameSet.add(".js");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 前置操作:排除静态资源HttpServletRequest request = (HttpServletRequest) servletRequest;String servletPath = request.getServletPath();if (servletPath.contains(".")) {String extName = servletPath.substring(servletPath.lastIndexOf("."));if (staticResourceExtNameSet.contains(extName)) {// 如果检测到当前请求确实是静态资源,则直接放行,不做事务操作filterChain.doFilter(servletRequest, servletResponse);// 当前方法立即返回return ;}}Connection connection = null;try{// 1、获取数据库连接connection = JDBCUtils.getConnection();// 重要操作:关闭自动提交功能connection.setAutoCommit(false);// 2、核心操作filterChain.doFilter(servletRequest, servletResponse);// 3、提交事务connection.commit();}catch (Exception e) {try {// 4、回滚事务connection.rollback();} catch (SQLException ex) {ex.printStackTrace();}// 页面显示:将这里捕获到的异常发送到指定页面显示// 获取异常信息String message = e.getMessage();// 将异常信息存入请求域request.setAttribute("systemMessage", message);// 将请求转发到指定页面request.getRequestDispatcher("/").forward(request, servletResponse);}finally {// 5、释放数据库连接JDBCUtils.releaseConnection(connection);}}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}
}

③配置 web.xml

注意:需要首先将当前工程改成 Web 工程。

pom文件添加:

<packaging>war</packaging>

然后在idea工具操作,如下:


web.xml文件添加filter标签

<filter><filter-name>txFilter</filter-name><filter-class>com.atguigu.imperial.court.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping><filter-name>txFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

④注意点

[1]确保异常回滚

在程序执行的过程中,必须让所有 catch 块都把编译时异常转换为运行时异常抛出;如果不这么做,在 TransactionFiltercatch 就无法捕获到底层抛出的异常,那么该回滚的时候就无法回滚。

[2]谨防数据库连接提前释放

由于诸多操作都是在使用同一个数据库连接,那么中间任何一个环节释放数据库连接都会导致后续操作无法正常完成。

四、搭建环境:表述层

1、视图模板技术 Thymeleaf

①服务器端渲染

参考资料

②Thymeleaf 简要工作机制

[1]初始化阶段

  • 目标:创建 TemplateEngine 对象(模板引擎)
  • 封装:因为对每一个请求来说,TemplateEngine 对象使用的都是同一个,所以在初始化阶段准备好

[2]请求处理阶段

③逻辑视图与物理视图

假设有下列页面地址:

/WEB-INF/pages/apple.html
/WEB-INF/pages/banana.html
/WEB-INF/pages/orange.html
/WEB-INF/pages/grape.html
/WEB-INF/pages/egg.html

这样的地址可以直接访问到页面本身,我们称之为:物理视图。而将物理视图中前面、后面的固定内容抽取出来,让每次请求指定中间变化部分即可,那么中间变化部分就叫:逻辑视图

④ViewBaseServlet 完整代码

为了简化视图页面处理过程,我们将 Thymeleaf 模板引擎的初始化和请求处理过程封装到一个 Servlet 基类中:ViewBaseServlet。以后负责具体模块业务功能的 Servlet 继承该基类即可直接使用。

package com.atguigu.imperial.court.servlet.base;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;/*** 处理视图模板文件的 Servlet 基类/父类*/
public class ViewBaseServlet extends HttpServlet {private TemplateEngine templateEngine;@Overridepublic 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(60 * 1000L);// ⑤设置是否缓存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());}
}

特别提醒:这个类不需要掌握,因为以后都被框架封装了,我们现在只是暂时用一下。

⑤声明初始化参数

<!-- 配置 Web 应用初始化参数指定视图前缀、后缀 -->
<!-- 物理视图举例:/WEB-INF/pages/index.html对应逻辑视图:index
-->
<context-param><param-name>view-prefix</param-name><param-value>/WEB-INF/pages/</param-value>
</context-param>
<context-param><param-name>view-suffix</param-name><param-value>.html</param-value>
</context-param>

⑥Thymeleaf 的页面语法

传送门

2、ModelBaseServlet

①提出问题

[1]我们的需求

[2]HttpServlet 的局限

  • doGet() 方法:处理 GET 请求
  • doPost() 方法:处理 POST 请求

②解决方案

  • 每个请求附带一个请求参数,表明自己要调用的目标方法
  • Servlet 根据目标方法名通过反射调用目标方法

③ModelBaseServlet 完整代码

特别提醒:为了配合 TransactionFilter 实现事务控制,捕获的异常必须抛出

package com.atguigu.imperial.court.servlet.base;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;public class ModelBaseServlet extends ViewBaseServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 在doGet()方法中调用doPost()方法,这样就可以在doPost()方法中集中处理所有请求doPost(request, response);}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1.在所有request.getParameter()前面设置解析请求体的字符集request.setCharacterEncoding("UTF-8");// 2.从请求参数中获取method对应的数据String method = request.getParameter("method");// 3.通过反射调用method对应的方法// ①获取Class对象Class<? extends ModelBaseServlet> clazz = this.getClass();try {// ②获取method对应的Method对象Method methodObject = clazz.getDeclaredMethod(method, HttpServletRequest.class, HttpServletResponse.class);// ③打开访问权限methodObject.setAccessible(true);// ④通过Method对象调用目标方法methodObject.invoke(this, request, response);} catch (Exception e) {e.printStackTrace();// **重要提醒:为了配合 TransactionFilter 实现事务控制,捕获的异常必须抛出。**throw new RuntimeException(e);}}
}

④继承关系

五、搭建环境:辅助功能

1、常量类

package com.atguigu.imperial.court.util;public class ImperialCourtConst {public static final String LOGIN_FAILED_MESSAGE = "账号、密码错误,不可进宫!";public static final String ACCESS_DENIED_MESSAGE = "宫闱禁地,不得擅入!";
}

2、MD5 加密工具方法

package com.atguigu.imperial.court.util;import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class MD5Util {/*** 针对明文字符串执行MD5加密* @param source* @return*/public static String encode(String source) {// 1.判断明文字符串是否有效if (source == null || "".equals(source)) {throw new RuntimeException("用于加密的明文不可为空");}// 2.声明算法名称String algorithm = "md5";// 3.获取MessageDigest对象MessageDigest messageDigest = null;try {messageDigest = MessageDigest.getInstance(algorithm);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}// 4.获取明文字符串对应的字节数组byte[] input = source.getBytes();// 5.执行加密byte[] output = messageDigest.digest(input);// 6.创建BigInteger对象int signum = 1;BigInteger bigInteger = new BigInteger(signum, output);// 7.按照16进制将bigInteger的值转换为字符串int radix = 16;String encoded = bigInteger.toString(radix).toUpperCase();return encoded;}
}

3、日志配置文件

resources目录下创建logback.xml日记配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true"><!-- 指定日志输出的位置 --><appender name="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- 日志输出的格式 --><!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --><pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern><charset>UTF-8</charset></encoder></appender><!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --><!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --><root level="INFO"><!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --><appender-ref ref="STDOUT" /></root><!-- 专门给某一个包指定日志级别 --><logger name="com.atguigu" level="DEBUG" additivity="false"><appender-ref ref="STDOUT" /></logger></configuration>

六、业务功能:登录

1、显示首页

①流程图

②创建 PortalServlet

[1]创建 Java 类

package com.atguigu.imperial.court.servlet.module;import com.atguigu.imperial.court.servlet.base.ViewBaseServlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class PortalServlet extends ViewBaseServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 声明要访问的首页的逻辑视图String templateName = "index";// 调用父类的方法根据逻辑视图名称渲染视图processTemplate(templateName, req, resp);}
}

[2]注册

webapp目录下 web.xml文件添加:

<servlet><servlet-name>portalServlet</servlet-name><servlet-class>com.atguigu.imperial.court.servlet.module.PortalServlet</servlet-class>
</servlet>
<servlet-mapping><servlet-name>portalServlet</servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>

③在 index.html 中编写登录表单

<!DOCTYPE html>
<html lang="en" xml:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>乾清宫</title>
</head>
<body>
<!-- @{/auth} 解析后:/demo/auth -->
<form th:action="@{/auth}" method="post"><!-- 传递 method 请求参数,目的是为了让当前请求调用 AuthServlet 中的 login() 方法 --><input type="hidden" name="method" value="login" /><!-- th:text 解析表达式后会替换标签体 --><!-- ${attrName} 从请求域获取属性名为 attrName 的属性值 --><p style="color: red; font-weight: bold;" th:text="${message}"></p><p style="color: red; font-weight: bold;" th:text="${systemMessage}"></p>账号:<input type="text" name="loginAccount"/><br/>密码:<input type="password" name="loginPassword"><br/><button type="submit">进宫</button>
</form>
</body>
</html>

④测试 index.html 页面




注意:这里没有 Artifact 选项:
操作如下:




效果:




页面效果:

2、登录操作

①流程图

②创建 EmpService

③创建登录失败异常

package com.atguigu.imperial.court.exception;public class LoginFailedException extends RuntimeException {public LoginFailedException() {}public LoginFailedException(String message) {super(message);}public LoginFailedException(String message, Throwable cause) {super(message, cause);}public LoginFailedException(Throwable cause) {super(cause);}public LoginFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}

④增加常量声明

public class ImperialCourtConst {public static final String LOGIN_FAILED_MESSAGE = "账号、密码错误,不可进宫!";public static final String ACCESS_DENIED_MESSAGE = "宫闱禁地,不得擅入!";public static final String LOGIN_EMP_ATTR_NAME = "loginInfo";
}

⑤创建 AuthServlet

[1]创建 Java 类

package com.atguigu.imperial.court.servlet.module;import com.atguigu.imperial.court.entity.Emp;
import com.atguigu.imperial.court.exception.LoginFailedException;
import com.atguigu.imperial.court.service.api.EmpService;
import com.atguigu.imperial.court.service.impl.EmpServiceImpl;
import com.atguigu.imperial.court.servlet.base.ModelBaseServlet;
import com.atguigu.imperial.court.util.ImperialCourtConst;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;public class AuthServlet extends ModelBaseServlet {private EmpService empService = new EmpServiceImpl();protected void login(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {try {// 1、获取请求参数String loginAccount = request.getParameter("loginAccount");String loginPassword = request.getParameter("loginPassword");// 2、调用 EmpService 方法执行登录逻辑Emp emp = empService.getEmpByLoginAccount(loginAccount, loginPassword);// 3、通过 request 获取 HttpSession 对象HttpSession session = request.getSession();// 4、将查询到的 Emp 对象存入 Session 域session.setAttribute(ImperialCourtConst.LOGIN_EMP_ATTR_NAME, emp);// 5、前往指定页面视图String templateName = "temp";processTemplate(templateName, request, response);} catch (Exception e) {e.printStackTrace();// 6、判断此处捕获到的异常是否是登录失败异常if (e instanceof LoginFailedException) {// 7、如果是登录失败异常则跳转回登录页面// ①将异常信息存入请求域request.setAttribute("message", e.getMessage());// ②处理视图:indexprocessTemplate("index", request, response);}else {// 8、如果不是登录异常则封装为运行时异常继续抛出throw new RuntimeException(e);}}}
}

[2]注册

<servlet><servlet-name>authServlet</servlet-name><servlet-class>com.atguigu.imperial.court.servlet.module.AuthServlet</servlet-class>
</servlet>
<servlet-mapping><servlet-name>authServlet</servlet-name><url-pattern>/auth</url-pattern>
</servlet-mapping>

⑥EmpService 方法

public class EmpServiceImpl implements EmpService {private EmpDao empDao = new EmpDaoImpl();@Overridepublic Emp getEmpByLoginAccount(String loginAccount, String loginPassword) {// 1、对密码执行加密String encodedLoginPassword = MD5Util.encode(loginPassword);// 2、根据账户和加密密码查询数据库Emp emp = empDao.selectEmpByLoginAccount(loginAccount, encodedLoginPassword);// 3、检查 Emp 对象是否为 nullif (emp != null) {// ①不为 null:返回 Empreturn emp;} else {// ②为 null:抛登录失败异常throw new LoginFailedException(ImperialCourtConst.LOGIN_FAILED_MESSAGE);}}
}

⑦EmpDao 方法

public class EmpDaoImpl extends BaseDao<Emp> implements EmpDao {@Overridepublic Emp selectEmpByLoginAccount(String loginAccount, String encodedLoginPassword) {// 1、编写 SQL 语句String sql = "select emp_id empId," +"emp_name empName," +"emp_position empPosition," +"login_account loginAccount," +"login_password loginPassword " +"from t_emp where login_account=? and login_password=?";// 2、调用父类方法查询单个对象return super.getSingleBean(sql, Emp.class, loginAccount, encodedLoginPassword);}
}

⑧临时页面

<!DOCTYPE html>
<html lang="en" xml:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>临时</title>
</head>
<body><p th:text="${session.loginInfo}"></p></body>
</html>

⑨测试

登录成功:

登录失败:

3、退出登录

①在临时页面编写超链接

<a th:href="@{/auth?method=logout}">退朝</a>

②在 AuthServlet 编写退出逻辑

protected void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1、通过 request 对象获取 HttpSession 对象HttpSession session = request.getSession();// 2、将 HttpSession 对象强制失效session.invalidate();// 3、回到首页String templateName = "index";processTemplate(templateName, request, response);
}

测试:

七、业务功能:显示奏折列表

1、流程图

2、创建组件

①创建 WorkServlet

[1]创建 Java 类

刚开始是空的,还没有写方法:

public class WorkServlet extends ModelBaseServlet {private MemorialsService memorialsService = new MemorialsServiceImpl();
}

[2]注册

<servlet><servlet-name>workServlet</servlet-name><servlet-class>com.atguigu.imperial.court.servlet.module.WorkServlet</servlet-class>
</servlet>
<servlet-mapping><servlet-name>workServlet</servlet-name><url-pattern>/work</url-pattern>
</servlet-mapping>

②创建 MemorialsService

[1]接口

[2]实现类

package com.atguigu.imperial.court.service.impl;import com.atguigu.imperial.court.dao.api.MemorialsDao;
import com.atguigu.imperial.court.dao.impl.MemorialsDaoImpl;
import com.atguigu.imperial.court.service.api.MemorialsService;public class MemorialsServiceImpl implements MemorialsService {private MemorialsDao memorialsDao = new MemorialsDaoImpl();
}

3、WorkServlet 方法

public class WorkServlet extends ModelBaseServlet {private MemorialsService memorialsService = new MemorialsServiceImpl();protected void showMemorialsDigestList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1、调用 Service 方法查询数据List<Memorials> memorialsList = memorialsService.getAllMemorialsDigest();// 2、将查询得到的数据存入请求域request.setAttribute("memorialsList", memorialsList);// 3、渲染视图String templateName = "memorials-list";processTemplate(templateName, request, response);}
}

4、MemorialsService 方法

public class MemorialsServiceImpl implements MemorialsService {private MemorialsDao memorialsDao = new MemorialsDaoImpl();@Overridepublic List<Memorials> getAllMemorialsDigest() {return memorialsDao.selectAllMemorialsDigest();}
}

5、MemorialsDao 方法

public class MemorialsDaoImpl extends BaseDao<Memorials> implements MemorialsDao {@Overridepublic List<Memorials> selectAllMemorialsDigest() {String sql = "select memorials_id memorialsId,\n" +"       memorials_title memorialsTitle,\n" +// 取左边的10个字符,后面拼上..."       concat(left(memorials_content, 10), \"...\") memorialsContentDigest,\n" +// t_emp表字段的 emp_name"       emp_name memorialsEmpName,\n" +"       memorials_create_time memorialsCreateTime,\n" +"       memorials_status memorialsStatus\n" +" from t_memorials m left join  t_emp e on m.memorials_emp=e.emp_id;";return getBeanList(sql, Memorials.class);}
}

6、页面显示

①页面上的样式声明

<head><meta charset="UTF-8"><title>奏折列表</title><style type="text/css">table {border-collapse: collapse;margin: 0px auto 0px auto;}table th, td {border: 1px solid black;text-align: center;}div {text-align: right;}</style>
</head>

②用户登录信息部分

<body><!-- 登录信息部分 --><div><span th:if="${session.loginInfo.empPosition == 'emperor'}">恭请皇上圣安</span><span th:if="${session.loginInfo.empPosition == 'minister'}">给<span th:text="${session.loginInfo.empName}">XXX</span>大人请安</span><a th:href="@{/auth?method=logout}">退朝</a></div>
</body>

③数据展示信息部分

<body><!-- 登录信息部分 -->...<!-- 数据显示部分 --><table><thead><tr><th>奏折标题</th><th>内容摘要</th><th>上疏大臣</th><th>上疏时间</th><th>奏折状态</th><th>奏折详情</th></tr></thead><tbody th:if="${#lists.isEmpty(memorialsList)}"><tr><td colspan="6">没有人上过折子</td></tr></tbody><tbody th:if="${not #lists.isEmpty(memorialsList)}"><tr th:each="memorials : ${memorialsList}"><td th:switch="${memorials.memorialsStatus}"><span th:text="${memorials.memorialsTitle}" th:case="0" style="color: red;">奏折标题</span><span th:text="${memorials.memorialsTitle}" th:case="1" style="color: blue;">奏折标题</span><span th:text="${memorials.memorialsTitle}" th:case="2">奏折标题</span></td><td th:switch="${memorials.memorialsStatus}"><span th:text="${memorials.memorialsContentDigest}" th:case="0" style="color: red;">内容摘要</span><span th:text="${memorials.memorialsContentDigest}" th:case="1" style="color: blue;">内容摘要</span><span th:text="${memorials.memorialsContentDigest}" th:case="2">内容摘要</span></td><td th:switch="${memorials.memorialsStatus}"><span th:text="${memorials.memorialsEmpName}" th:case="0" style="color: red;">上疏大臣</span><span th:text="${memorials.memorialsEmpName}" th:case="1" style="color: blue;">上疏大臣</span><span th:text="${memorials.memorialsEmpName}" th:case="2">上疏大臣</span></td><td th:switch="${memorials.memorialsStatus}"><span th:text="${memorials.memorialsCreateTime}" th:case="0" style="color: red;">上疏时间</span><span th:text="${memorials.memorialsCreateTime}" th:case="1" style="color: blue;">上疏时间</span><span th:text="${memorials.memorialsCreateTime}" th:case="2">上疏时间</span></td><td th:switch="${memorials.memorialsStatus}"><span th:case="0" style="color: red;">未读</span><span th:case="1" style="color: blue;">已读</span><span th:case="2">已批示</span></td><td><a th:href="@{/work?method=detail}">奏折详情</a></td></tr></tbody></table>
</body>

测试:
先登录成后,在地址栏输入http://localhost:8080/demo/work?method=showMemorialsDigestList


7、和登录成功对接

修改AuthServlet类的login()方法

// 5、前往指定页面视图
// 前往临时页面
// String templateName = "temp";
// processTemplate(templateName, request, response);// 前往正式的目标地址
response.sendRedirect(request.getContextPath() + "/work?method=showMemorialsDigestList");

测试:

八、业务功能:显示奏折详情

1、流程图

2、调整奏折列表页面的超链接

/webapp/WEB-INFO/pages/目录下memorials-list.html文件

<a th:href="@{/work(method='showMemorialsDetail',memorialsId=${memorials.memorialsId})}">奏折详情</a>

3、WorkServlet 方法

WorkServlet 类添加showMemorialsDetail()方法

protected void showMemorialsDetail(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {// 1、从请求参数读取 memorialsIdString memorialsId = request.getParameter("memorialsId");// 2、根据 memorialsId 从 Service 中查询 Memorials 对象Memorials memorials = memorialsService.getMemorialsDetailById(memorialsId);// 3、将 Memorials 对象存入请求域request.setAttribute("memorials", memorials);// 4、解析渲染视图String templateName = "memorials_detail";processTemplate(templateName, request, response);
}

4、MemorialsService 方法

public class MemorialsServiceImpl implements MemorialsService {private MemorialsDao memorialsDao = new MemorialsDaoImpl();@Overridepublic Memorials getMemorialsDetailById(String memorialsId) {return memorialsDao.selectMemorialsById(memorialsId);}
}

5、MemorialsDao 方法

public class MemorialsDaoImpl extends BaseDao<Memorials> implements MemorialsDao {@Overridepublic Memorials selectMemorialsById(String memorialsId) {String sql = "select memorials_id memorialsId,\n" +"       memorials_title memorialsTitle,\n" +"       memorials_content memorialsContent,\n" +"       emp_name memorialsEmpName,\n" +"       memorials_create_time memorialsCreateTime,\n" +"       memorials_status memorialsStatus,\n" +"       feedback_time feedbackTime,\n" +"       feedback_content feedbackContent\n" +"from t_memorials m left join  t_emp e on m.memorials_emp=e.emp_id " +"where memorials_id=?;";return getSingleBean(sql, Memorials.class, memorialsId);}
}

6、详情页

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>奏折详情</title><style type="text/css">table {border-collapse: collapse;margin: 0px auto 0px auto;width: 70%;}table th, td {border: 1px solid black;text-align: center;}div {text-align: right;}</style>
</head>
<body><!-- 登录信息部分 --><div><span th:if="${session.loginInfo.empPosition == 'emperor'}">恭请皇上圣安</span><span th:if="${session.loginInfo.empPosition == 'minister'}">给<span th:text="${session.loginInfo.empName}">XXX</span>大人请安</span><a th:href="@{/auth?method=logout}">退朝</a></div><table><tr><td>奏折标题</td><td th:text="${memorials.memorialsTitle}"></td></tr><tr><td>上疏大臣</td><td th:text="${memorials.memorialsEmpName}"></td></tr><tr><td>上疏时间</td><td th:text="${memorials.memorialsCreateTime}"></td></tr><tr><td>奏折内容</td><td th:text="${memorials.memorialsContent}"></td></tr><tr th:if="${memorials.memorialsStatus == 2}"><td>批复时间</td><td th:text="${memorials.feedbackTime}"></td></tr><tr th:if="${memorials.memorialsStatus == 2}"><td>批复内容</td><td th:text="${memorials.feedbackContent}"></td></tr></table><div th:if="${memorials.memorialsStatus != 2}"><form th:action="@{/work}" method="post"><input type="hidden" name="method" value="feedBack" /><input type="hidden" name="memorialsId" th:value="${memorials.memorialsId}"/><textarea name="feedbackContent"></textarea><button type="submit">御批</button></form></div><a th:href="@{/work?method=showMemorialsDigestList}">返回列表</a></body>
</html>

测试效果:

7、更新状态

①业务逻辑规则

一份未读奏折,点击查看后,需要从未读变成已读。

②WorkServlet 方法

WorkServlet 类修改showMemorialsDetail()方法,增加判断:

protected void showMemorialsDetail(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {// 1、从请求参数读取 memorialsIdString memorialsId = request.getParameter("memorialsId");// 2、根据 memorialsId 从 Service 中查询 Memorials 对象Memorials memorials = memorialsService.getMemorialsDetailById(memorialsId);// **********************补充功能**********************// 获取当前奏折对象的状态Integer memorialsStatus = memorials.getMemorialsStatus();// 判断奏折状态if (memorialsStatus == 0) {// 更新奏折状态:数据库修改memorialsService.updateMemorialsStatusToRead(memorialsId);// 更新奏折状态:当前对象修改memorials.setMemorialsStatus(1);}// **********************补充功能**********************// 3、将 Memorials 对象存入请求域request.setAttribute("memorials", memorials);// 4、解析渲染视图String templateName = "memorials_detail";processTemplate(templateName, request, response);
}

③MemorialsService 方法

public class MemorialsServiceImpl implements MemorialsService {private MemorialsDao memorialsDao = new MemorialsDaoImpl();@Overridepublic void updateMemorialsStatusToRead(String memorialsId) {memorialsDao.updateMemorialsStatusToRead(memorialsId);}
}

④MemorialsDao 方法

public class MemorialsDaoImpl extends BaseDao<Memorials> implements MemorialsDao {@Overridepublic void updateMemorialsStatusToRead(String memorialsId) {String sql = "update t_memorials set memorials_status=1 where memorials_id=?";update(sql, memorialsId);}
}

⑤测试

上面的御批表格可以改下:在memorials_detail.html修改一下

    <div style="margin: 0 auto 0 auto;width: 60%;" th:if="${memorials.memorialsStatus != 2}"><form th:action="@{/work}" method="post"><input type="hidden" name="method" value="feedBack" /><input type="hidden" name="memorialsId" th:value="${memorials.memorialsId}"/><textarea name="feedbackContent" style="width: 500px;height: 200px"></textarea><button type="submit">御批</button></form></div>

效果:

九、业务功能:批复奏折

1、本质

提交表单,更新数据。

2、WorkServlet 方法

WorkServlet 类添加feedBack()方法

protected void feedBack(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 获取表单提交的请求参数String memorialsId = request.getParameter("memorialsId");String feedbackContent = request.getParameter("feedbackContent");// 执行更新memorialsService.updateMemorialsFeedBack(memorialsId, feedbackContent);// 重定向回显示奏折列表页面response.sendRedirect(request.getContextPath() + "/work?method=showMemorialsDigestList");
}

3、MemorialsService 方法

public class MemorialsServiceImpl implements MemorialsService {private MemorialsDao memorialsDao = new MemorialsDaoImpl();@Overridepublic void updateMemorialsFeedBack(String memorialsId, String feedbackContent) {memorialsDao.updateMemorialsFeedBack(memorialsId, feedbackContent);}
}

4、MemorialsDao 方法

public class MemorialsDaoImpl extends BaseDao<Memorials> implements MemorialsDao {@Overridepublic void updateMemorialsFeedBack(String memorialsId, String feedbackContent) {String feedbackTime = new SimpleDateFormat("yyyy-MM-dd").format(new Date());String sql = "update t_memorials set memorials_status=2,feedback_content=?,feedback_time=? where memorials_id=?";update(sql, feedbackContent, feedbackTime, memorialsId);}
}

十、业务功能:登录检查

1、流程图

2、创建 LoginFilter

①创建 Java 类

package com.atguigu.imperial.court.filter;import com.atguigu.imperial.court.util.ImperialCourtConst;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;public class LoginFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 1、获取 HttpSession 对象HttpServletRequest request = (HttpServletRequest) servletRequest;HttpSession session = request.getSession();// 2、尝试从 Session 域获取已登录的对象Object loginEmp = session.getAttribute(ImperialCourtConst.LOGIN_EMP_ATTR_NAME);// 3、判断 loginEmp 是否为空if (loginEmp != null) {// 4、若不为空则说明当前请求已登录,直接放行filterChain.doFilter(request, servletResponse);return ;}// 5、若为空说明尚未登录,则回到登录页面request.setAttribute("systemMessage", ImperialCourtConst.ACCESS_DENIED_MESSAGE);request.getRequestDispatcher("/").forward(request, servletResponse);}@Overridepublic void destroy() {}
}

②注册


LoginFilter 放在 TransactionFilter 前面声明,原因是:如果登录检查失败不放行,直接跳转到页面,此时将不必执行 TransactionFilter 中的事务操作,可以节约性能。

<filter><filter-name>loginFilter</filter-name><filter-class>com.atguigu.imperial.court.filter.LoginFilter</filter-class>
</filter>
<filter-mapping><filter-name>loginFilter</filter-name><url-pattern>/work</url-pattern>
</filter-mapping>

测试:
请求http://localhost:8080/demo/work?method=showMemorialsDetail&memorialsId=5

十一、 打包部署

1、适配部署环境

MySQL 连接信息中,IP 地址部分需要改成 localhost

2、跳过测试打包

mvn clean package -Dmaven.test.skip=true

可以人为指定最终 war 包名称:

    <!-- 对构建过程进行自己的定制 --><build><!-- 当前工程在构建过程中使用的最终名称 --><finalName>demo-me</finalName></build>

3、部署执行

①上传 war 包

上传到tomcatwebapps目录下

②启动 Tomcat

/opt/apache-tomcat-8.5.75/bin/startup.sh

③访问测试

2022版Maven教程 - 第六章 单一架构案例相关推荐

  1. 【Maven基础】单一架构案例(一)

    第一节 创建工程,引入依赖 1.架构 1.1.架构的概念 『架构』其实就是『项目的结构』,只是因为架构是一个更大的词,通常用来形容比较大规模事物的结构. 1.2.单一架构 单一架构也叫『all-in- ...

  2. java界面编辑教程_java程序设计基础教程第六章图形用户界面编辑.docx

    java程序设计基础教程第六章图形用户界面编辑.docx 还剩 27页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,很抱歉,此页已超出免费预览范围啦! 如果喜欢就下载吧,价低环保! 内容要 ...

  3. Android[中级教程]第六章 XML解析之SAX解析器

    Android[中级教程]第六章 XML解析之SAX解析器 分类: Android中级2011-10-06 01:52 125人阅读 评论(1) 收藏 举报 接上一章,这一章我们就来学习SAX解析器, ...

  4. linux教程第六章,Linux教程(第六章).ppt

    Linux教程(第六章) 1 计算机专业必修课程linux第六章 系统管理 教师: 李晓红 信息科学与工程学院计算机系 2005/8 2 主要内容 性能及内核管理 软件包管理 用户和工作组管理 设备管 ...

  5. AArch64教程第六章

    AArch64教程第六章 Nov 27, 2016 • Roger Ferrer Ibáñez • AArch64 到目前为止我们知道如何做一些计算和访问内存.今天我们将学习怎样修改我们程序的控制流. ...

  6. C++ Primer Plus(第六版)第十六章课后习题

    C++ Primer Plus(第六版)第十六章课后习题 16.10.1 #include <iostream> #include <string> using namespa ...

  7. linux教程第五版课后答案第六章,linux基础及应用第六章练习题

    linux基础及应用第六章练习题 1. 下列哪个文件的内容为当前已挂载文件系统的列表? A. /etc/inittab B. /etc/profile C. /etc/mtab D. /etc/fst ...

  8. 偏微分方程简明教程第六章部分答案

    偏微分方程简明教程答案 第六章 椭圆型方程 习题6.1 6.1.1 6.1.3 6.1.6 6.1.7 习题6.4 6.4.4 6.4.7 6.4.8 6.4.9 第六章 椭圆型方程 习题6.1 6. ...

  9. 《游戏设计艺术(第二版)》第六章个人学习

    目录 第六章 元素撑起主题 微不足道的游戏 统一主题 11号透镜:统一 共鸣 12号透镜:共鸣 回归现实 第六章 元素撑起主题 "一部伟大的著作必然有一个伟大的主题."--赫尔曼· ...

最新文章

  1. 卡方 matlab,matlab卡方分布 卡方检验(Chi-square test)及其MATLAB实现 - 余姚娱乐网
  2. using bgp data to find spammers
  3. PHP判断iPhone、iPad、Android、PC设备的方法
  4. spring environment_程序员:Spring项目中简单几步实现多个动态数据源切换
  5. 【牛客 - 370H】Rinne Loves Dynamic Graph(分层图最短路)
  6. sed mysql配置文件_linux下mysql配置文件my.cnf最详细解释
  7. 【AAAI2021】NLP所有方向论文列表(情感分析、句法、NER、对话/问答、关系抽取、KD等)...
  8. RUP大讲堂(第五讲)-基于用例的需求工程技术
  9. c语言 json_dumps,关于json.dumps中的参数,例如ensure_ascii
  10. 关于TP模板的目录设置和渲染问题
  11. d-s证据理论 matlab代码2
  12. [UE]EpicGames Launcher 添加/识别本地已有编辑器版本
  13. 关务+物流领域的技术发展路线
  14. win10相机打不开,qq可以正常打开:Windows 相机应用错误代码 0xA00F4288
  15. Google Play Store上线流程
  16. commons-lang3官方教程
  17. 游戏时弹出内存不能为read的提示框
  18. 爬取虎扑nba球员得分榜信息并存储至MongoDB数据库
  19. 从双非渣硕到字节NLP算法,很强!
  20. mysql实现增量备份

热门文章

  1. Layui数据表格监听单元格编辑恢复原值
  2. 小米11即将发布,这是小米进军高端市场最好的机会?
  3. 文字转语音+html5,HTML5新特性之文字转语音
  4. 5分钟搞定内网穿透工具-ngrok
  5. HTML标签学习基础新人笔记
  6. STM32F103高级定时器死区时间计算
  7. 阿里服务器微信发不了图片,为什么微信发不了图片?这四招教你解决难题
  8. 华硕天选2和华硕天选3哪个好 华硕天选2和华硕天选3区别
  9. XBOX登录白屏,微软商店无法连接网络
  10. 微信小程序云开发 操作数据库-数据的批量更新