Java后端基础知识笔记

本文详细介绍了Java Web的基础内容,包括Servlet的生命周期、配置、服务方法,以及Http协议、会话跟踪技术和Thymeleaf模板引擎的使用。讲解了Thymeleaf的标签、表达式、内置对象和迭代分支等功能,并提供了HTML页面的实例代码。还涵盖了过滤器、事务管理和监听器等组件的应用,最后提到了MVC模式和IoC(控制反转)的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

Java后端基础内容

前言

Java/ DB/JDBC 1.BS/CS

HTML/CSS/JS 2. Tomcat

CS:容户端服务器架构模式 3.Servlet人门

优点:充分利用容户端机器的资源,减轻服务器的负荷

(一部分安全要求不高的计算任务存储任务放在客户端执行,不需要把所有的计算和

存储都在服务器端执行,从而能够减轻服务器的压力,也能够减轻网络负荷)

缺点:需要安装;升级维护成本较高

BS:浏览器眼务册架构模式

优点:喜户端不需要安装,维护成本较低

缺点:所有的计算和存储任务都是放在服务器端的,服务器的负荷较重;在服务端计算完成之后把结果再传输给客户端,因此春户端和服务器端会进行非常频繁的数据通信,从而网笔负荷较重

doPost 方法 和 doGet 方法 不能同时执行

作用域

1. 保存作用域
   原始情况下,保存作用域我们可以认为有四个: page(页面级别,现在几乎不用) , request(一次请求响应范围) , session(一次会话范围) , application(整个应用程序范围)
   1) request:一次请求响应范围
   2) session:一次会话范围有效
   3) application: 一次应用程序范围有效

servlet

1、设置编码:

tomcat8之前,设置编码:
1)get请求方式:
//get方式目前不需要设置编码(基于tomcat8)
//如果是get请求发送的中文数据,转码稍微有点麻烦(tomcat8之前)
String fname = request.getParameter(“fname”);
//1.将字符串打散成字节数组
byte[] bytes = fname.getBytes(“ISO-8859-1”);
//2.将字节数组按照设定的编码重新组装成字符串
fname = new String(bytes,“UTF-8”);
2)post请求方式:
request.setCharacterEncoding(“UTF-8”);
tomcat8开始,设置编码,只需要针对post方式
request.setCharacterEncoding(“UTF-8”);
注意:
需要注意的是,设置编码(post)这一句代码必须在所有的获取参数动作之前

2、Servlet的继承关系 - 重点查看的是服务方法(service())

1. #### 继承关系
        
      javax.servlet.Servlet接口
      javax.servlet.GenericServlet抽象类
        javax.servlet.http.HttpServlet抽象子类

  #### 2.相关方法
    
    javax.servlet.Servlet接口:
    void init(config) - 初始化方法
    void service(request,response) - 服务方法
    void destory() - 销毁方法

​ javax.servlet.GenericServlet抽象类:
​ void service(request,response) - 仍然是抽象的

javax.servlet.http.HttpServlet 抽象子类:

void service(request,response) - 不是抽象的

1.String method = req.getMethod(); 获取请求的方式

2. 各种if判断,根据请求方式不同,决定去调用不同的do方法
   if (method.equals("GET")) {
    this.doGet(req,resp);
   } else if (method.equals("HEAD")) {
    this.doHead(req, resp);
   } else if (method.equals("POST")) {
    this.doPost(req, resp);
   } else if (method.equals("PUT")) {

3. 在HttpServlet这个抽象类中,do方法都差不多:
       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       String protocol = req.getProtocol();
       String msg = lStrings.getString("http.method_get_not_supported");
       if (protocol.endsWith("1.1")) {
        resp.sendError(405, msg);
       } else {
        resp.sendError(400, msg);
       }

       }

小结:

  1. 继承关系: HttpServlet -> GenericServlet -> Servlet
  2. Servlet中的核心方法: init() , service() , destroy()
  3. 服务方法: 当有请求过来时,service方法会自动响应(其实是tomcat容器调用的)
    在HttpServlet中我们会去分析请求的方式:到底是get、post、head还是delete等等
    然后再决定调用的是哪个do开头的方法
    那么在HttpServlet中这些do方法默认都是405的实现风格-要我们子类去实现对应的方法,否则默认会报405错误
  4. 因此,我们在新建Servlet时,我们才会去考虑请求方法,从而决定重写哪个do方法

3、servlet生命周期

Servlet生命周期:实例化、初始化、服务、销毁

1) 生命周期:从出生到死亡的过程就是生命周期。对应Servlet中的三个方法:init(),service(),destroy()
2) 默认情况下:
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
从第二次请求开始,每一次都是服务
当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法
3) 通过案例我们发现:

  • Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。

    • 默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务.这样的好处是什么? 提高系统的启动速度 。 这样的缺点是什么? 第一次请求时,耗时较长。

    • 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
      4) Servlet的初始化时机:

    • 默认是第一次接收请求时,实例化,初始化

    • 我们可以通过来设置servlet启动的先后顺序,数字越小,启动越靠前,最小值0
      5) Servlet在容器中是:单例的、线程不安全的

    • 单例:所有的请求都是同一个实例去响应

    • 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化 在servlet内部不设有公共属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jPfAFu8q-1647596480630)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317165834814.png)]


​ - 我们已经知道了servlet是线程不安全的,给我们的启发是: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么不要去:①不要去修改成员变量的值 ②不要去根据成员变量的值做一些逻辑判断

4、配置xml文件 ,注册servlet

方法一:
    <servlet>
        <servlet-name>AddServlet</servlet-name>
        <servlet-class>com.atguigu.servlets.AddServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AddServlet</servlet-name>
        <url-pattern>/add</url-pattern>
    </servlet-mapping>
    <!--
    1. 用户发请求,action=add
    2. 项目中,web.xml中找到url-pattern = /add   -> 第12行
    3. 找第11行的servlet-name = AddServlet
    4. 找和servlet-mapping中servlet-name一致的servlet , 找到第7行
    5. 找第8行的servlet-class -> com.atguigu.servlets.AddServlet
    6. 用户发送的是post请求(method=post) , 因此 tomcat会执行AddServlet中的doPost方法
    -->

5、服务器内部的转发与客户端的重定向

1) 服务器内部转发 : request.getRequestDispatcher("…").forward(request,response);

​ 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的

地址栏没有变化。 转发至另外的servlet

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUqrqLbW-1647596480631)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317172412476.png)]在这里插入图片描述

2) 客户端重定向: response.sendRedirect("…");

两次请求响应的过程。客户端肯定知道请求URL有变化

地址栏有变化

6、API

1)Servlet中的初始化方法有两个:init() , init(config)

其中带参数的方法代码如下:
public void init(ServletConfig config) throws ServletException {
this.config = config ;
init();
}
另外一个无参的init方法如下:
public void init() throws ServletException{
}
如果我们想要在Servlet初始化时做一些准备工作,那么我们可以重写init方法
我们可以通过如下步骤去获取初始化设置的数据

  • 获取config对象:ServletConfig config = getServletConfig();
  • 获取初始化参数值: config.getInitParameter(key);
2)配置servlet

方式一:在web.xml文件中

Demo01Servlet com.atguigu.servlet.Demo01Servlet hello world uname jim Demo01Servlet /demo01

方式二:也可以通过注解的方式进行配置:

@WebServlet(urlPatterns = {"/demo01"} ,
     initParams = {
         @WebInitParam(name="hello",value="world"),
         @WebInitParam(name="uname",value="jim")
     })
3)ServletContext 和 context-param
  1. 获取ServletContext,有很多方法
    在初始化方法中: ServletContxt servletContext = getServletContext();
    在服务方法中也可以通过request对象获取,也可以通过session获取:
    request.getServletContext(); session.getServletContext()

  2. 获取初始化值:
    servletContext.getInitParameter();

  1. 在web.xml文档中配置context-param
    在这里插入图片描述

Http协议

1) Http是无状态的

  1. HTTP 无状态 :服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的
  2. 无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱
  3. 通过会话跟踪技术来解决无状态的问题。

2) 会话跟踪技术

客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端

下次客户端给服务器发请求时,会把sessionID带给服务器,那么服务器就能获取到了,那么服务器就判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端

常用的API:

request.getSession() -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true) -> 效果和不带参数相同
request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的

session.getId() -> 获取sessionID
session.isNew() -> 判断当前session是否是新的
session.getMaxInactiveInterval() -> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval()
session.invalidate() -> 强制性让会话立即失效

3) session保存作用域

session保存作用域是和具体的某一个session对应的

常用的API:

void session.setAttribute(k,v)
Object session.getAttribute(k)
void removeAttribute(k)

##  [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10iUTjVw-1647596480633)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317172501646.png)]Thymeleaf

1、配置:

1) 添加thymeleaf的jar包
2) 新建一个Servlet类ViewBaseServlet 直接官网copy

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());
    }
}

3) 在web.xml文件中添加配置

配置前缀 view-prefix

配置后缀 view-suffix

  <!-- 配置上下文参数 -->
    <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>

4) Servlet继承ViewBaseServlet

​ 5) 根据逻辑视图名称 得到 物理视图名称
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html

super.processTemplate(“index”,request,response);
​ 6) 使用thymeleaf的标签
​ th:if , th:unless , th:each , th:text

2、HTML中的使用方法:

1)修改标签文本值

代码示例:

<p th:text="标签体新值">标签体原始值</p>
①th:text作用
  • 不经过服务器解析,直接用浏览器打开HTML文件,看到的是『标签体原始值』
  • 经过服务器解析,Thymeleaf引擎根据th:text属性指定的『标签体新值』去替换『标签体原始值』
②字面量

『字面量』是一个经常会遇到的概念,我们可以对照『变量』来理解它的含义。

// a是变量,100是字面量
int a = 100;
System.out.println("a = " + a);
  • 变量:变量名字符串本身不是它的值,它指向的才是它的值
  • 字面量:它就是字面上的含义,我们从『字面』上看到的直接就是它的值

现在我们在th:text属性中使用的就是『字面量』,它不指代任何其他值

2)修改指定属性值

代码示例:

<input type="text" name="username" th:value="文本框新值" value="文本框旧值" />

语法:任何HTML标签原有的属性,前面加上『th:』就都可以通过Thymeleaf来设定新值。

3)解析URL地址
①基本语法

代码示例:

<p th:text="@{/aaa/bbb/ccc}">标签体原始值</p>

经过解析后得到:

/view/aaa/bbb/ccc

所以**@{}的作用是在字符串前附加『上下文路径』**

这个语法的好处是:实际开发过程中,项目在不同环境部署时,Web应用的名字有可能发生变化。所以上下文路径不能写死。而通过@{}动态获取上下文路径后,不管怎么变都不怕啦!

②首页使用URL地址解析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TsnCiZv3-1647596480633)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img018.3954c282.png)]

如果我们直接访问index.html本身,那么index.html是不需要通过Servlet,当然也不经过模板引擎,所以index.html上的Thymeleaf的任何表达式都不会被解析。

解决办法:通过Servlet访问index.html,这样就可以让模板引擎渲染页面了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U1OlzZS5-1647596480634)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img019.7663465a.png)]

进一步的好处:

通过上面的例子我们看到,所有和业务功能相关的请求都能够确保它们通过Servlet来处理,这样就方便我们统一对这些请求进行特定规则的限定。

③给URL地址后面附加请求参数

参照官方文档说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7GVp0dI-1647596480634)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img037.78b02342.png)]

4)、直接执行表达式

Servlet代码:

request.setAttribute("reqAttrName", "<span>hello-value</span>");

页面代码:

<p>有转义效果:[[${reqAttrName}]]</p>
<p>无转义效果:[(${reqAttrName})]</p>

执行效果:

    <p>有转义效果:&lt;span&gt;hello-value&lt;/span&gt;</p>
    <p>无转义效果:<span>hello-value</span></p>
5)访问域对象
1、域对象
①请求域

在请求转发的场景下,我们可以借助HttpServletRequest对象内部给我们提供的存储空间,帮助我们携带数据,把数据发送给转发的目标资源。

请求域:HttpServletRequest对象内部给我们提供的存储空间
在这里插入图片描述

②会话域

在这里插入图片描述

③应用域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1UBULkr5-1647596480636)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img014.26733437.png)]

PS:在我们使用的视图是JSP的时候,域对象有4个

  • pageContext
  • request:请求域
  • session:会话域
  • application:应用域

所以在JSP的使用背景下,我们可以说域对象有4个,现在使用Thymeleaf了,没有pageContext。

2、在Servlet中将数据存入属性域
①操作请求域

Servlet中代码:

String requestAttrName = "helloRequestAttr";
String requestAttrValue = "helloRequestAttr-VALUE";

request.setAttribute(requestAttrName, requestAttrValue);

Thymeleaf表达式:

②操作会话域

Servlet中代码:

// ①通过request对象获取session对象
HttpSession session = request.getSession();

// ②存入数据
session.setAttribute("helloSessionAttr", "helloSessionAttr-VALUE");

Thymeleaf表达式:

③操作应用域

存储在服务器中

Servlet中代码:

// ①通过调用父类的方法获取ServletContext对象
ServletContext servletContext = getServletContext();

// ②存入数据
servletContext.setAttribute("helloAppAttr", "helloAppAttr-VALUE");

Thymeleaf表达式:

<p th:text="${application.helloAppAttr}">这里显示应用域数据</p>
6)获取请求参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IL05mNU6-1647596480636)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img020.05a00595.png)]

1、一个名字一个值

页面代码:

<p th:text="${param.username}">这里替换为请求参数的值</p>
2、一个名字多个值

页面代码:

<p th:text="${param.team}">这里替换为请求参数的值</p>

如果想要精确获取某一个值,可以使用数组下标。页面代码:

<p th:text="${param.team[0]}">这里替换为请求参数的值</p>
<p th:text="${param.team[1]}">这里替换为请求参数的值</p>
7)内置对象
1、概念

所谓内置对象其实就是在表达式中可以直接使用的对象。

2、基本内置对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qAut7Qkj-1647596480638)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img021.98446d22.png)]

用法举例:

<h3>表达式的基本内置对象</h3>
<p th:text="${#request.getClass().getName()}">这里显示#request对象的全类名</p>
<p th:text="${#request.getContextPath()}">调用#request对象的getContextPath()方法</p>
<p th:text="${#request.getAttribute('helloRequestAttr')}">调用#request对象的getAttribute()方法,读取属性域</p>

基本思路:

  • 如果不清楚这个对象有哪些方法可以使用,那么就通过getClass().getName()获取全类名,再回到Java环境查看这个对象有哪些方法
  • 内置对象的方法可以直接调用
  • 调用方法时需要传参的也可以直接传入参数
3、公共内置对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bwHjM4Y0-1647596480639)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img022.b5d6690d.png)]

Servlet中将List集合数据存入请求域:

request.setAttribute("aNotEmptyList", Arrays.asList("aaa","bbb","ccc"));
request.setAttribute("anEmptyList", new ArrayList<>());

页面代码:

<p>#list对象isEmpty方法判断集合整体是否为空aNotEmptyList:<span th:text="${#lists.isEmpty(aNotEmptyList)}">测试#lists</span></p>
<p>#list对象isEmpty方法判断集合整体是否为空anEmptyList:<span th:text="${#lists.isEmpty(anEmptyList)}">测试#lists</span></p>

公共内置对象对应的源码位置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LtoCBzLT-1647596480639)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img023.b592213a.png)]

8)${}中的表达式本质是OGNL
1、OGNL

OGNL:Object-Graph Navigation Language对象-图 导航语言

2、对象图

从根对象触发,通过特定的语法,逐层访问对象的各种属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0KXcG93U-1647596480639)(https://heavy_code_industry.gitee.io/code_heavy_industry/assets/img/img024.76f91a6f.png)]

3、OGNL语法
#①起点

在Thymeleaf环境下,${}中的表达式可以从下列元素开始:

  • 访问属性域的起点
    • 请求域属性名
    • session
    • application
  • param
  • 内置对象
    • #request
    • #session
    • #lists
    • #strings
#②属性访问语法
  • 访问对象属性:使用getXxx()、setXxx()方法定义的属性
    • 对象.属性名
  • 访问List集合或数组
    • 集合或数组[下标]
  • 访问Map集合
    • Map集合.key
    • Map集合[‘key’]
9)迭代与分支
#①if和unless

让标记了th:if、th:unless的标签根据条件决定是否显示。

示例的实体类:

public class Employee {

    private Integer empId;
    private String empName;
    private Double empSalary;

示例的Servlet代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.创建ArrayList对象并填充
    List<Employee> employeeList = new ArrayList<>();

    employeeList.add(new Employee(1, "tom", 500.00));
    employeeList.add(new Employee(2, "jerry", 600.00));
    employeeList.add(new Employee(3, "harry", 700.00));

    // 2.将集合数据存入请求域
    request.setAttribute("employeeList", employeeList);

    // 3.调用父类方法渲染视图
    super.processTemplate("list", request, response);
}

示例的HTML代码:

<table>
    <tr>
        <th>员工编号</th>
        <th>员工姓名</th>
        <th>员工工资</th>
    </tr>
    <tr th:if="${#lists.isEmpty(employeeList)}">
        <td colspan="3">抱歉!没有查询到你搜索的数据!</td>
    </tr>
    <tr th:if="${not #lists.isEmpty(employeeList)}">
        <td colspan="3">有数据!</td>
    </tr>
    <tr th:unless="${#lists.isEmpty(employeeList)}">
        <td colspan="3">有数据!</td>
    </tr>
</table>

if配合not关键词和unless配合原表达式效果是一样的,看自己的喜好。

#②switch
<h3>测试switch</h3>
<div th:switch="${user.memberLevel}">
    <p th:case="level-1">银牌会员</p>
    <p th:case="level-2">金牌会员</p>
    <p th:case="level-3">白金会员</p>
    <p th:case="level-4">钻石会员</p>
</div>
迭代
<h3>测试each</h3>
<table>
    <thead>
        <tr>
            <th>员工编号</th>
            <th>员工姓名</th>
            <th>员工工资</th>
        </tr>
    </thead>
    <tbody th:if="${#lists.isEmpty(employeeList)}">
        <tr>
            <td colspan="3">抱歉!没有查询到你搜索的数据!</td>
        </tr>
    </tbody>
    <tbody th:if="${not #lists.isEmpty(employeeList)}">
        <!-- 遍历出来的每一个元素的名字 : ${要遍历的集合} -->
        <tr th:each="employee : ${employeeList}">
            <td th:text="${employee.empId}">empId</td>
            <td th:text="${employee.empName}">empName</td>
            <td th:text="${employee.empSalary}">empSalary</td>
        </tr>
    </tbody>
</table>

在迭代过程中,可以参考下面的说明使用迭代状态:
在这里插入图片描述

<h3>测试each</h3>
<table>
    <thead>
        <tr>
            <th>员工编号</th>
            <th>员工姓名</th>
            <th>员工工资</th>
            <th>迭代状态</th>
        </tr>
    </thead>
    <tbody th:if="${#lists.isEmpty(employeeList)}">
        <tr>
            <td colspan="3">抱歉!没有查询到你搜索的数据!</td>
        </tr>
    </tbody>
    <tbody th:if="${not #lists.isEmpty(employeeList)}">
        <!-- 遍历出来的每一个元素的名字 : ${要遍历的集合} -->
        <tr th:each="employee,empStatus : ${employeeList}">
            <td th:text="${employee.empId}">empId</td>
            <td th:text="${employee.empName}">empName</td>
            <td th:text="${employee.empSalary}">empSalary</td>
            <td th:text="${empStatus.count}">count</td>
        </tr>
    </tbody>
</table>
10)包含其他模板文件

在这里插入图片描述

#1、应用场景

抽取各个页面的公共部分:

#2、创建页面的代码片段

使用th:fragment来给这个片段命名:

<div th:fragment="header">
    <p>被抽取出来的头部内容</p>
</div>
#3、包含到有需要的页面
语法效果
th:insert把目标的代码片段整个插入到当前标签内部
th:replace用目标的代码替换当前标签
th:include把目标的代码片段去除最外层标签,然后再插入到当前标签内部

页面代码举例:

<!-- 代码片段所在页面的逻辑视图 :: 代码片段的名称 -->
<div id="badBoy" th:insert="segment :: header">
    div标签的原始内容
</div>

<div id="worseBoy" th:replace="segment :: header">
    div标签的原始内容
</div>

<div id="worstBoy" th:include="segment :: header">
    div标签的原始内容
</div>

3、HTML使用实例:

<tr th:if="${#lists.isEmpty(session.fruitList)}">   遍历session保存的列表   &  条件判断是否为空
						<td colspan="4">对不起,库存为空!</td>
					</tr>  
																						$取出fruitlist中每一个元素(引用为fruit)
					<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit : ${session.fruitList}"> 
                                            $取出元素的属性              @自动补全url的绝对路径
						<!-- <td><a th:text="${fruit.fname}" th:href="@{'/edit.do?fid='+${fruit.fid}}">苹果</a></td> -->
						<td><a th:text="${fruit.fname}" th:href="@{/edit.do(fid=${fruit.fid})}">苹果</a></td>
						<td th:text="${fruit.price}">5</td>
						<td th:text="${fruit.fcount}">20</td>                         拼接th语句 |替代字符串内的''|
						<!-- <td><img src="imgs/del.jpg" class="delImg" th:οnclick="'delFruit('+${fruit.fid}+')'"/></td> -->
                        <td><img src="imgs/del.jpg" class="delImg" th:onclick="|delFruit(${fruit.fid})|"/></td>
					</tr>

小功能

1、翻页:

1)在sql查询中加入limit 限制查询条数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YIdALuq3-1647596480641)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317185805995.png)]

2)获取页面中的pageNo的值 并 servlet设置pageNo
		Integer pageNo = 1 ;  // 设置默认值为1
        String pageNoStr = request.getParameter("pageNo");
        if(StringUtil.isNotEmpty(pageNoStr)) {
            pageNo = Integer.parseInt(pageNoStr);
        }
        HttpSession session = request.getSession() ;
        session.setAttribute("pageNo",pageNo);

        FruitDAO fruitDAO = new FruitDAOImpl();
        List<Fruit> fruitList = fruitDAO.getFruitList(pageNo);

        session.setAttribute("fruitList",fruitList);

        //总记录条数
        int fruitCount = fruitDAO.getFruitCount();
        //总页数
        int pageCount = (fruitCount+5-1)/5 ;
        /*
        总记录条数       总页数
        1               1
        5               1
        6               2
        10              2
        11              3
        fruitCount      (fruitCount+5-1)/5
         */
        session.setAttribute("pageCount",pageCount);
3)翻页 在html中通过js、thymeleaf实现
function page(pageNo){
    window.location.href="index?pageNo="+pageNo;
}
<div style="width:60%;margin-left:20%;border:0px solid red;padding-top:4px;" class="center">
			<input type="button" value="首  页1" class="btn" th:onclick="|page(1)|" th:disabled="${session.pageNo==1}"/>
			<input type="button" value="上一页" class="btn" th:onclick="|page(${session.pageNo-1})|" th:disabled="${session.pageNo==1}"/>
			<input type="button" value="下一页" class="btn" th:onclick="|page(${session.pageNo+1})|" th:disabled="${session.pageNo==session.pageCount}"/>
			<input type="button" value="尾  页" class="btn" th:onclick="|page(${session.pageCount})|" th:disabled="${session.pageNo==session.pageCount}"/>
		</div>

2、关键字搜索:

1)在sql语句中设置模糊查询

在这里插入图片描述

2)获取页面中的keyWord的值 并 将其保存在session作用域中

1、获取发送操作详情

String oper = request.getParameter("oper");
        //如果oper!=null 说明 通过表单的查询按钮点击过来的
        //如果oper是空的,说明 不是通过表单的查询按钮点击过来的

        String keyword = null ;
        

2、获取keyword

if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){
            //说明是点击表单查询发送过来的请求
            //此时,pageNo应该还原为1 , keyword应该从请求参数中获取
            pageNo = 1 ;
            keyword = request.getParameter("keyword");
            if(StringUtil.isEmpty(keyword)){
                keyword = "" ;
            }
            session.setAttribute("keyword",keyword);
        }else{
            //说明此处不是点击表单查询发送过来的请求(比如点击下面的上一页下一页或者直接在地址栏输入网址)
            //此时keyword应该从session作用域获取
            String pageNoStr = request.getParameter("pageNo");
            if(StringUtil.isNotEmpty(pageNoStr)){
                pageNo = Integer.parseInt(pageNoStr);
            }
            Object keywordObj = session.getAttribute("keyword");
            if(keywordObj!=null){
                keyword = (String)keywordObj ;
            }else{
                keyword = "" ;
            }

3、查询keyword

 FruitDAO fruitDAO = new FruitDAOImpl();
        List<Fruit> fruitList = fruitDAO.getFruitList(keyword , pageNo);

        session.setAttribute("fruitList",fruitList);

HTML中的查询:

隐藏文本框 ,设置操作方式

<form th:action="@{/index}" method="post" style="float:left;width:60%;margin-left:20%;">
				<input type="hidden" name="oper" value="search"/>
				请输入关键字:<input type="text" name="keyword" th:value="${session.keyword}"/>
				<input type="submit" value="查询" class="btn"/>
			</form>

3、Cookies

**目的:保存用户信息 **

设置位置:Despatchter

功能:1)时效内免登录 2) 记住用户名和密码(本地文件里) 设置时效

  1. 创建Cookie对象
  2. 在客户端保存Cookie
  3. 设置Cookie的有效时长
    cookie.setMaxAge(60) , 设置cookie的有效时长是60秒
    cookie.setDomain(pattern);
    cookie.setPath(uri);

4、KaptchaServlet

目的:产生登录验证码

使用:

  • 添加jar

  • 在web.xml文件中注册KaptchaServlet,并设置验证码图片的相关属性
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIcbx37T-1647596480641)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317210908766.png)]

  • 在html页面上编写一个img标签,然后设置src等于KaptchaServlet对应的url-pattern

kaptcha验证码图片的各个属性在常量接口:Constants中

KaptchaServlet在生成验证码图片时,会同时将验证码信息保存到session中
因此,我们在注册请求时,首先将用户文本框中输入的验证码值和session中保存的值进行比较,相等,则进行注册。
在这里插入图片描述

HttpSession session = request.getSession() ;
Object obj = session.getAttribute("KAPTCHA_SESSION_KEY");
System.out.println("obj = " + obj);

*重点:mvc

M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。

  1. Model1和Model2
    MVC : Model(模型)、View(视图)、Controller(控制器)
    视图层:用于做数据展示以及和用户交互的一个界面
    控制层:能够接受客户端的请求,具体的业务功能还是需要借助于模型组件来完成
    模型层:模型分为很多种:有比较简单的pojo/vo(value object),有业务模型组件,有数据访问层组件
    1) pojo/vo : 值对象

    2) DAO : 数据访问对象
    3) BO : 业务对象

    1. 区分业务对象和数据访问对象:
      1) DAO中的方法都是单精度方法或者称之为细粒度方法。什么叫单精度?一个方法只考虑一个操作,比如添加,那就是insert操作、查询那就是select操作…
      2) BO中的方法属于业务方法,也实际的业务是比较复杂的,因此业务方法的粒度是比较粗的
      注册这个功能属于业务功能,也就是说注册这个方法属于业务方法。
      那么这个业务方法中包含了多个DAO方法。也就是说注册这个业务功能需要通过多个DAO方法的组合调用,从而完成注册功能的开发。
      注册:
      1. 检查用户名是否已经被注册 - DAO中的select操作
      2. 向用户表新增一条新用户记录 - DAO中的insert操作
      3. 向用户积分表新增一条记录(新用户默认初始化积分100分) - DAO中的insert操作
      4. 向系统消息表新增一条记录(某某某新用户注册了,需要根据通讯录信息向他的联系人推送消息) - DAO中的insert操作
      5. 向系统日志表新增一条记录(某用户在某IP在某年某月某日某时某分某秒某毫秒注册) - DAO中的insert操作
      6. …
    2. 在库存系统中添加业务层组件

目的:将业务层每层逻辑分离,每层负责自己的业务逻辑,对每层的功能进行抽象,实现低解耦。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LOhZTUwn-1647596480642)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317191332252.png)]

**controller:**业务层组件实现某大一模块的功能,如 用户模块的登录、注册等

​ **属性:**service

**service:**实现某一功能(CRUD+组装),功能颗粒度低

​ **属性:**DAOImpl || service

**DAOImpl:**高精度CRUD 获得所需的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtDafaCZ-1647596480642)(D:\JAVA\JavaWeb\Day7-mvc-ioc-servlet\素材\01.MVC04.png)]

1、配置applicationContext.xml文档

**目的:**注册每一层所要操纵的类,以及属性的类。为在服务端开始时进行注册,实例化所有需要的类。

如:

<!-- 这个bean标签的作用是 将来servletpath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理 -->

<!--
1.概念
HTML : 超文本标记语言
XML : 可扩展的标记语言
HTML是XML的一个子集

2.XML包含三个部分:
1) XML声明 , 而且声明这一行代码必须在XML文件的第一行
2) DTD 文档类型定义
3) XML正文
 -->

在这里插入图片描述

2、实现DispatcherServlet

通过反射技术获得具体所要使用的模块、以及操作方法。也就是对应的controller以及controller中对应的方法(实现的模块)

@WebServlet("*.do")
public class DepacherServLet extends ViewBaseServlet {
    private BeanFactory beanFactory;  //  获得所有需要使用到的对应的实例类  
    
    // 通过ioc模块实现
    @Override
    public void init() throws ServletException {
        super.init();
        //  提前保存在servletContext中
        ServletContext servletContext = getServletContext();
        Object beanFactoryObj = servletContext.getAttribute("beanFactory");
        if (beanFactoryObj != null) {
            beanFactory = (BeanFactory) beanFactoryObj;
        } else {
            throw new RuntimeException("IOC error!");
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String servletPath = req.getServletPath();//  获得请求的servletPath  即对应的controller
        int endStr = servletPath.lastIndexOf(".do");  
        //  字符串截取获得 具体的servlet 即 controller 的名字,再通过map获得实例化对象
        String servlet = servletPath.substring(1, endStr);
        Object controllerObj = beanFactory.getBean(servlet);
        //  获取用户端发来的操作
        String oper = req.getParameter("operate");
        //  默认去到index 页面
        if (oper == null) {
            oper = "index";
        }
        //  获得对应的controller的所有方法并且遍历 ==>  并于operate 进行匹配
        for (Method method : controllerObj.getClass().getDeclaredMethods()) {
            if (method.getName().equals(oper)) {
                //  这一步肯定是获取方法的所有参数类型,并且给他设置值
                Parameter[] parameters = method.getParameters();
                Object[] paraVals = new Object[parameters.length];// 参数 集合
                for (int i = 0; i < parameters.length; i++) {
                    //  常见所需要的 http类的
                    if (parameters[i].getName().equals("req")) {
                        paraVals[i] = req;
                    } else if (parameters[i].getName().equals("resp")) {
                        paraVals[i] = resp;
                    } else if (parameters[i].getName().equals("session")) {
                        paraVals[i] = req.getSession();
                    } else {
                        //  需要用户传的参数通过request获取
                        String paraVal = req.getParameter(parameters[i].getName());
                        String typerName = parameters[i].getType().getName();
                        Object paraObj = paraVal;
                        //  参数类型
                        if (paraObj != null) {
                            if (typerName.equals("java.lang.Integer")) {
                                paraObj = Integer.parseInt(paraVal);
                            } else if (typerName.equals("java.lang.Double")) {
                                paraObj = Double.parseDouble(paraVal);
                            }
                        }
                        paraVals[i] = paraObj;
                    }
                }
                try {
                    //  部署用户界面
                    method.setAccessible(true);
                    //  调用对应的方法
                    Object returnObj = method.invoke(controllerObj, paraVals);
                    //  获得返回值,其中包含 服务器的转发或者重新发送之类的内容
                    String returnString = (String) returnObj;
                    if(returnString.isEmpty()){
                        return;
                    }
                    //  对返回值的内容进行判断
                    if (returnString.indexOf(".do") != -1) {
                        String redirectStr = returnString.substring("redirect:".length());
                        resp.sendRedirect(redirectStr);
                    } else if (returnString.startsWith("json:")) {
                        resp.setCharacterEncoding("utf-8");
                        resp.setContentType("application/json;charset=utf-8");
                        String jsonStr = returnString.substring("json:".length());
                        PrintWriter out = resp.getWriter();
                        out.print(jsonStr);
                        out.flush();
                    } else {
                        super.processTemplate(returnString, req, resp);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

3、controller 与 service、DAOImpl

他们都是一层一层抽象其所要实现的功能

controller:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9dGn7NB8-1647596480643)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317194007179.png)]
service:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzqfOumj-1647596480643)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317194102784.png)]
Impl:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u99AK0s7-1647596480644)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317194159003.png)]

*重点ioc

1.耦合/依赖

依赖指的是某某某离不开某某某
在软件系统中,层与层之间是存在依赖的。我们也称之为耦合。
我们系统架构或者是设计的一个原则是: 高内聚低耦合。
层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合(就是没有耦合)

2.IOC - 控制反转 / DI - 依赖注入

控制反转:
  1. 之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl();
    这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别;
    如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别
  2. 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中
    因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转
依赖注入:
1) 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
   那么,控制层和service层存在耦合。
1) 之后,我们将代码修改成FruitService fruitService = null ;
   然后,在配置文件中配置:
<bean id="fruit" class="FruitController">
        <property name="fruitService" ref="fruitService"/>
   </bean>

3. 目的:解除Dispatcher与注册组件的耦合

4.ioc的实现

public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();

    public ClassPathXmlApplicationContext(){
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
            //1.创建DocumentBuilderFactory
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            //2.创建DocumentBuilder对象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
            //3.创建Document对象
            Document document = documentBuilder.parse(inputStream);

            //4.获取所有的bean节点
            NodeList beanNodeList = document.getElementsByTagName("bean");
            for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                Node beanNode = beanNodeList.item(i);
                if(beanNode.getNodeType() == Node.ELEMENT_NODE){
                    Element beanElement = (Element)beanNode ;
                    String beanId =  beanElement.getAttribute("id");
                    String className = beanElement.getAttribute("class");
                    Class beanClass = Class.forName(className);
                    //创建bean实例
                    Object beanObj = beanClass.newInstance() ;
                    //将bean实例对象保存到map容器中
                    beanMap.put(beanId , beanObj) ;
                    //到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
                }
            }
            //5.组装bean之间的依赖关系
            for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                Node beanNode = beanNodeList.item(i);
                if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element beanElement = (Element) beanNode;
                    String beanId = beanElement.getAttribute("id");
                    NodeList beanChildNodeList = beanElement.getChildNodes();
                    for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
                        Node beanChildNode = beanChildNodeList.item(j);
                        if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
                            Element propertyElement = (Element) beanChildNode;
                            String propertyName = propertyElement.getAttribute("name");
                            String propertyRef = propertyElement.getAttribute("ref");
                            //1) 找到propertyRef对应的实例
                            Object refObj = beanMap.get(propertyRef);
                            //2) 将refObj设置到当前bean对应的实例的property属性上去
                            Object beanObj = beanMap.get(beanId);
                            Class beanClazz = beanObj.getClass();
                            Field propertyField = beanClazz.getDeclaredField(propertyName);
                            propertyField.setAccessible(true);
                            propertyField.set(beanObj,refObj);
                        }
                    }
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}

其他组件(过滤器等)

1、过滤器(Filter)

  1. Filter也属于Servlet规范

  2. Filter开发步骤:新建类实现Filter接口,然后实现其中的三个方法:init、doFilter、destroy配置Filter,可以用注解@WebFilter,也可以使用xml文件

  3. Filter在配置时,和servlet一样,也可以配置通配符,例如 @WebFilter("*.do")表示拦截所有以.do结尾的请求

  4. 过滤器链
    1)执行的顺序依次是: A B C demo03 C2 B2 A2
    2)如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的
    3)如果采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MAQLMTAo-1647596480644)(C:\Users\53026\AppData\Roaming\Typora\typora-user-images\image-20220317202805020.png)]

1)、设置全局信息性过滤器:

在这里插入图片描述

@WebFilter(urlPatterns = {"*.do"},initParams = {@WebInitParam(name = "encoding",value = "UTF-8")})
public class CharacterEncodingFilter implements Filter {

    private String encoding = "UTF-8";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String encodingStr = filterConfig.getInitParameter("encoding");
        if(StringUtil.isNotEmpty(encodingStr)){
            encoding = encodingStr ;
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ((HttpServletRequest)servletRequest).setCharacterEncoding(encoding);
        filterChain.doFilter(servletRequest,servletResponse);

    }

    @Override
    public void destroy() {

    }
}
2)、事务管理型过滤器:
@WebFilter("*.do")
public class OpenSessionInViewFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try{
            TransactionManager.beginTrans();
            System.out.println("开启事务....");
            filterChain.doFilter(servletRequest, servletResponse);
            TransactionManager.commit();
            System.out.println("提交事务...");
        }catch (Exception e){
            e.printStackTrace();
            try {
                TransactionManager.rollback();
                System.out.println("回滚事务....");
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }

    @Override
    public void destroy() {

    }
}
3)限制访问过滤器

目的:防止非法访问信息管理页面,造成信息泄露

@WebFilter(urlPatterns = {"*.do","*.html"},
        initParams = {
                @WebInitParam(name = "accessible",
                        value = "/bookstore/page.do?operate=page&page=user/login," +
                                "/bookstore/page.do?operate=page&page=index," +
                                "/bookstore/user.do?null,/bookstore/book.do?null," +
                                "/bookstore/page.do?operate=page&page=user/regist,")
        })
public class SessionFilter implements Filter {
    //  用户未登录时 可以访问的url白名单
    List<String> accessibleList;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //  初始化白名单 ,  从 配置文档或者注解中获取value
       String[] list = filterConfig.getInitParameter("accessible").split(",");
       accessibleList = Arrays.asList(list);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //  获取访问的url
        String uri = request.getRequestURI() ;
        String queryString = request.getQueryString() ;
        String str = uri + "?" + queryString ;
        //  组装url
        if(accessibleList.contains(str)){
            filterChain.doFilter(request,response);
        }else{
            HttpSession session = request.getSession() ;
            Object currUserObj = session.getAttribute("currUser");
            //  对访问重定向
            if(currUserObj==null){
                response.sendRedirect("page.do?operate=page&page=user/login");
            }else{
                filterChain.doFilter(request,response);
            }
        }
    }

    @Override
    public void destroy() {

    }
}

2、事务管理:

在这里插入图片描述

1)涉及到的组件:
  1. OpenSessionInViewFilter
  2. TransactionManager
  3. ThreadLocal
  4. ConnUtil
  5. BaseDAO
2)ThreadLocal:

在这里插入图片描述

1)get() , set(obj)

2)ThreadLocal称之为本地线程 。 我们可以通过set方法在当前线程上存储数据、通过get方法在当前线程上获取数据

3)set方法源码分析:

public void set(T value) {
  Thread t = Thread.currentThread(); //获取当前的线程
  ThreadLocalMap map = getMap(t);    //每一个线程都维护各自的一个容器(ThreadLocalMap)
  if (map != null)
      map.set(this, value);          //这里的key对应的是ThreadLocal,因为我们的组件中需要传输(共享)的对象可能会有多个(不止Connection)
  else
      createMap(t, value);           //默认情况下map是没有初始化的,那么第一次往其中添加数据时,会去初始化
}

4)get方法源码分析:

public T get() {
  Thread t = Thread.currentThread(); //获取当前的线程
  ThreadLocalMap map = getMap(t);    //获取和这个线程(企业)相关的ThreadLocalMap(也就是工作纽带的集合)
  if (map != null) {
      ThreadLocalMap.Entry e = map.getEntry(this);   //this指的是ThreadLocal对象,通过它才能知道是哪一个工作纽带
      if (e != null) {
          @SuppressWarnings("unchecked")
          T result = (T)e.value;     //entry.value就可以获取到工具箱了
          return result;
      }
  }
  return setInitialValue();
}
3)Conn组件:

目的:获取与数据库的连接以及设置本地线程

public class ConnUtil {

    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public static final String DRIVER = "com.mysql.jdbc.Driver" ;
    public static final String URL = "jdbc:mysql://localhost:3306/qqzonedb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    public static final String USER = "root";
    public static final String PWD = "123456" ;

    private static Connection createConn(){
        try {
            //1.加载驱动
            Class.forName(DRIVER);
            //2.通过驱动管理器获取连接对象
            return DriverManager.getConnection(URL, USER, PWD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null ;
    }

    public static Connection getConn(){
        Connection conn = threadLocal.get();
        if(conn==null){
            conn =createConn();
            threadLocal.set(conn);
        }
        return threadLocal.get() ;
    }

    public static void closeConn() throws SQLException {
        Connection conn = threadLocal.get();
        if(conn==null){
            return ;
        }
        if(!conn.isClosed()){
            conn.close();
            //threadLocal.set(null);
            threadLocal.remove();
        }
    }
}
4)TranscManager:

管理整个事务:

public class TransactionManager {

    //开启事务
    public static void beginTrans() throws SQLException {
        ConnUtil.getConn().setAutoCommit(false);
    }

    //提交事务
    public static void commit() throws SQLException {
        Connection conn = ConnUtil.getConn();
        conn.commit();
        ConnUtil.closeConn();
    }

    //回滚事务
    public static void rollback() throws SQLException {
        Connection conn = ConnUtil.getConn();
        conn.rollback();
        ConnUtil.closeConn();
    }
}
5)OpenSessionFilter:

开启事务管理:

@WebFilter("*.do")
public class OpenSessionInViewFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try{
            TransactionManager.beginTrans();
            //System.out.println("开启事务....");
            filterChain.doFilter(servletRequest, servletResponse);
            TransactionManager.commit();
            //System.out.println("提交事务...");
        }catch (Exception e){
            e.printStackTrace();
            try {
                TransactionManager.rollback();
                //System.out.println("回滚事务....");
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }

    @Override
    public void destroy() {

    }
}

3、监听器(Listener)

  1. ServletContextListener - 监听ServletContext对象的创建和销毁的过程

  2. HttpSessionListener - 监听HttpSession对象的创建和销毁的过程

  3. ServletRequestListener - 监听ServletRequest对象的创建和销毁的过程

  4. ServletContextAttributeListener - 监听ServletContext的保存作用域的改动(add,remove,replace)

  5. HttpSessionAttributeListener - 监听HttpSession的保存作用域的改动(add,remove,replace)

  6. ServletRequestAttributeListener - 监听ServletRequest的保存作用域的改动(add,remove,replace)

  7. HttpSessionBindingListener - 监听某个对象在Session域中的创建与移除

  8. HttpSessionActivationListener - 监听某个对象在Session域中的序列化和反序列化

ContextLoaderListener:

监听上下文启动,在上下文启动的时候去创建IOC容器,然后将其保存到application作用域,中央控制器再从application作用域中去获取IOC容器

@WebListener
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //1.获取ServletContext对象
        ServletContext application = servletContextEvent.getServletContext();
        //2.获取上下文的初始化参数
        String path = application.getInitParameter("contextConfigLocation");
        //3.创建IOC容器
        BeanFactory beanFactory = new ClassPathXmlApplicationContext(path);
        //4.将IOC容器保存到application作用域
        application.setAttribute("beanFactory",beanFactory);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

DB设计(后续补充):

数据库的范式:

1) 第一范式:列不可再分
2) 第二范式:一张表只表达一层含义(只描述一件事情)
3) 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖

数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择:

  • 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率
  • 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

渝北最后的单纯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值