古老的技术,jdbc+jsp+servlet。生活就像俄罗斯套娃,一个套着一个。我们后面看着的大框架,像剥洋葱一般一层一层剥下来,底层就是一些技术。虽然有的过时,但是从中可以窥见一丝前人的思想。

xml

XML

XML介绍

什么是XML

  • XML 指可扩展标记语言(EXtensible Markup Language

  • XML是用来传输数据的,不是用来显示数据的。之后学习另外一个HTML是用来显示数据的。

  • XML 标签没有被预定义。您需要自行定义标签。

  • XML 是 W3C 的推荐标准

W3C在1988年2月发布1.0版本,2004年2月又发布1.1版本,但因为1.1版本不能向下兼容1.0版本,所以1.1没有人用。同时,在2004年2月W3C又发布了1.0版本的第三版。我们要学习的还是1.0版本。

image-20230101093924611

XML 与 HTML 的主要差异

  • html语法松散,xml语法严格,区分大小写
  • html做页面展示,xml传输数据
  • html所有标签都是预定义的,xml所有标签都是自定义的

xml的作用

  • 作为配置文件,javaee框架 ssm大部分都会使用xml作为配置文件。

  • XML可以存储数据, 作为数据交换的载体(使用XML格式进行数据的传输)。

XML组成元素-重点

一个标准XML文件一般由以下几部分组成:文档声明、元素、属性、注释、转义字符、字符区。

文档声明

1
<?xml version="1.0" encoding="utf-8" ?>
  1. 文档声明可以不写

  2. 文档声明必须为<?xml开头,以?>结束

  3. 文档声明必须从文档的0行0列位置开始

  4. 文档声明中常见的两个属性:

  • version:指定XML文档版本。必须属性,这里一般选择1.0;
  • enconding:指定当前文档的编码,可选属性,默认值是utf-8;

注释

1
<!--注释内容-->
  • XML的注释,既以<!--开始,-->结束。
  • 注释不能嵌套
  • idea上快捷键: ctrl + /

元素\标签

  1. 元素是XML中最重要的组成部分,元素也叫标签

  2. 标签分为开始标签和结束标签,开始标签<名字> 结束标签</名字>

  3. 开始标签和结束标签中间写的是标签内容,标签的内容可以是文本,也可以是其他标签

  4. 如果标签没有任何内容,那么可以定义空标签(比如:<名字/>)

  5. 标签可以嵌套,但是不能乱嵌套

  6. 一个XML文件只有一个根标签

  7. 命名规则:

  • 不要使用XML xML xml 这样的单词

  • 不能使用空格,冒号

  • 命名区分大小写

  • 数字不能开头

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" ?>
<person>
<name>唐三</name>
<age>年龄</age>
<aaa/>
</person>

属性

  1. 位置: 属性是元素的一部分,它必须出现在元素的开始标签中,不能写在结束标签中

  2. 格式: 属性的定义格式:属性名=”属性值”,其中属性值必须使用单引或双引号括起来

  3. 一个元素可以有0~N个属性,但一个元素中不能出现同名属性

  4. 属性名必须符合标识符命名规范和规则

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" ?>
<person>
<name id = "001" level = '98'>唐三</name>
<age>10</age>

<aaa type = 'itheima' />
</person>

转义字符

因为有些特殊的字符在XML中是不会被识别的,所以在元素体或属性值中想使用这些符号就必须使用转义字符(也叫实体字符),例如:><'",&

image-20230921123005980

注意:严格地讲,在 XML 中仅有字符 “<”和”&” 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。

转义字符应用示例:

1
<price> 苹果的价格: price > 5 &amp;&amp;  price &lt; 10</price>

字符区(了解)

  • CDATA 内部的所有东西都会被解析器忽略,当做文本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<![CDATA[
文本数据
]]>
快捷键:CD
<!--写步骤 -->
<>
<!>
<![]>
<![CDATA]>
<![CDATA[ 文本 ]]>

<!-- 案例 -->
<price>
<![CDATA[
苹果的价格: price > 5 && price < 10
]]>
</price>

XML文件的约束-DTD约束(了解)

xml约束概述

  • 在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。
  • 约束文档定义了在XML中允许出现的元素(标签)名称、属性及元素(标签)出现的顺序等等。
  • 两种约束:DTD约束(文件后缀为dtd),Schema约束(文件后缀为xsd)
  • 注意: 约束不是我们要写的东西,我们的工作是根据约束去写XML

根据DTD约束写XML

  • DTD约束文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?xml version="1.0" encoding="UTF-8" ?>
<!--
复制内容如下到XML文件中:
<!DOCTYPE 书架 SYSTEM "bookdtd.dtd">

-->
<!--
xml文件中引入dtd约束文档:
方式一:外部本地DTD,DTD文档在本地,dtd约束文档和xml文档在同一路径下
<!DOCTYPE 根元素 SYSTEM "文件名">
方式二:外部本地DTD,DTD文档在网络上
<!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD文档的URL">\
方式三:内部DTD,在XML文档内部嵌入DTD,只对当前XML有效。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE 根元素 [元素声明]>
-->
<!--
对标签层级关系的约束:
格式:<!ELEMENT 标签名 (子标签,子标签,...)>
数量词:
* 表示元素可以出现0到多个
+ 表示元素可以出现至少1个
? 表示元素可以是0或1个
, 表示元素需要按照顺序显示
| 表示元素需要选择其中的某一个
-->
<!--
对标签的约束:
格式:<!ELEMENT 标签名 标签类型>
标签类型: EMPTY(即空元素,例如<hr/>) ANY(任意类型) (#PCDATA) 字符串数据
-->
<!--
对属性的约束:
格式:
<!ATTLIST 标签名
属性名 属性类型 属性约束
属性名 属性类型 属性约束
...
>
解释:
属性类型:
CDATA :表示文本字符串
ID:表示属性值唯一,不能以数字开头
ENUMERATED (DTD没有此关键字):表示枚举,只能从枚举列表中任选其一,如(鸡肉|牛肉|猪肉|鱼肉)

属性约束:
REQUIRED:表示该属性必须出现
IMPLIED:表示该属性可有可无
FIXED:表示属性的取值为一个固定值。语法:#FIXED "固定值"

-->

<!--根标签名为书架,书架标签下至少有一个书标签 +表示至少出现一次-->
<!ELEMENT 书架 (书+)>
<!--书标签下,包含书名,做做,售价三个子标签,并且必须按照这个顺序出现-->
<!ELEMENT 书 (书名,作者,售价)>
<!--书名标签中的内容是文本数据-->
<!ELEMENT 书名 (#PCDATA)>
<!--作者标签中的内容是文本数据-->
<!ELEMENT 作者 (#PCDATA)>
<!--售价标签中的内容是文本数据-->
<!ELEMENT 售价 (#PCDATA)>
<!--
对书标签中的属性进行约束:
书标签中有一个id属性,类型为ID类型,也就是说该属性值要唯一,不能以数字开头,约束是必须出现
书标签中有一个编号属性,类型是CDATA类型,也就是字符串类型,约束是可有可无
书标签中有一个出版社属性,类型是枚举类型,也就是说该属性值只能在枚举值中任选一个,默认值为传智播客
书标签中有一个type属性,类型为CDATA类型,也就是字符串类型,固定值为IT
-->
<!ATTLIST 书
id ID #REQUIRED
编号 CDATA #IMPLIED
出版社 (清华|北大|传智播客) "传智播客"
type CDATA #FIXED "IT"
>
  • XML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE 书架 SYSTEM "bookdtd.dtd">
    <书架>
    < id="a1" 编号="001" 出版社="清华" type="IT">
    <书名>斗罗大陆</书名>
    <作者>唐家三少</作者>
    <售价>99.8</售价>
    </>
    < id="a2">
    <书名>java从入门到放弃</书名>
    <作者>无名氏</作者>
    <售价>9.8</售价>
    </>
    </书架>

语法(了解)

文档声明(了解)
  1. 内部DTD,在XML文档内部嵌入DTD,只对当前XML有效。

    1
    2
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE 根元素 [元素声明]>><!--内部DTD-->
  2. 外部DTD—本地DTD,DTD文档在本地系统上,企业内部自己项目使用。

    1
    2
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE 根元素 SYSTEM "文件名"><!--外部本地DTD-->
  3. 外部DTD—公共DTD,DTD文档在网络上,一般都有框架提供 , 也是我们使用最多的.

    1
    2
    3
    4
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD文档的URL">

    例如: <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
元素声明(了解)
  1. 约束元素的嵌套层级

    语法

    1
    2
    3
    4
    <!ELEMENT 父标签 (子标签1,子标签2,…)>
    例如:
    <!ELEMENT books (book+)> <!--约束根元素是"books","books"子元素为"book",“+”为数量词-->
    <!ELEMENT book (name,author,price)><!--约束"book"子元素依次为“name”、“author”、“price”,-->
  2. 约束元素体里面的数据

    语法

    1
    2
    <!ELEMENT 标签名字 标签类型>
    例如 <!ELEMENT name (#PCDATA)>

    标签类型: EMPTY(即空元素,例如<hr/>) ANY(任意类型) (#PCDATA) 字符串数据

    代码

    1
    2
    3
    <!ELEMENT name (#PCDATA)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT price (#PCDATA)>
  3. 数量词(掌握)

    数量词符号 含义
    * 表示元素可以出现0到多个
    + 表示元素可以出现至少1个
    ? 表示元素可以是0或1个
    , 表示元素需要按照顺序显示
    | 表示元素需要选择其中的某一个
属性声明(了解)

语法

1
2
3
4
5
6
7
<!ATTLIST 标签名称 
属性名称1 属性类型1 属性说明1
属性名称2 属性类型2 属性说明2

>
例如
<!ATTLIST book bid ID #REQUIRED>

属性类型

  • CDATA :表示文本字符串
  • ID:表示属性值唯一,不能以数字开头
  • ENUMERATED (DTD没有此关键字):表示枚举,只能从枚举列表中任选其一,如(鸡肉|牛肉|猪肉|鱼肉)

属性说明:

  • REQUIRED:表示该属性必须出现
  • IMPLIED:表示该属性可有可无
  • FIXED:表示属性的取值为一个固定值。语法:#FIXED "固定值"

属性说明

代码

1
2
3
4
5
6
<!ATTLIST 书									<!--设置"书"元素的的属性列表-->
id ID #REQUIRED <!--"id"属性值为必须有-->
编号 CDATA #IMPLIED <!--"编号"属性可有可无-->
出版社 (清华|北大|传智播客) "传智播客" <!--"出版社"属性值是枚举值,默认为“传智播客”-->
type CDATA #FIXED "IT" <!--"type"属性为文本字符串并且固定值为"IT"-->
>
案例
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version = "1.0" encoding="GB2312" standalone="yes"?>
<!DOCTYPE 购物篮 [
<!ELEMENT 购物篮 (肉+)>
<!ELEMENT 肉 EMPTY>
<!ATTLIST 肉 品种 ( 鸡肉 | 牛肉 | 猪肉 | 鱼肉 ) "鸡肉">
]>
<购物篮>
< 品种="牛肉"></>
< 品种="牛肉"></>
< 品种="鱼肉"></>
</>
</购物篮>

schema约束(了解)

概念

schema和DTD一样, 也是一种XML文件的约束.

Schema 语言也可作为 XSD(XML Schema Definition)。

Schema约束的文件的后缀名.xsd

Schema 功能更强大,数据类型约束更完善。

根据schema约束写出xml文档

  • Schema约束文档:

image-20220612184624465

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 传智播客教学实例文档.将注释中的以下内容复制到要编写的xml的声明下面
复制内容如下到XML文件中:
<书架 xmlns="http://www.itcast.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itcast.cn bookSchema.xsd" >
-->
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itcast.cn"
elementFormDefault="qualified">

<!--element表示元素,也就是对元素进行约束-->
<!--根标签的名称为书架-->
<xs:element name='书架'>
<!--complexType:表示标签是一个复杂标签-->
<!--书架是一个复杂标签-->
<xs:complexType>
<!--sequence表示要按照顺序出现,maxOccurs最多出现多少次,unbounded无数次,minOccurs:最少出现多少次-->
<!--书架标签的子标签必须按照顺序出现,最多出现2次,最少出现1次-->
<xs:sequence maxOccurs="2" minOccurs="1">
<!--书架的子标签的名称为书-->
<xs:element name='书'>
<!--书标签是一个复杂标签-->
<xs:complexType>
<!--书标签的子标签必须按照顺序出现-->
<xs:sequence>
<!--书标签的子标签名为书名,类型为string-->
<!--书标签的子标签名为作者,类型为string-->
<!--书标签的子标签名为售价,类型为double-->
<xs:element name='书名' type='xs:string'/>
<xs:element name='作者' type='xs:string'/>
<xs:element name='售价' type='xs:double'/>
</xs:sequence>
<!--attribute表示属性,也就是对属性的元约束,optional:表示可选的,required:表示必须的-->
<!--书标签有一个bid属性,类型为int类型,是可选-->
<xs:attribute name="bid" type="xs:int" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
  • 根据上面的Schema约束编写XML

    • 声明方式

      1
      2
      3
      4
      <书架 xmlns="http://www.itcast.cn"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.itcast.cn bookSchema.xsd" >

    • 案例1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <?xml version="1.0" encoding="UTF-8" ?>
      <书架 xmlns="http://www.itcast.cn"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.itcast.cn bookSchema.xsd" >
      < bid="1">
      <书名>斗罗大陆</书名>
      <作者>唐家三少</作者>
      <售价>99.8</售价>
      </>
      </书架>
    • 案例2

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <?xml version="1.0" encoding="UTF-8" ?>
      <a:书架 xmlns:a="http://www.itcast.cn"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.itcast.cn bookSchema.xsd" >
      <a:书 bid="1">
      <a:书名>斗罗大陆</a:书名>
      <a:作者>唐家三少</a:作者>
      <a:售价>99.8</a:售价>
      </a:书>
      </a:书架>

Dom4j

XML解析

解析方式

  • 开发中比较常见的解析方式有三种,如下:

    1. DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象

      a)优点:元素与元素之间保留结构关系,故可以进行增删改查操作。

      b)缺点:XML文档过大,可能出现内存溢出

    2. SAX:是一种速度更快,更有效的方法。她逐行扫描文档,一边扫描一边解析。并以事件驱动的方式进行具体解析,每执行一行,都触发对应的事件。(了解)

      a)优点:不会出现内存问题,可以处理大文件

      b)缺点:只能读,不能回写。

    3. PULL:Android内置的XML解析方式,类似SAX。(了解)

  • 解析器,就是根据不同的解析方式提供具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包

    image-20230921123029811

解析包

  • JAXP:sun公司提供支持DOM和SAX开发包
  • Dom4j: 比较简单的的解析开发包(常用),
  • JDom:与Dom4j类似
  • Jsoup:功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便(项目中讲解)

Dom4j的基本使用

为什么了解它? 很多框架底层多是用dom4j解析xml的

DOM解析原理及结构模型

  • 解析原理

    XML DOM 和 HTML DOM一样,XML DOM 将整个XML文档加载到内存,生成一个DOM树,并获得一个Document对象,通过Document对象就可以对DOM进行操作。以下面books.xml文档为例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="UTF-8"?>
    <books>
    <book id="0001">
    <name>JavaWeb开发教程</name>
    <author>张孝祥</author>
    <sale>100.00元</sale>
    </book>
    <book id="0002">
    <name>三国演义</name>
    <author>罗贯中</author>
    <sale>100.00元</sale>
    </book>
    </books>
  • 结构模型

    image-20230921123036890

    DOM中的核心概念就是节点,在XML文档中的元素、属性、文本,在DOM中都是节点!所有的节点都封装到了Document对象中。

使用步骤

  1. 导入jar包 dom4j-1.6.1j.jar
  2. 创建解析器
  3. 读取xml 获得document对象
  4. 得到根元素
  5. 根据根元素获取对于的子元素或者属性

常用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
创建解析器对象:
SAXReader sr = new SAXReader();
解析器读取文件方法:
Document doc = sr.read(String fileName);
Document的方法:
getRootElement() : 获取根元素

节点中的方法:
elements() : 获取当前元素的子元素
element(String name) : 根据元素名获取指定子元素(如果有多个就获取到第一个)
getName() : 获取元素的元素名
elementText(String name) : 获取指定子元素的文本值,参数是子元素名称
attributeValue(String name) : 获取当前元素下某个属性的值
getText() : 获取当前元素的文本值

方法演示

  • xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="UTF-8"?>
    <books>
    <book id="0001">
    <name>JavaWeb开发教程</name>
    <author>张孝祥</author>
    <sale>100.00元</sale>
    </book>
    <book id="0002">
    <name>三国演义</name>
    <author>罗贯中</author>
    <sale>100.00元</sale>
    </book>
    </books>
  • 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.itheima.demo1_Dom4j的基本使用;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

/**
* @Author:pengzhilin
* @Date: 2020/9/26 12:11
*/
public class Test1 {
public static void main(String[] args) throws Exception {
/*
Dom4j的基本使用:
使用步骤:
1.下载Dom4j的jar包
2.把jar包导入到模块的lib文件夹中,并添加到classpath路径下
3.创建解析器对象
4.使用解析器读取xml文档,生成Document对象
5.根据Document对象获取根元素
6.根据根元素获取下面的子元素\属性...
常用的方法:
创建解析器对象:
SAXReader sr = new SAXReader();
解析器读取文件方法:
Document doc = sr.read(String fileName);
Document的方法:
Element getRootElement() : 获取根元素

Element的方法:
elements() : 获取当前元素的子元素
getName() : 获取元素的元素名
attributeValue(String name) : 获取当前元素下某个属性的值
getText() : 获取当前元素的文本值

element(String name) : 根据元素名获取指定子元素(如果有多个就获取到第一个)
elementText(String name) : 获取指定子元素的文本值,参数是子元素名称
*/
// 1.创建解析器对象
SAXReader sr = new SAXReader();
// 2.使用解析器读取xml文档,生成Document对象
Document document = sr.read("day16\\src\\book.xml");
// 3.根据Document对象获取根元素
Element rootE = document.getRootElement();
System.out.println("根元素的名称:" + rootE.getName());// books

// 4.获取根元素下的所有子元素
List<Element> list1 = rootE.elements();
// 5.循环遍历根元素的所有的子元素
for (Element e1 : list1) {
System.out.println("根元素的子元素的名称:" + e1.getName());// book book
System.out.println("根元素的子元素的id属性值:" + e1.attributeValue("id"));// 0001 0002
// 获取e1元素下的所有子元素
List<Element> list2 = e1.elements();
// 循环遍历list2
for (Element e2 : list2) {
System.out.println("book下的子元素名:" + e2.getName());// name author sale
System.out.println("book下的子元素中的文本:" + e2.getText());// name author sale

}
System.out.println("==================================");

}

// 5.获取根元素下的book子元素(就获取第一个)
Element bookE = rootE.element("book");
System.out.println(bookE.getName());// book
System.out.println(bookE.attributeValue("id"));// 0001

// 6.获取bookE元素下的author子元素的文本
String text = bookE.elementText("author");
System.out.println(text);// 张孝祥

}
}

Dom4J结合XPath解析XML

介绍

XPath 使用路径表达式来选取HTML\XML 文档中的元素节点或属性节点。节点是通过沿着路径 (path) 来选取的。XPath在解析HTML\XML文档方面提供了独树一帜的路径思想。

XPath使用步骤

步骤1:导入jar包(dom4j和jaxen-1.1-beta-6.jar)

步骤2:通过dom4j的SaxReader解析器对象,获取Document对象

步骤3: 利用Xpath提供的api,结合xpaht的语法完成选取XML文档元素节点进行解析操作。

document常用的api

  • document.selectSingleNode("xpath语法"); 获得一个节点(标签,元素)
  • document.selectNodes("xpath语法"); 获得多个节点(标签,元素)

XPath语法(了解)

  • XPath表达式,就是用于选取HTML文档中节点的表达式字符串。

    获取XML文档节点元素一共有如下4种XPath语法方式:

    1. 绝对路径表达式方式 例如: /元素/子元素/子子元素...
    2. 相对路径表达式方式 例如: 子元素/子子元素.. 或者 ./子元素/子子元素..
    3. 全文搜索路径表达式方式 例如: //子元素//子子元素
    4. 谓语(条件筛选)方式 例如: //元素[@attr1=value]
  • 获取不同节点语法

    获取类型 语法代码
    获取元素节点 元素名称
    获取属性节点 @属性名称
绝对路径表达式(了解)

绝对路径介绍: 以/开头的路径叫做是绝对路径,绝对路径要从根元素开始写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args)throws Exception {
/*
- document.selectSingleNode("xpath语法"); 获得一个节点(标签,元素)
- document.selectNodes("xpath语法"); 获得多个节点(标签,元素)
*/
// 创建解析器
SAXReader sr = new SAXReader();
// 读取xml 获得document对象
Document document = sr.read("day22/src/tianqi.xml");
// 得到根元素
Element rootE = document.getRootElement();
System.out.println(rootE);

// 绝对路径: 获取深圳 湿度
Node node = rootE.selectSingleNode("/天气预报/深圳/湿度");
System.out.println(node.getName());// 湿度
System.out.println(node.getText());// 50%
}
}
相对路径表达式(了解)

相对路径介绍: 相对路径就是相对当前节点元素位置继续查找节点,不以/开头, ../ 表示上一个元素, ./表示当前元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test {
public static void main(String[] args)throws Exception {
/*
- document.selectSingleNode("xpath语法"); 获得一个节点(标签,元素)
- document.selectNodes("xpath语法"); 获得多个节点(标签,元素)
*/
// 创建解析器
SAXReader sr = new SAXReader();
// 读取xml 获得document对象
Document document = sr.read("day22/src/tianqi.xml");
// 得到根元素
Element rootE = document.getRootElement();
System.out.println(rootE);

// 相对路径: 获取深圳 湿度
Node node = rootE.selectSingleNode("./深圳/湿度");
System.out.println(node.getName());// 湿度
System.out.println(node.getText());// 50%


//用绝对路径的方式获取“温度”
Node node2 = document.selectSingleNode("/天气预报/北京/温度");
// 相对路径: 获取北京 湿度
Node node3 = node2.selectSingleNode("../湿度");
System.out.println(node3.getName());// 湿度
System.out.println(node3.getText());// 20%
}
}
全文搜索路径表达式(了解)

全文搜索路径介绍: 代表不论中间有多少层,直接获取所有子元素中满足条件的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args)throws Exception {
/*
- document.selectSingleNode("xpath语法"); 获得一个节点(标签,元素)
- document.selectNodes("xpath语法"); 获得多个节点(标签,元素)
*/
// 创建解析器
SAXReader sr = new SAXReader();
// 读取xml 获得document对象
Document document = sr.read("day22/src/tianqi.xml");
// 得到根元素
Element rootE = document.getRootElement();
System.out.println(rootE);

// 获取天气预报里的所有湿度,不论有多少层
List<Element> list = rootE.selectNodes("//湿度");
for (Element e : list) {
System.out.println(e.getName()+","+e.getText());
}
}
}
谓语(条件筛选 了解)

介绍: 谓语,又称为条件筛选方式,就是根据条件过滤判断进行选取节点

格式:

1
2
3
4
5
String xpath1="//元素[@attr1=value]";//获取元素属性attr1=value的元素
String xpath2="//元素[@attr1>value]/@attr1"//获取元素属性attr1>value的d的所有attr1的值
String xpath3="//元素[@attr1=value]/text()";//获取符合条件元素体的自有文本数据
String xpath4="//元素[@attr1=value]/html()";//获取符合条件元素体的自有html代码数据。
String xpath3="//元素[@attr1=value]/allText()";//获取符合条件元素体的所有文本数据(包含子元素里面的文本)

演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args)throws Exception {
/*
- document.selectSingleNode("xpath语法"); 获得一个节点(标签,元素)
- document.selectNodes("xpath语法"); 获得多个节点(标签,元素)
*/
// 创建解析器
SAXReader sr = new SAXReader();
// 读取xml 获得document对象
Document document = sr.read("day22/src/tianqi.xml");
// 得到根元素
Element rootE = document.getRootElement();
System.out.println(rootE);

// 获取最高温度中属性名是level 属性值是A的元素
List<Element> list = rootE.selectNodes("//最高温度[@level='C']");
for (Element e : list) {
System.out.println(e.getName()+","+e.getText());
}
}
}

正则表达式

正则表达式的概念及演示

概述: 正则表达式其实就是一个匹配规则,用来替换之前复杂的if结构判断

在Java中,我们经常需要验证一些字符串,是否符合规则, 例如:校验qq号码是否正确,手机号码是否正确,邮箱是否正确等等。那么如果使用if就会很麻烦, 而正则表达式就是用来验证各种字符串的规则。它内部描述了一些规则,我们可以验证用户输入的字符串是否匹配这个规则。

先看一个不使用正则表达式验证的例子:下面的程序让用户输入一个QQ号码,我们要验证:

  • QQ号码必须是5–15位长度
  • 而且必须全部是数字
  • 而且首位不能为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Demo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

System.out.println("请输入你的QQ号码:");
String qq = sc.next();

System.out.println(checkQQ(qq));
}
//我们自己编写代码,验证QQ号码
private static boolean checkQQ(String qq) {
//1.验证5--15位
if(qq.length() < 5 || qq.length() > 15){
return false;
}
//2.必须都是数字;
for(int i = 0;i < qq.length() ; i++){
char c = qq.charAt(i);
if(c < '0' || c > '9'){
return false;
}
}
//3.首位不能是0;
char c = qq.charAt(0);
if(c == '0'){
return false;
}
return true;//验证通过
}
}
  • 使用正则表达式验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

System.out.println("请输入你的QQ号码:");
String qq = sc.next();

System.out.println(checkQQ2(qq));
}
//使用正则表达式验证
private static boolean checkQQ2(String qq){
String regex = "[1-9]\\d{4,14}";//正则表达式
return qq.matches(regex);
}
}

上面程序checkQQ2()方法中String类型的变量regex就存储了一个”正则表达式 “,而这个正则表达式就描述了我们需要的三个规则。matches()方法是String类的一个方法,用于接收一个正则表达式,并将”本对象”与参数”正则表达式”进行匹配,如果本对象符合正则表达式的规则,则返回true,否则返回false。

正则表达式的基本使用

正则表达式-字符类

  • 语法示例:[] 表示匹配单个字符 ^ 取反 - 范围

    1. [abc]:代表a或者b,或者c字符中的一个。

    2. [^abc]:代表除a,b,c以外的任何字符。

    3. [a-z]:代表a-z的所有小写字符中的一个。 左右包含

    4. [A-Z]:代表A-Z的所有大写字符中的一个。

    5. [0-9]:代表0-9之间的某一个数字字符。

    6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。

    7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。

  • 代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test1 {
public static void main(String[] args) {
String str = "ead";
//1.验证str是否以h开头,以d结尾,中间是a,e,i,o,u中某个字符
System.out.println(str.matches("h[aeiou]d"));// false
System.out.println("hed".matches("h[aeiou]d"));// true
System.out.println("heod".matches("h[aeiou]d"));// false
System.out.println("==========================================");

//2.验证str是否以h开头,以d结尾,中间不是a,e,i,o,u中的某个字符
System.out.println(str.matches("h[^aeiou]d"));// false
System.out.println("hed".matches("h[^aeiou]d"));// false
System.out.println("hbd".matches("h[^aeiou]d"));// true
System.out.println("==========================================");

//3.验证str是否a-z的任何一个小写字符开头,后跟ad
System.out.println(str.matches("[a-z]ad"));// true
System.out.println("Aad".matches("[a-z]ad"));// false
System.out.println("==========================================");

//4.验证str是否以a-d或者m-p之间某个字符开头,后跟ad
System.out.println(str.matches("[a-dm-p]ad"));//false
System.out.println("bad".matches("[a-dm-p]ad"));// true
System.out.println("nad".matches("[a-dm-p]ad"));// true

}
}

正则表达式-逻辑运算符

  • 语法示例:

    1. &&:并且
    2. |:或者
  • 代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Test2 {
public static void main(String[] args) {
/*
正则表达式-逻辑运算符
- 语法示例:
1. &&:并且
2. | :或者
*/
String str = "had";
//1.要求字符串是小写辅音字符开头,后跟ad 除了a,e,i,o,u之外,其他的都是辅音字母
System.out.println(str.matches("[a-z&&[^aeiou]]ad"));// true
System.out.println("aad".matches("[a-z&&[^aeiou]]ad"));// false
System.out.println("Aad".matches("[a-z&&[^aeiou]]ad"));// false
System.out.println("========================");

//2.要求字符串是aeiou中的某个字符开头,后跟ad
System.out.println(str.matches("[aeiou]ad"));// false
System.out.println(str.matches("[a|e|i|o|u]ad"));// false

System.out.println("aad".matches("[aeiou]ad"));// true
System.out.println("aad".matches("[a|e|i|o|u]ad"));// true
}
}


正则表达式-预定义字符

  • 语法示例:

    1. “.” : 匹配任何字符。如果要表示一个字符点,那么就得使用\\.
    2. “\d”:任何数字[0-9]的简写;
    3. “\D”:任何非数字[^0-9]的简写;
    4. “\s”: 空白字符:[ \t\n\x0B\f\r] 的简写
    5. “\S”: 非空白字符:[^\s] 的简写
    6. “\w”:单词字符:[a-zA-Z_0-9]的简写
    7. “\W”:非单词字符:[^\w]
  • 代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Test3 {
public static void main(String[] args) {
/*
正则表达式-预定义字符
- 语法示例:
1. "." : 匹配任何字符。如果要表示一个字符点,那么就得使用\.
2. "\d":任何数字[0-9]的简写;
3. "\D":任何非数字[^0-9]的简写;
4. "\s": 空白字符:[ \t\n\x0B\f\r] 的简写
5. "\S": 非空白字符:[^\s] 的简写
6. "\w":单词字符:[a-zA-Z_0-9]的简写
7. "\W":非单词字符:[^\w]
*/
String str = "258";
//1.验证str是否3位数字
System.out.println(str.matches("\\d\\d\\d"));// true
System.out.println("25".matches("\\d\\d\\d"));// false
System.out.println("+===================================");

//2.验证手机号:1开头,第二位:3/5/8,剩下9位都是0-9的数字
System.out.println(str.matches("1[358]\\d\\d\\d\\d\\d\\d\\d\\d\\d"));// false
System.out.println("13838381234".matches("1[358]\\d\\d\\d\\d\\d\\d\\d\\d\\d"));// true
System.out.println("14838381234".matches("1[358]\\d\\d\\d\\d\\d\\d\\d\\d\\d"));// false
System.out.println("+===================================");

//3.验证字符串是否以h开头,以d结尾,中间是任何字符
System.out.println(str.matches("h.d"));// false
System.out.println("h%d".matches("h.d"));// true
System.out.println("h#d".matches("h.d"));// true
System.out.println("+===================================");

//4.验证str是否是:h.d
System.out.println("h.d".matches("h.d"));// true
System.out.println("had".matches("h.d"));// true

System.out.println("h.d".matches("h\\.d"));// true
System.out.println("had".matches("h\\.d"));// false
}
}

正则表达式-数量词

  • 语法示例:

    1. X? : 0次或1次
    2. X* : 0次到多次
    3. X+ : 1次或多次
    4. X{n} : 恰好n次
    5. X{n,} : 至少n次
    6. X{n,m}: n到m次(n和m都是包含的)
  • 代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test4 {
public static void main(String[] args) {
/*
正则表达式-数量词
- 语法示例:
1. X? : 0次或1次
2. X* : 0次到多次
3. X+ : 1次或多次
4. X{n} : 恰好n次
5. X{n,} : 至少n次 包含n
6. X{n,m}: n到m次(n和m都是包含的)

*/
//1.验证str是否是三位数字
System.out.println("123".matches("\\d{3}"));// true
System.out.println("12".matches("\\d{3}"));// false
System.out.println("1234".matches("\\d{3}"));// false
System.out.println("===================================");

//2.验证str是否是多位数字
System.out.println("12".matches("\\d{2,3}"));// true
System.out.println("123".matches("\\d{2,3}"));// true
System.out.println("1223".matches("\\d{2,3}"));// false

System.out.println("1".matches("\\d+"));// true
System.out.println("12".matches("\\d+"));// true
System.out.println("123".matches("\\d+"));// true
System.out.println("1234".matches("\\d+"));// true
System.out.println("===================================");

//3.验证str是否是手机号:1).第一位为1 2).第二位是3,5,8 3).后面9位都是数字
System.out.println("13838381234".matches("1[358]\\d{9}"));// true
System.out.println("14838381234".matches("1[358]\\d{9}"));// false
System.out.println("===================================");

//4.验证qq号码:1).5--15位;2).全部是数字;3).第一位不是0
System.out.println("123456".matches("[1-9]\\d{4,14}"));// true
System.out.println("023456".matches("[1-9]\\d{4,14}"));// false
System.out.println("123".matches("[1-9]\\d{4,14}"));// false
}
}

正则表达式-分组括号( )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Test5 {
public static void main(String[] args) {
/*
正则表达式-分组括号( )
*/
String str = "DG8FV-B9TKY-FRT9J-99899-XPQ4G";
// 分成5组: 前面4组的规则是一样的 后面一组单独规则
System.out.println(str.matches("([A-Z0-9]{5}-){4}[A-Z0-9]{5}"));
// xxyy
// 扩展:正则表达式匹配叠词
// 哈哈,呵呵,嘿嘿,XX (.)表示第一组,\\1表示第一组
System.out.println("哈哈".matches("(.)\\1"));//true
System.out.println("呵呵".matches("(.)\\1"));//true
System.out.println("嘿嘿".matches("(.)\\1"));//true
System.out.println("嘿哈".matches("(.)\\1"));// false
// (.)表示第一组,\\1表示第一组,{3}表示\\1出现3次
System.out.println("哈哈哈哈".matches("(.)\\1{3}"));//true
System.out.println("呵呵呵呵".matches("(.)\\1{3}"));//true
System.out.println("嘿嘿嘿嘿".matches("(.)\\1{3}"));//true
System.out.println("===================================");

// 高高兴兴,逼逼赖赖,XXYY
// \\1表示第一组,\\2表示第二组,\\3表示第三组....
System.out.println("高高兴兴".matches("(.)\\1(.)\\2"));//true
System.out.println("逼逼赖赖".matches("(.)\\1(.)\\2"));//true
System.out.println("===================================");

// 快乐快乐,哔哩哔哩,XYXY
System.out.println("快乐快乐".matches("(..)\\1"));//true
System.out.println("哔哩哔哩".matches("(..)\\1"));//true
}
}

String中正则表达式的使用

String的split方法中使用正则表达式

  • String类的split()方法原型:
1
public String[] split(String regex)//参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
  • 代码示例:
1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
String str = "18 4 567 99 56";
String[] strArray = str.split(" +");
for (int i = 0; i < strArray.length; i++) {
System.out.println(strArray[i]);
}
}
}

String类的replaceAll方法中使用正则表达式

  • String类的replaceAll()方法原型:
1
public String replaceAll(String regex,String newStr)//参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
  • 代码示例:
1
2
3
4
5
6
7
public class Demo {
public static void main(String[] args) {
//将下面字符串中的"数字"替换为"*"
String str = "jfdk432jfdk2jk24354j47jk5l31324";
System.out.println(str.replaceAll("\\d+", "*"));
}
}

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
必须练习:
1.编写xml文档------元素\属性的书写
2.使用Dom4j结合xpath路径解析xml文件中的数据
3.要求能看懂正则表达式

- 能够说出XML的作用
1.作为配置文件
2.存储数据

- 了解XML的组成元素
文档声明
元素
属性
注释
转义字符
字符区

- 能够说出有哪些XML约束技术
dtd\xsd

- 能够说出解析XML文档DOM方式原理
使用解析器读取xml文档到内存中,加载成Dom树,生成Document对象

- 能够使用dom4j解析XML文档
1.导入jar包
2.创建解析器对象
3.读取xml文档,生成Document对象
4.使用Document对象获取根元素
5.根据根元素获取子元素\属性
...

- 能够使用xpath解析XML
1.导入jar包(2个)
2.创建解析器对象
3.读取xml文档,生成Document对象
4.使用Document对象调用方法
5.使用元素调用方法
...
- document.selectSingleNode("xpath语法"); 获得一个节点(标签,元素)
- document.selectNodes("xpath语法"); 获得多个节点(标签,元素)
xpath路径语法:
1. 绝对路径表达式方式 例如: /元素/子元素/子子元素...
以/开头的路径叫做是绝对路径,绝对路径要从根元素开始写

2. 相对路径表达式方式 例如: 子元素/子子元素.. 或者 ./子元素/子子元素..
相对路径就是相对当前节点元素位置继续查找节点,不以/开头, ../ 表示上一个元素, ./表示当前元素

3. 全文搜索路径表达式方式 例如: //子元素, //子元素/子子元素,//子元素//子子元素
代表不论中间有多少层,直接获取所有子元素中满足条件的元素

4. 谓语(条件筛选)方式 例如: //元素[@attr1=value]

- 能够理解正则表达式的作用
替换复杂的if结构判断

- 能够使用正则表达式的字符类
语法示例:[] 表示匹配单个字符 ^ 取反 - 范围
1. [abc]:代表a或者b,或者c字符中的一个。
2. [^abc]:代表除a,b,c以外的任何字符。
3. [a-z]:代表a-z的所有小写字符中的一个。 左右包含
4. [A-Z]:代表A-Z的所有大写字符中的一个。
5. [0-9]:代表0-9之间的某一个数字字符。
6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。

- 能够使用正则表达式的逻辑运算符
&&
|
- 能够使用正则表达式的预定义字符类
1. "." : 匹配任何字符。如果要表示一个字符点,那么就得使用\.
2. "\d":任何数字[0-9]的简写;
3. "\D":任何非数字[^0-9]的简写;
4. "\s": 空白字符:[ \t\n\x0B\f\r] 的简写
5. "\S": 非空白字符:[^\s] 的简写
6. "\w":单词字符:[a-zA-Z_0-9]的简写
7. "\W":非单词字符:[^\w]

- 能够使用正则表达式的数量词
1. X? : 0次或1
2. X* : 0次到多次
3. X+ : 1次或多次
4. X{n} : 恰好n次
5. X{n,} : 至少n次
6. X{n,m}: n到m次(n和m都是包含的)

- 能够使用正则表达式的分组
()

- 能够在String的split方法中使用正则表达式

正则表达式 案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
正则表达式
正则表达式(英文:Regular Expression)在计算机科学中,是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。
常用的正则表达式
常用正则表达式
正则表达式用于字符串处理、表单验证等场合,实用高效。现将一些常用的表达式收集于此,以备不时之需。
用户名:/^[a-z0-9_-]{3,16}$/
密码:/^[a-z0-9_-]{6,18}$/
十六进制值:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/
电子邮箱:/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/
URL:/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
IP 地址:/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
HTML 标签:/^<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)$/
Unicode编码中的汉字范围:/^[u4e00-u9fa5],{0,}$/
匹配中文字符的正则表达式: [\u4e00-\u9fa5]
评注:匹配中文还真是个头疼的事,有了这个表达式就好办了
匹配双字节字符(包括汉字在内):[^\x00-\xff]
评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
匹配空白行的正则表达式:\n\s*\r
评注:可以用来删除空白行
匹配HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? />
评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力
匹配首尾空白字符的正则表达式:^\s*|\s*$
评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
评注:表单验证时很实用
匹配网址URL的正则表达式:[a-zA-z]+://[^\s]*
评注:网上流传的版本功能很有限,上面这个基本可以满足需求
匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
评注:表单验证时很实用
匹配国内电话号码:\d{3}-\d{8}|\d{4}-\d{7}
评注:匹配形式如 0511-4405222 或 021-87888822
匹配腾讯QQ号:[1-9][0-9]{4,}
评注:腾讯QQ号从10000开始
匹配中国大陆邮政编码:[1-9]\d{5}(?!\d)
评注:中国大陆邮政编码为6位数字
匹配身份证:\d{15}|\d{18}
评注:中国大陆的身份证为15位或18位
匹配ip地址:\d+\.\d+\.\d+\.\d+
评注:提取ip地址时有用
匹配特定数字:
^[1-9]\d*$    //匹配正整数
^-[1-9]\d*$   //匹配负整数
^-?[1-9]\d*$   //匹配整数
^[1-9]\d*|0$  //匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$   //匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$   //匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$  //匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$  //匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$   //匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$  //匹配非正浮点数(负浮点数 + 0)
评注:处理大量数据时有用,具体应用时注意修正
匹配特定字符串:
^[A-Za-z]+$  //匹配由26个英文字母组成的字符串
^[A-Z]+$  //匹配由26个英文字母的大写组成的字符串
^[a-z]+$  //匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$  //匹配由数字和26个英文字母组成的字符串
^\w+$  //匹配由数字、26个英文字母或者下划线组成的字符串
表达式全集
正则表达式有多种不同的风格。下表是在PCRE中元字符及其在正则表达式上下文中的行为的一个完整列表:
字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。序列“\\”匹配“\”而“\(”则匹配“(”。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
{n} n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,} n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
. 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“[.\n]”的模式。
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern) 正向预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 负向预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^\f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xnn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。.
\num 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
\nml 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\unnnn 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(?)。
以下是以PHP的语法所写的示例
验证字符串是否只含数字与英文,字符串长度并在4~16个字符之间
<?php
$str = 'a1234';
if (preg_match("^[a-zA-Z0-9]{4,16}$", $str)) {
echo "驗證成功";
} else {
echo "驗證失敗";
}
?>
简易的台湾身份证字号验证
<?php
$str = 'a1234';
if (preg_match("/^\w[12]\d{8}$/", $str)) {
echo "驗證成功";
} else {
echo "驗證失敗";
}
?>
以下示例是用 Perl 语言写的,与上面的示例功能相同
print $str = "a1234" =~ m:^[a-zA-Z0-9]{4,16}$: ? "COMFIRM" : "FAILED";
print $str = "a1234" =~ m"^\w[12]\d{8}$" ? "COMFIRM" : "INVAILD";
如何写出高效率的正则表达式
如果纯粹是为了挑战自己的正则水平,用来实现一些特效(例如使用正则表达式计算质数、解线性方程),效率不是问题;如果所写的正则表达式只是为了满 足一两次、几十次的运行,优化与否区别也不太大。但是,如果所写的正则表达式会百万次、千万次地运行,效率就是很大的问题了。我这里总结了几条提升正则表 达式运行效率的经验(工作中学到的,看书学来的,自己的体会),贴在这里。如果您有其它的经验而这里没有提及,欢迎赐教。
为行文方便,先定义两个概念。
误匹配:指正则表达式所匹配的内容范围超出了所需要范围,有些文本明明不符合要求,但是被所写的正则式“击中了”。例如,如果使用\d{11}来匹配11位的手机号,\d{11}不单能匹配正确的手机号,它还会匹配98765432100这样的明显不是手机号的字符串。我们把这样的匹配称之为误匹配。
漏匹配:指正则表达式所匹配的内容所规定的范围太狭窄,有些文本确实是所需要的,但是所写的正则没有将这种情况囊括在内。例如,使用\d{18}来匹配18位的身份证号码,就会漏掉结尾是字母X的情况。
写出一条正则表达式,既可能只出现误匹配(条件写得极宽松,其范围大于目标文本),也可能只出现漏匹配(只描述了目标文本中多种情况种的一种),还可能既有误匹配又有漏匹配。例如,使用\w+\.com来匹配.com结尾的域名,既会误匹配abc_.com这样的字串(合法的域名中不含下划线,\w包含了下划线这种情况),又会漏掉ab-c.com这样的域名(合法域名中可以含中划线,但是\w不匹配中划线)。
精准的正则表达式意味着既无误匹配且无漏匹配。当然,现实中存在这样的情况:只能看到有限数量的文本,根据这些文本写规则,但是这些规则将会用到海 量的文本中。这种情况下,尽可能地(如果不是完全地)消除误匹配以及漏匹配,并提升运行效率,就是我们的目标。本文所提出的经验,主要是针对这种情况。
掌握语法细节。正则表达式在各种语言中,其语法大致相同,细节各有千秋。明确所使用语言的正则的语法的细节,是写出正确、高效正则表达式的基础。例如,perl中与\w等效的匹配范围是[a-zA-Z0-9_];perl正则式不支持肯定逆序环视中使用可变的重复(variable repetition inside lookbehind,例如(?<=.*)abc),但是.Net语法是支持这一特性的;又如,JavaScript连逆序环视(Lookbehind,如(?<=ab)c) 都不支持,而perl和python是支持的。《精通正则表达式》第3章《正则表达式的特性和流派概览》明确地列出了各大派系正则的异同,这篇文章也简要 地列出了几种常用语言、工具中正则的比较。对于具体使用者而言,至少应该详细了解正在使用的那种工作语言里正则的语法细节。
先粗后精,先加后减。使用正则表达式语法对于目标文本进行描述和界定,可以像画素描一样,先大致勾勒出框架,再逐步在局步实现细节。仍举刚才的手机号的例子,先界定\d{11},总不会错;再细化为1[358]\d{9}, 就向前迈了一大步(至于第二位是不是3、5、8,这里无意深究,只举这样一个例子,说明逐步细化的过程)。这样做的目的是先消除漏匹配(刚开始先尽可能多 地匹配,做加法),然后再一点一点地消除误匹配(做减法)。这样有先有后,在考虑时才不易出错,从而向“不误不漏”这个目标迈进。
留有余地。所能看到的文本sample是有限的,而待匹配检验的文本是海量的,暂时不可见的。对于这样的情况,在写正则表达 式时要跳出所能见到的文本的圈子,开拓思路,作出“战略性前瞻”。例如,经常收到这样的垃圾短信:“发*票”、“发#漂”。如果要写规则屏蔽这样烦人的垃 圾短信,不但要能写出可以匹配当前文本的正则表达式 发[*#](?:票|漂),还要能够想到 发.(?:票|漂|飘)之类可能出现的“变种”。这在具体的领域或许会有针对性的规则,不多言。这样做的目的是消除漏匹配,延长正则表达式的生命周期。
明确。具体说来,就是谨慎用点号这样的元字符,尽可能不用星号和加号这样的任意量词。只要能确 定范围的,例如\w,就不要用点号;只要能够预测重复次数的,就不要用任意量词。例如,写析取twitter消息的脚本,假设一条消息的xml正文部分结 构是<span class=”msg”>…</span>且正文中无尖括号,那么<span class=”msg”>[^<]{1,480}</span>这种写法的思路要好于<span class=”msg”>.*</span>,原因有二:一是使用[^<],它保证了文本的范围不会超出下一个小于号所在的位置;二是明确长度范围,{1,480},其依据是一条twitter消息大致能的字符长度范围。当然,480这个长度是否正确还可推敲,但是这种思路是值得借鉴的。说得狠一点,“滥用点号、星号和加号是不环保、不负责任的做法”。
不要让稻草压死骆驼。每使用一个普通括号()而不是非捕获型括号(?:…),就会保留一部分内存等着你再次访问。这样的正则表达式、无限次地运行次数,无异于一根根稻草的堆加,终于能将骆驼压死。养成合理使用(?:…)括号的习惯。
宁简勿繁。将一条复杂的正则表达式拆分为两条或多条简单的正则表达式,编程难度会降低,运行效率会提升。例如用来消除行首和行尾空白字符的正则表达式s/^\s+|\s+$//g;,其运行效率理论上要低于s/^\s+//g; s/\s+$//g; 。这个例子出自《精通正则表达式》第五章,书中对它的评论是“它几乎总是最快的,而且显然最容易理解”。既快又容易理解,何乐而不为?工作中我们还有其它的理由要将C==(A|B)这 样的正则表达式拆为A和B两条表达式分别执行。例如,虽然A和B这两种情况只要有一种能够击中所需要的文本模式就会成功匹配,但是如果只要有一条子表达式 (例如A)会产生误匹配,那么不论其它的子表达式(例如B)效率如何之高,范围如何精准,C的总体精准度也会因A而受到影响。
巧妙定位。有时候,我们需要匹配的the,是作为单词的the(两边有空格),而不是作为单词一部分的t-h-e的有序排列(例如together中的the)。在适当的时候用上^,$,\b等等定位锚点,能有效提升找到成功匹配、淘汰不成功匹配的效率。
正则表达式: http://114.xixik.com/regex/

JDBC

MySql常见的函数

MySql函数的介绍

使用MySql函数的目的

为了简化操作,MySql提供了大量的函数给程序员使用(比如你想输入当前时间,可以调用now()函数)

函数可以出现的位置

插入语句的values()中,更新语句中,删除语句中,查询语句及其子句中。

环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 用户表
CREATE TABLE t_user (
id int primary key AUTO_INCREMENT,
uname varchar(40) DEFAULT NULL,
age int DEFAULT NULL,
sex int DEFAULT NULL
);

insert into t_user values (null,'zs',18,1);
insert into t_user values (null,'ls',20,0);
insert into t_user values (null,'ww',23,1);
insert into t_user values (null,'zl',24,1);
insert into t_user values (null,'lq',15,0);
insert into t_user values (null,'hh',12,0);
insert into t_user values (null,'wzx',60,null);
insert into t_user values (null,'lb',null,null);

if相关函数

if函数

语法
1
if(expr1,expr2,expr3)

说明: 如果 expr1 是TRUE,则 IF()的返回值为expr2; 否则返回值则为 expr3。if() 的返回值为数字值或字符串值,具体情况视其所在语境而定。

示例

练习1:获取用户的姓名、性别,如果性别为1则显示1,否则显示0;要求使用if函数查询:

1
SELECT uname, IF(sex, 1, 0) FROM t_user;

image-20230921123057832

ifnull函数

语法
1
ifnull(expr1,expr2)

说明:假如expr1 不为 NULL,则 IFNULL() 的返回值为 expr1; 否则其返回值为 expr2。ifnull()的返回值是数字或是字符串,具体情况取决于其所使用的语境。

示例

练习1:获取用户的姓名、性别,如果性别为null则显示为1;要求使用ifnull函数查询:

1
SELECT uname, IFNULL(sex, 1) FROM t_user;

image-20230921123102281

字符串函数

字符串连接函数

字符串连接函数主要有2个:

函数或操作符 描述
concat(str1, str2, …) 字符串连接函数,可以将多个字符串进行连接
concat_ws(separator, str1, str2, …) 可以指定间隔符将多个字符串进行连接;

练习1:使用concat函数显示出 你好,uname 的结果

1
SELECT CONCAT('你好,' , uname) FROM t_user;

练习2:使用concat_ws函数显示出 你好,uname 的结果

1
SELECT CONCAT_WS(',', '你好', uname) FROM t_user;

字符串大小写处理函数

字符串大小写处理函数主要有2个:

函数或操作符 描述
upper(str) 得到str的大写形式
lower(str) 得到str的小写形式

练习1: 将字符串 hello 转换为大写显示

1
SELECT UPPER('hello'); -- HELLO

练习2:将字符串 heLLo 转换为小写显示

1
SELECT LOWER('heLLo'); -- hello

移除空格函数

可以对字符串进行按长度填充满、也可以移除空格符

函数或操作符 描述
trim(str) 将str两边的空白符移除

练习1: 将用户id位8的用户的姓名的两边空白符移除

1
2
-- 表中数据是:'      lb   ', 使用trim后是: 'lb'
SELECT TRIM(uname) FROM t_user WHERE id = 8;

子串函数

字符串也可以按条件进行截取,主要有以下可以截取子串的函数;

函数或操作符 描述
substr()、substring() 获取子串: 1:substr(str, pos) 、substring(str, pos); 2:substr(str, pos, len)、substring(str, pos, len)

练习1:获取 hello,world 从第二个字符开始的完整子串

1
SELECT SUBSTR("hello,world", 2);  -- ello,world

练习2:获取 hello,world 从第二个字符开始但是长度为4的子串

1
SELECT SUBSTR("hello,world", 2, 4); -- ello

时间日期函数

mysql提供了一些用于获取特定时间的函数:

函数或操作符 描述
current_date() 获取当前日期,如 2019-10-18
current_tme() 获取当前时:分:秒,如:15:36:11
now() 获取当前的日期和时间,如:2019-10-18 15:37:17

练习1:获取当前的日期

1
select cruuent_date();

练习2: 获取当前的时间(仅仅需要时分秒)

1
select current_time();

练习3: 获取当前时间(包含年月日时分秒)

1
select now();

数值函数

常见的数值相关函数如下表:

函数或操作符 描述
abs(x) 获取数值x的绝对值
ceil(x) 向上取整,获取不小于x的整数值
floor(x) 向下取整,获取不大于x的整数值
pow(x, y) 获取x的y次幂
rand() 获取一个0-1之间的随机浮点数

练习1: 获取 -12 的绝对值

1
select abs(-12);

练习2: 将 -11.2 向上取整

1
select ceil(-11.2);

练习3: 将 1.6 向下取整

1
select floor(1.6);

练习4: 获得2的32次幂的值

1
select pow(2, 32);

练习5: 获得一个在0-100之间的随机数

1
select rand()*100;

JDBC入门

JDBC概述

  1. 没有JDBC

image-20230921123113554

  1. 有了JDBC后

image-20230921123120353

image-20230114094528034

什么是JDBC

JDBC(java database connectivity): sun公司为了简化和统一java连接数据库,定义的一套规范(API,接口).

JDBC和驱动的关系

接口(JDBC)与实现(驱动jar包)的关系

小结

  1. 为什么要学习JDBC? 为了java连接/操作数据库更加的简单方便, 学习成本减低

  2. JDBC和驱动关系?

    • Jdbc 规范(大量接口,少量的类)

    • 驱动 实现

JDBC快速入门

准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create database day18;

use day18;

create table user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
nickname varchar(20)
);

INSERT INTO `USER` VALUES(null,'zs','123456','老张');
INSERT INTO `USER` VALUES(null,'ls','123456','老李');
INSERT INTO `USER` VALUES(null,'wangwu','123','东方不败');

需求

查询所有的用户, 输出到控制台

步骤

  1. 创建Java工程, 拷贝驱动jar包
  2. 加载驱动
  3. 获得连接
  4. 创建执行sql语句对象
  5. 执行sql语句, 处理结果
  6. 释放资源

代码实现

  1. 驱动jar包导入项目

image-20230921123133530

image-20230921123143406

image-20230921123153181

  1. 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Test {
public static void main(String[] args) throws Exception{
// 需求:查询所有的用户, 输出到控制台
// 分析:
// 1.创建项目,拷贝驱动jar包到模块下,并添加到classpath路径中
// 2.注册驱动
DriverManager.registerDriver(new Driver());

// 3.获得连接
String url = "jdbc:mysql://localhost:3306/day20_1";
String username = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url,username,password);

// 4.创建执行sql的对象
Statement statement = connection.createStatement();

// 5.执行sql语句,处理结果
String sql = "select * from user";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
// 取出来
//System.out.print(resultSet.getObject("id")+" ");
//System.out.print(resultSet.getObject("username")+" ");
//System.out.print(resultSet.getObject("password")+" ");
//System.out.println(resultSet.getObject("nickname"));
System.out.print(resultSet.getObject(1)+" ");
System.out.print(resultSet.getObject(2)+" ");
System.out.print(resultSet.getObject(3)+" ");
System.out.println(resultSet.getObject(4));
System.out.println("------------------------------------------------");
}

// 6.释放资源
if (resultSet != null){
resultSet.close();
}

if (statement != null){
statement.close();
}

if (connection != null){
connection.close();
}
}
}

小结

  1. 加载,注册驱动
  2. 获得连接
  3. 创建执行sql语句对象
  4. 执行sql语句,处理结果
  5. 释放资源

JDBC API详解

image-20230921123203866

API-Drivermanager类

能够使用DriverManager类

1.registerDriver(Driver driver) ;注册驱动

1
2
3
4
5
6
7
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}

翻阅源码发现,通过API的方式注册驱动,Driver会new两次,所有推荐这种写法:

Class.forName("com.mysql.jdbc.Driver");  //当前就理解成 可以让com.mysql.jdbc.Driver里面的静态代码块执行
  1. getConnection(String url, String user, String password) ;与数据库建立连接

image-20230921123215905

作用:

  1. 注册驱动
  2. 获得连接

API-Connection接口

能够使用Connection接口

  1. 概述: 接口的实现在数据库驱动中。所有与数据库交互都是基于连接对象的。

  2. 作用

1
2
createStatement() ;创建执行sql语句对象
prepareStatement(String sql) ;创建预编译执行sql语句的对象
  1. Connection代表连接对象, 是一个接口, 实现在驱动jar包; 操作数据库都是基于Connection的

  2. 作用: connection.createStatement(); 创建执行sql语句对象

API-Statement接口

能够使用Statement接口

  1. 概述: 接口的实现在数据库驱动中. 用来**操作sql语句(增删改查)**,并返回相应结果对象

  2. 作用

1
2
3
4
5
ResultSet  executeQuery(String sql) 根据查询语句返回结果集。只能执行**select**语句。
int executeUpdate(String sql) 根据执行的DML(insert update delete)语句,返回受影响的行数。
boolean execute(String sql) 此方法可以执行任意sql语句。返回boolean值. 【了解】
true: 执行select有查询的结果
false: 执行insert, delete,update, 执行select没有查询的结果
  1. statement 作用用来执行sql语句
  2. 执行查询
1
ResultSet excuteQuery(String sql);    //返回值是结果集
  1. 执行增删改
1
int excuteUpdate(String sql);  //返回值是受影响的行数

API-ResultSet接口

掌握ResultSet接口的使用

  1. 封装结果集,查询结果表的对象;

    提供一个游标,默认游标指向结果集第一行之前。

    调用一次next(),游标向下移动一行。

    提供一些get方法。

  2. ResultSet接口常用API

    • boolean next();将光标从当前位置向下移动一行
    • int getInt(int colIndex)以int形式获取ResultSet结果集当前行指定列号值
    • int getInt(String colLabel)以int形式获取ResultSet结果集当前行指定列名值
    • float getFloat(int colIndex)以float形式获取ResultSet结果集当前行指定列号值
    • float getFloat(String colLabel)以float形式获取ResultSet结果集当前行指定列名值
    • String getString(int colIndex)以String 形式获取ResultSet结果集当前行指定列号值
    • String getString(String colLabel)以String形式获取ResultSet结果集当前行指定列名值
    • Date getDate(int columnIndex); 以Date 形式获取ResultSet结果集当前行指定列号值
    • Date getDate(String columnName);以Date形式获取ResultSet结果集当前行指定列名值
    • void close()关闭ResultSet 对象

图解

image-20191202110340587

封装

  • 定义一个User类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.nbchen.bean;

/**
* @Description: user实体类, 封装数据的
* @Author: yp
*/
public class User {

private int id;
private String username;
private String password;
private String nickname;

//提供get/set方法 Alt+Insert


public User() {
}

public User(int id, String username, String password, String nickname) {
this.id = id;
this.username = username;
this.password = password;
this.nickname = nickname;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getNickname() {
return nickname;
}

public void setNickname(String nickname) {
this.nickname = nickname;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
  • 封装
1
2
3
4
5
6
7
8
9
10
List<User> list = new ArrayList<User>();

while (resultSet.next()) {
//每遍历一次, 就是1条记录, 就封装成一个User对象
User user = new User(resultSet.getInt("id"),
resultSet.getString("username"),
resultSet.getString("password"),
resultSet.getString("nickname"));
list.add(user);
}

API-总结

API

  1. DriverManager:驱动管理器
    • 注册驱动
    • 获得连接
  2. Connection: 代表连接对象
    • 创建执行sql语句对象
    • 创建预编译sql语句对象
  3. Statement: 执行sql语句对象
    • 执行查询 Result executeQuery(String sql) 返回结果集
    • 执行增删改 int excuteUpdate(String sql) 返回受影响的行数
  4. ResultSet: 结果集
    • boolen next() 每调用一次, 光标就向下移动一行; 这个行有数据, 返回true; 没有数据, 返回false
    • get类型(String 列名); 根据列名 获得当前列的数据

注意事项

  • 包名

image-20230921123228481

JDBC操作数据库练习

单元测试介绍和使用

JUnit介绍

JUnit是一个Java语言的单元测试jar。属于第三方工具,一般情况下需要导入jar包,不过,多数Java开发环境已经集成了JUnit作为单元测试工具.编写测试类,简单理解可以用于取代java的main方法

使用

  • 在测试类方法上添加注解@Test

  • 注解修饰的方法要求:public void 方法名() {…} ,方法名自定义,==没有参数==。

image-20230921123233761

  • 添加IDEA中集成的Junit库,使用快捷键“Alt+Enter”,点击“Add Junit …”

image-20230921123238850

  • 使用:选中方法右键,执行当前方法或者选中类名右键,执行类中所有方法(方法必须标记@Test)

image-20230921123246022

小结

  1. 常见使用错误,如果没有添加“@Test”,使用“Junit Test”进行运行,将抛异常

  2. 单元测试需要注意的地方:

image-20230921123331101

增删改查练习

  • 使用JDBC完成增删改查练习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package com.nbchen.demo6_JDBC增删改查练习;

import com.nbchen.demo1_JDBC快速入门.User;
import org.junit.Test;

import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;


public class Demo {

@Test
// 往day20_1数据库的user表中添加一条记录
public void insert() throws Exception{
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");

// 2.获得连接
String url = "jdbc:mysql://localhost:3306/day20_1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);

// 3.创建执行sql语句对象
Statement statement = connection.createStatement();

// 4.执行sql语句,处理结果
String sql = "insert into user values(null,'zl','123456','赵六')";
int rows = statement.executeUpdate(sql);
System.out.println("受影响的行数:"+rows);

// 5.释放资源
if (statement != null){
statement.close();
}

if (connection != null){
connection.close();
}
}

@Test
// 修改day20_1数据库的user表中wangwu的密码为123456
public void update()throws Exception{
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");

// 2.获得连接
String url = "jdbc:mysql://localhost:3306/day20_1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);

// 3.创建执行sql语句对象
Statement statement = connection.createStatement();

// 4.执行sql语句,处理结果
String sql = "update user set password = '123456' where username = 'wangwu'";
int rows = statement.executeUpdate(sql);
System.out.println("受影响的行数:"+rows);

// 5.释放资源
if (statement != null){
statement.close();
}

if (connection != null){
connection.close();
}
}

@Test
// 删除day20_1数据库的user表中id为3的记录
public void delete() throws Exception{
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");

// 2.获得连接
String url = "jdbc:mysql://localhost:3306/day20_1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);

// 3.创建执行sql语句对象
Statement statement = connection.createStatement();

// 4.执行sql语句,处理结果
String sql = "delete from user where id = 3";
int rows = statement.executeUpdate(sql);
System.out.println("受影响的行数:"+rows);

// 5.释放资源
if (statement != null){
statement.close();
}

if (connection != null){
connection.close();
}
}

@Test
// 查询day20_1数据库的user表中所有记录
public void select1() throws Exception{
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");

// 2.获得连接
String url = "jdbc:mysql://localhost:3306/day20_1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);

// 3.创建执行sql语句对象
Statement statement = connection.createStatement();

// 4.执行sql语句,处理结果
String sql = "select * from user";
ResultSet resultSet = statement.executeQuery(sql);

// 创建ArrayList集合,限制集合元素类型为User
ArrayList<User> list = new ArrayList<>();

while (resultSet.next()) {
// 创建一个User对象,用来封装记录的列数据
User use = new User();
// 封装数据
use.setId(resultSet.getInt("id"));
use.setUsername(resultSet.getString("username"));
use.setPassword(resultSet.getString("password"));
use.setNickname(resultSet.getString("nickname"));

// 添加对象到集合
list.add(use);
}

// 5.释放资源
if (resultSet != null){
resultSet.close();
}

if (statement != null){
statement.close();
}

if (connection != null){
connection.close();
}

//...
for (User user1 : list) {
System.out.println(user1);
}
}

@Test
// 查询day20_1数据库的user表中姓名为zs并且密码为123456的记录
public void select2() throws Exception{
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");

// 2.获得连接
String url = "jdbc:mysql://localhost:3306/day20_1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);

// 3.创建执行sql语句对象
Statement statement = connection.createStatement();

// 4.执行sql语句,处理结果
String sql = "select * from user where username = 'zs' and password = '123456'";
ResultSet resultSet = statement.executeQuery(sql);

User use = null;

while (resultSet.next()) {
// 创建一个User对象,用来封装记录的列数据
use = new User();

// 封装数据
use.setId(resultSet.getInt("id"));
use.setUsername(resultSet.getString("username"));
use.setPassword(resultSet.getString("password"));
use.setNickname(resultSet.getString("nickname"));
}
// 5.释放资源
if (resultSet != null){
resultSet.close();
}

if (statement != null){
statement.close();
}

if (connection != null){
connection.close();
}
// ...
if (use == null){
System.out.println("失败");
}else{
System.out.println("成功");
}
}
}

JDBC工具类的抽取

  • 创建配置文件,配置文件在src目录下,扩展名是properties

    image-20230921123417021

    配置文件:

1
2
3
4
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day20_1
username=root
password=root

  • 工具类实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.nbchen.Utils;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {

private static String driver;
private static String url;
private static String user;
private static String password;

static {
try {
// 使用Properties对象加载配置文件
// 创建Properties对象
Properties pro = new Properties();

// 读取配置文件中的数据
//pro.load(new FileInputStream("day20\\src\\db.properties"));
// 返回的输入流的路径默认到src路径
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");
pro.load(is);

// 通过pro对象给属性赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");

// 1.加载驱动
Class.forName(driver);

} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 获得连接
*
* @return 连接
* @throws Exception
*/
public static Connection getConnection() throws Exception {
// 2.获得连接
Connection connection = DriverManager.getConnection(url, user, password);

return connection;
}

/**
* 释放资源
*
* @param resultSet
* @param statement
* @param connection
*/
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

}
}

小结

  1. 注意事项

    • 配置文件建议定义在src目录

    • 使用的getProperty()方法里面的参数应该和配置文件里面的key一致, 加””

    image-20230921123427200

PreparedStatement

登录案例

需求

​ 在控制台输入用户名和密码,查询数据库,如果数据库存在当前用户,显示登录成功!

​ 如果数据库不存在当前用户,显示登录失败!

img

分析

登录是做什么

  • 登录说白了就是根据==用户名和密码查询数据库==, 如果能查询出来就是登录成功, 查询不出来就是登录失败

思路分析

image-20191202144713867

代码实现

  • LoginClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.nbchen.demo5_登录案例;

import com.nbchen.demo2_JDBC_API详解.User;
import com.nbchen.demo4_JDBC工具类的抽取.JDBCUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;


public class Test {
public static void main(String[] args) throws Exception{
/*
需求:
在控制台输入用户名和密码,查询数据库,如果数据库存在当前用户,显示登录成功!
如果数据库不存在当前用户,显示登录失败!
*/
// 0.获取控制台输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();

// 1.注册驱动,获得连接
Connection connection = JDBCUtils.getConnection();

// 2.创建执行sql语句对象
Statement statement = connection.createStatement();

// 3.执行sql语句,处理结果 select * from user where username = 'zs' and password = '123456';
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
ResultSet resultSet = statement.executeQuery(sql);
// 创建User对象,封装数据
User user = null;
while (resultSet.next()) {
user = new User();// 查询到了就new对象
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
// 4.释放资源
JDBCUtils.release(resultSet,statement,connection);
// 5.判断结果,显示信息(判断user对象是否为null,如果为null说明就没有查询到数据,否则就查询到了)
if (user == null){
// 没有查询到
System.out.println("登录失败!");
}else{
// 查询到
System.out.println("登录成功,欢迎回来!");
}

}
}

小结

登录思路
  • 登录说白了就是根据用户名和密码查询数据库
  • 登录思路
    • 获得用户输入的用户名和密码
    • 使用Jdbc根据用户名和密码查询数据库 封装成User对象
    • 判断是否登录成功(判断User是否为null)
      • 登录成功 打印 ‘登录成功’
      • 登录失败 打印 登录失败’
SQL注入问题出现

当输入的密码 ' or '' = ' , 发现永远登录成功

image-20230921123526561

SQL注入问题分析
  • 输入的密码 ' or '' = ', 语句如下
1
2
3
4
SELECT * FROM user WHERE username ='zs' AND password = '' or '' = ''
SELECT * FROM user WHERE username ='zs' AND password = '' or true
SELECT * FROM user WHERE true
SELECT * FROM user
  • 发现语句出现了sql注入问题

    把用户输入的 or 当成关键词注入到了sql语句里面了

登录中SQL注入问题解决

preparedStatement概述

预编译SQL语句对象, 是Statemen接口的子接口。

特点:

  • 性能要比Statement高
  • 会把sql语句先编译,格式固定好,
  • sql语句中的参数会发生变化,过滤掉用户输入的关键字(eg: or)

用法

通过connection对象创建
  • connection.prepareStatement(String sql) ;创建prepareStatement对象

  • sql表示预编译的sql语句,如果sql语句有参数通过?来占位

    1
    SELECT * FROM user WHERE username = ? AND password = ?
设置参数
  • prepareStatement.set类型(int i,Object obj);参数1 i 指的就是问号的索引(指第几个问号,从1开始),参数2就是值 eg: setString(1,”zs”); setString(2,”123456”);
执行
  • ResultSet executeQuery(); 执行查询语句
    int executeUpdate();执行增删改语句
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67



    #### 实现

    ```java
    package com.nbchen.demo6_登录中SQL注入问题解决;

    import com.nbchen.demo2_JDBC_API详解.User;
    import com.nbchen.demo4_JDBC工具类的抽取.JDBCUtils;

    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.Statement;
    import java.util.Scanner;

    public class Test {
    public static void main(String[] args) throws Exception{
    /*
    需求:
    在控制台输入用户名和密码,查询数据库,如果数据库存在当前用户,显示登录成功!
    如果数据库不存在当前用户,显示登录失败!
    */
    // 0.获取控制台输入的用户名和密码
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入用户名:");
    String username = sc.nextLine();
    System.out.println("请输入密码:");
    String password = sc.nextLine();

    // 1.注册驱动,获得连接
    Connection connection = JDBCUtils.getConnection();

    // 2.创建预编译sql语句对象
    String sql = "select * from user where username = ? and password = ?";
    PreparedStatement ps = connection.prepareStatement(sql);// 固定sql语句的格式

    // 3.设置参数值
    ps.setString(1,username);
    ps.setString(2,password);

    // 3.执行sql语句,处理结果
    ResultSet resultSet = ps.executeQuery();// 注意: 这里不需要传sql语句

    // 创建User对象,封装数据
    User user = null;
    while (resultSet.next()) {
    user = new User();// 查询到了就new对象
    user.setId(resultSet.getInt("id"));
    user.setUsername(resultSet.getString("username"));
    user.setPassword(resultSet.getString("password"));
    user.setNickname(resultSet.getString("nickname"));
    }
    // 4.释放资源
    JDBCUtils.release(resultSet,ps,connection);

    // 5.判断结果,显示信息(判断user对象是否为null,如果为null说明就没有查询到数据,否则就查询到了)
    if (user == null){
    // 没有查询到
    System.out.println("登录失败!");
    }else{
    // 查询到
    System.out.println("登录成功,欢迎回来!");
    }
    }
    }

使用preparedStatement完成CRUD

需求

​ 通过PreparedStatement完成增、删、改、查

分析

  1. 注册驱动
  2. 获得连接
  3. 创建预编译sql语句对象
  4. 设置参数 执行
  5. 释放资源

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.nbchen.demo10_使用preparedStatement完成CRUD;

import com.nbchen.Utils.JDBCUtils;
import com.nbchen.demo1_JDBC快速入门.User;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;


public class Demo {
@Test
// 往day20_1数据库的user表中插入一条记录
public void insert() throws Exception{
// 1.加载驱动,获得连接
Connection connection = JDBCUtils.getConnection();

// 2.创建预编译sql语句对象
String sql = "insert into user values(null,?,?,?)";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数
ps.setString(1,"tq" );
ps.setString(2,"123456" );
ps.setString(3,"田七" );

// 4.执行sql语句
int rows = ps.executeUpdate();
System.out.println("受影响的行数:"+rows);

// 5.释放资源
JDBCUtils.release(null, ps, connection);
}

@Test
// 修改day20_1数据库的user表中的id为7的密码为abcdef
public void update() throws Exception{
// 1.加载驱动,获得连接
Connection connection = JDBCUtils.getConnection();

// 2.创建预编译sql语句对象
String sql = "update user set password = ? where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数
ps.setString(1,"abcdef" );
ps.setInt(2,7 );

// 4.执行sql语句
int rows = ps.executeUpdate();
System.out.println("受影响的行数:"+rows);

// 5.释放资源
JDBCUtils.release(null, ps, connection);
}

@Test
// 删除day20_1数据库的user表中的id为5
public void delete() throws Exception{
// 1.加载驱动,获得连接
Connection connection = JDBCUtils.getConnection();

// 2.创建预编译sql语句对象
String sql = "delete from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数
ps.setInt(1,5 );

// 4.执行sql语句
int rows = ps.executeUpdate();
System.out.println("受影响的行数:"+rows);

// 5.释放资源
JDBCUtils.release(null, ps, connection);
}

@Test
// 查询day20_1数据库的user表中的所有数据
public void select1() throws Exception{
// 1.注册驱动,获得连接
Connection connection = JDBCUtils.getConnection();

// 2.创建预编译sql语句对象
String sql = "select * from user";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数(不需要,因为sql语句没有参数)

// 4.执行sql语句,处理结果
ResultSet resultSet = ps.executeQuery();
// 创建ArrayList集合,限制集合元素类型为User
ArrayList<User> list = new ArrayList<User>();
while (resultSet.next()) {
// 创建User对象
User user = new User();
// 给属性赋值
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
// 添加到集合中
list.add(user);
}

// 5.释放资源
JDBCUtils.release(resultSet, ps, connection);

for (User user : list) {
System.out.println(user);
}
}


@Test
// 查询day20_1数据库的user表中姓名为zs
public void select2() throws Exception{
// 1.注册驱动,获得连接
Connection connection = JDBCUtils.getConnection();

// 2.创建预编译sql语句对象
String sql = "select * from user where username = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数
ps.setString(1,"zs" );

// 4.执行sql语句,处理结果
ResultSet resultSet = ps.executeQuery();

User user = null;

while (resultSet.next()) {
// 创建User对象
user = new User();
// 给属性赋值
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}

// 5.释放资源
JDBCUtils.release(resultSet, ps, connection);

System.out.println(user);
}

}

小结

步骤
  1. 注册驱动
  2. 获得连接
  3. 创建预编译sql语句对象
  4. 设置参数,
  5. 执行sql语句
  6. 释放资源
API
  1. 创建预编译sql语句对象
1
connection.prepareStatement(String sql); //sql里面有参数, 先用?代替,进行占位
  1. 设置参数
1
prepareStatement.set类型(int 第几个问号,Object 值);
  1. 执行
1
2
Result result = prepareStatement.excuteQuery();  //执行查询 不传sql语句
int rows = prepareStatement.excuteUpdate(); //执行增删改 不传sql语句
注意事项
  1. ?只能占参数,说白了就是列的值

  2. ?从1开始计

  3. 执行的时候不要传入sql语句

JDBC事务的处理

JDBC事务介绍

之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作事务. 先来学习下相关的API

Connection中与事务有关的方法 说明
setAutoCommit(boolean autoCommit) 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction;
void commit() 提交事务; 类似sql里面的 commit;
void rollback() 回滚事务; 类似sql里面的 rollback;

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Test {
public static void main(String[] args) throws Exception{
/*
Connection接口:
setAutoCommit(boolean autoCommit) 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction;
void commit() 提交事务; 类似sql里面的 commit;
void rollback() 回滚事务; 类似sql里面的 rollback;
*/
// 1.注册驱动获得连接
Connection connection = JDBCUtils.getConnection();

// 2.开启事务
connection.setAutoCommit(false);

// 3.执行sql语句
String sql = "update user set password = ? where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 设置参数
ps.setString(1, "666666");
ps.setInt(2,6);

// 执行sql语句
int rows = ps.executeUpdate();
System.out.println("受影响的行数:"+rows);

// 4.提交或者回滚事务
//connection.commit();

connection.rollback();

// 5.释放资源
JDBCUtils.release(null,ps,connection);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Test {
public static void main(String[] args) {
/*
Connection接口:
setAutoCommit(boolean autoCommit) 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction;
void commit() 提交事务; 类似sql里面的 commit;
void rollback() 回滚事务; 类似sql里面的 rollback;
*/
Connection connection = null;
PreparedStatement ps = null;
try {
// 1.注册驱动获得连接
connection = JDBCUtils.getConnection();

// 2.开启事务
connection.setAutoCommit(false);

// 3.执行sql语句
String sql = "update user set password = ? where id = ?";
ps = connection.prepareStatement(sql);

// 设置参数
ps.setString(1, "666666");
ps.setInt(2, 6);

// 执行sql语句
int rows = ps.executeUpdate();
System.out.println("受影响的行数:" + rows);

System.out.println(1/0);// 出现异常

// 4.提交或者回滚事务
connection.commit();

} catch (Exception e) {
e.printStackTrace();
// 如果有异常,就回滚事务
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {

// 5.释放资源
JDBCUtils.release(null, ps, connection);
}
}
}

转账案例

  • 案例的准备工作
1
2
3
4
5
6
7
8
9
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);

insert into account values (null,'zs',1000);
insert into account values (null,'ls',1000);
insert into account values (null,'ww',1000);

需求

​ zs给ls转100, 使用事务进行控制

分析

image-20191202161110453

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Test {
public static void main(String[] args) {
// 需求: zs给ls转100, 使用事务进行控制
Connection connection = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
try {
// 1.注册驱动,获得连接
connection = JDBCUtils.getConnection();

// 2.***************开启事务***********************
connection.setAutoCommit(false);

// 3.书写sql语句,预编译sql语句,得到预编译对象
String sql1 = "update account set money = money - ? where name = ?";
String sql2 = "update account set money = money + ? where name = ?";
ps1 = connection.prepareStatement(sql1);
ps2 = connection.prepareStatement(sql2);

// 4.设置参数,执行sql语句
// zs账户-100
ps1.setDouble(1,100);
ps1.setString(2,"zs");
ps1.executeUpdate();

// 模拟张三-100之后,李四还没+100就出现异常
System.out.println(1/0);

// ls账户+100
ps2.setDouble(1,100);
ps2.setString(2,"ls");
ps2.executeUpdate();

// 5.***********************如果成功,就提交事务;******************
connection.commit();
} catch (Exception e) {
e.printStackTrace();
// 5.*********************如果失败,就回滚;**********************
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 6.释放资源
JDBCUtils.release(null,ps1,connection);
JDBCUtils.release(null,ps2,null);
}
}
}

小结

  1. 涉及到两个写的操作,我们一般通过手动事务去控制
  2. JDBC操作事务API
1
2
3
connection.setAutoCommit(fasle);  //开启事务
connection.commit(); //提交事务
connection.rollback(); //回滚事务

自定义连接池

连接池概念

为什么要使用连接池

​ Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优化.

​ 程序初始化的时候,初始化多个连接,将多个连接放入到池(集合)中.每次获取的时候,都可以直接从连接池中进行获取.使用结束以后,将连接归还到池中.

生活里面的连接池例子

  • 老方式:

    ​ 下了地铁需要骑车, 跑去生产一个, 然后骑完之后,直接把车销毁了.

  • 连接池方式 摩拜单车:

    ​ 骑之前, 有一个公司生产了很多的自行车, 下了地铁需要骑车, 直接扫码使用就好了, 然后骑完之后, 还回去

image-20230921123555617

连接池原理【重点】

image-20230921123601966

  1. 程序一开始就创建一定数量的连接,放在一个容器(集合)中,这个容器称为连接池。
  2. 使用的时候直接从连接池中取一个已经创建好的连接对象, 使用完成之后 归还到池子
  3. 如果池子里面的连接使用完了, 还有程序需要使用连接, 先等待一段时间(eg: 3s), 如果在这段时间之内有连接归还, 就拿去使用; 如果还没有连接归还, 新创建一个, 但是新创建的这一个不会归还了(销毁)
  4. 集合选择LinkedList
    • 增删比较快
    • LinkedList里面的removeFirst()和addLast()方法和连接池的原理吻合

小结

  1. 使用连接池的目的: 可以让连接得到复用, 避免浪费

自定义连接池-初级版本

目标

​ 根据连接池的原理, 使用LinkedList自定义连接池

分析

  1. 创建一个类MyDataSource, 定义一个集合LinkedList
  2. 程序初始化的时候, 创建5个连接 存到LinkedList
  3. 定义getConnection() 从LinkedList取出Connection返回
  4. 定义addBack()方法归还Connection到LinkedList

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class MyDataSource1 {
// 1.定义一个LinkedList集合,用来存储初始化的连接
private static LinkedList<Connection> list = new LinkedList<>();

// 2.初始化5个连接,并存储到集合中
static {
for (int i = 0; i < 5; i++) {
try {
// 获得连接
Connection connection = JDBCUtils.getConnection();
// 添加到集合中
list.add(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 3.定义getAbc方法,用来获得连接
public Connection getAbc(){
Connection connection = list.removeFirst();
return connection;
}

// 4.定义addBack方法,用来归还连接
public void addBack(Connection connection){
list.addLast(connection);
}

// 5.定义getCount方法,返回连接池中连接数量
public static int getCount(){
return list.size();
}
}

// 测试
public class CRUDTest1 {
// 查询记录
@Test
public void select() throws Exception {
// 1.创建连接池对象
MyDataSource1 dataSource = new MyDataSource1();
System.out.println("获得连接之前,连接池中连接的数量:"+MyDataSource1.getCount());// 5

// 2.获得连接
Connection connection = dataSource.getAbc();

// 2.书写sql语句,预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
// 3.设置参数
ps.setInt(1,3);
// 4.执行sql语句
ResultSet resultSet = ps.executeQuery();
// 封装,处理数据
User user = null;
while (resultSet.next()){
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
System.out.println("获得连接之后,连接池中连接的数量:"+MyDataSource1.getCount());// 4
System.out.println(user);

// 5.释放资源
// 归还连接
dataSource.addBack(connection);
JDBCUtils.release(resultSet,ps,null);

System.out.println("归还连接之后,连接池中连接的数量:"+MyDataSource1.getCount());// 5

}
}

小结

  1. 创建一个类MyDataSource, 定义一个集合LinkedList
  2. 程序初始化(静态代码块)里面 创建5个连接存到LinkedList
  3. 定义提供Connection的方法
  4. 定义归还Connection的方法

自定义连接池-进阶版本

目标

​ 实现datasource完成自定义连接池

分析

​ 在初级版本版本中, 我们定义的方法是getAbc(). 因为是自定义的.如果改用李四的自定义的连接池,李四定义的方法是getCon(), 那么我们的源码就需要修改, 这样不方便维护. 所以sun公司定义了一个接口DataSource,让自定义连接池有了规范

讲解

datasource接口概述

​ Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商(用户)需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!

image-20230921123615185

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public class MyDataSource2 implements DataSource {
// 1.定义一个LinkedList集合,用来存储初始化的连接
private static LinkedList<Connection> list = new LinkedList<>();

// 2.初始化5个连接,并存储到集合中
static {
for (int i = 0; i < 5; i++) {
try {
// 获得连接
Connection connection = JDBCUtils.getConnection();
// 添加到集合中
list.add(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
}

/*// 3.定义getAbc方法,用来获得连接
public Connection getAbc(){
Connection connection = list.removeFirst();
return connection;
}*/

// 4.定义addBack方法,用来归还连接
public void addBack(Connection connection){
list.addLast(connection);
}

// 5.定义getCount方法,返回连接池中连接数量
public static int getCount(){
return list.size();
}

@Override
public Connection getConnection() throws SQLException {
Connection connection = list.removeFirst();
return connection;
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}

@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}

@Override
public void setLogWriter(PrintWriter out) throws SQLException {

}

@Override
public void setLoginTimeout(int seconds) throws SQLException {

}

@Override
public int getLoginTimeout() throws SQLException {
return 0;
}

@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}


// 测试
public class CRUDTest2 {

// 查询记录
@Test
public void select() throws Exception {
// 1.创建连接池对象
DataSource dataSource = new MyDataSource2();
System.out.println("获得连接之前,连接池中连接的数量:"+MyDataSource2.getCount());// 5

// 2.获得连接
Connection connection = dataSource.getConnection();

// 2.书写sql语句,预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
// 3.设置参数
ps.setInt(1,3);
// 4.执行sql语句
ResultSet resultSet = ps.executeQuery();
// 封装,处理数据
User user = null;
while (resultSet.next()){
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
System.out.println("获得连接之后,连接池中连接的数量:"+MyDataSource2.getCount());// 4
System.out.println(user);

// 5.释放资源
// 归还连接
// 解决办法: 1.向下转型 2.增强Connection的close方法(默认是销毁连接,增强后变成归还连接)
((MyDataSource2)dataSource).addBack(connection);
JDBCUtils.release(resultSet,ps,null);

System.out.println("归还连接之后,连接池中连接的数量:"+MyDataSource2.getCount());// 5

}
}


小结

编写连接池遇到的问题
  • 实现DataSource接口后,addBack()不能调用了.
  • 能不能不引入新的api,直接调用之前的connection.close(),但是这个close不是关闭,是归还
解决办法
  • 继承

    • 条件:可以控制父类, 最起码知道父类的名字
  • 装饰者模式

    • 作用:改写已存在的类的某个方法或某些方法
    • 条件:
      • 增强类和被增强类实现的是同一个接口
      • 增强类里面要拿到被增强类的引用
  • 动态代理

自定义连接池-终极版本

使用装饰者模式改写connection的close()方法, 让connection归还

装饰者模式

概述

  • 什么是装饰者模式

    ​ 装饰者模式,是 23种常用的面向对象软件的设计模式之一. 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。

    ​ 装饰者的作用:改写已存在的类的某个方法或某些方法, 增强方法的逻辑

  • 使用装饰者模式需要满足的条件

    • 增强类和被增强类实现的是同一个接口
    • 增强类里面要拿到被增强类的引用

装饰者模式的使用【重点】

实现步骤:

  1. 增强类和被增强类需要实现同一个接口
  2. 增强类里面需要得到被增强类的引用,
  3. 对于不需要改写的方法,调用被增强类原有的方法。
  4. 对于需要改写的方法写自己的代码
  • 接口:
1
2
3
4
public interface Star {
public void sing();
public void dance();
}
  • 被增强的类:
1
2
3
4
5
6
7
8
9
10
11
12
public class LiuDeHua implements Star {
@Override
public void sing() {
System.out.println("刘德华唱忘情水...");
}

@Override
public void dance() {
System.out.println("刘德华跳霹雳舞...");
}
}

  • 增强的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LiuDeHuaWrapper implements Star {

// 增强类中获得被增强类的引用
LiuDeHua ldh;

public LiuDeHuaWrapper(LiuDeHua ldh) {
this.ldh = ldh;
}

@Override
public void sing() {
System.out.println("刘德华唱冰雨...");
ldh.sing();
System.out.println("刘德华唱练习...");
}

@Override
public void dance() {
// 不增强
ldh.dance();
}
}
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Test {
    public static void main(String[] args) {
    // 没有使用装饰者
    LiuDeHua ldh = new LiuDeHua();
    //ldh.sing();
    //ldh.dance();

    // 使用装饰者
    LiuDeHuaWrapper ldhw = new LiuDeHuaWrapper(ldh);
    ldhw.sing();
    ldhw.dance();
    }
    }

自定义连接池终极版本

分析

​ 增强connection的close()方法, 其它的方法逻辑不改

image-20191203101709735

  1. 创建MyConnection实现Connection
  2. 在MyConnection里面需要得到被增强的connection对象(通过构造方法传进去)
  3. 改写close()的逻辑, 变成归还
  4. 其它方法的逻辑, 还是调用被增强connection对象之前的逻辑
实现
  • MyConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MyConnection implements Connection {

// 获得被增强的连接对象的引用
Connection con;

// 获得连接池
LinkedList<Connection> list;

public MyConnection(Connection con, LinkedList<Connection> list) {
this.con = con;
this.list = list;
}

@Override
public void close() throws SQLException {
// 归还连接
list.addLast(con);
}

@Override
public Statement createStatement() throws SQLException {
return con.createStatement();
}
// 必须写
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return con.prepareStatement(sql);
}
// 剩余的重写方法,使用被增强的连接对象调用原有方法...
// ...
}

  • MyDataSource03
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class MyDataSource3 implements DataSource {
// 1.定义一个LinkedList集合,用来存储初始化的连接
private static LinkedList<Connection> list = new LinkedList<>();

// 2.初始化5个连接,并存储到集合中
static {
for (int i = 0; i < 5; i++) {
try {
// 获得连接
Connection connection = JDBCUtils.getConnection();
// 添加到集合中
list.add(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
}

/*// 3.定义getAbc方法,用来获得连接
public Connection getAbc(){
Connection connection = list.removeFirst();
return connection;
}*/

/*// 4.定义addBack方法,用来归还连接
public void addBack(Connection connection){
list.addLast(connection);
}*/

// 5.定义getCount方法,返回连接池中连接数量
public static int getCount(){
return list.size();
}

@Override
public Connection getConnection() throws SQLException {
Connection connection = list.removeFirst();// 返回的是被增强的连接对象
// 增强
MyConnection myConnection = new MyConnection(connection,list);
return myConnection;
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}

@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}

@Override
public void setLogWriter(PrintWriter out) throws SQLException {

}

@Override
public void setLoginTimeout(int seconds) throws SQLException {

}

@Override
public int getLoginTimeout() throws SQLException {
return 0;
}

@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}

  • 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class CRUDTest3 {

    // 查询记录
    @Test
    public void select() throws Exception {
    // 1.创建连接池对象
    DataSource dataSource = new MyDataSource3();
    System.out.println("获得连接之前,连接池中连接的数量:"+MyDataSource3.getCount());// 5

    // 2.获得连接
    Connection connection = dataSource.getConnection();// 返回的是增强的连接对象 MyConnection

    // 2.书写sql语句,预编译sql语句,得到预编译对象
    String sql = "select * from user where id = ?";
    PreparedStatement ps = connection.prepareStatement(sql);
    // 3.设置参数
    ps.setInt(1,3);
    // 4.执行sql语句
    ResultSet resultSet = ps.executeQuery();
    // 封装,处理数据
    User user = null;
    while (resultSet.next()){
    user = new User();
    user.setId(resultSet.getInt("id"));
    user.setUsername(resultSet.getString("username"));
    user.setPassword(resultSet.getString("password"));
    user.setNickname(resultSet.getString("nickname"));
    }
    System.out.println("获得连接之后,连接池中连接的数量:"+MyDataSource3.getCount());// 4
    System.out.println(user);

    // 5.释放资源
    // 归还连接
    //connection.close();// 归还连接
    JDBCUtils.release(resultSet,ps,connection);

    System.out.println("归还连接之后,连接池中连接的数量:"+MyDataSource3.getCount());// 5

    }
    }

小结

  1. 创建一个MyConnection实现Connection
  2. 在MyConnection得到被增强的connection对象
  3. 改写MyConnection里面的close()方法的逻辑为归还
  4. MyConnection里面的其它方法 调用被增强的connection对象之前的逻辑
  5. 在MyDataSource03的getConnection()方法里面 返回了myConnection(增强的连接对象)

第三方连接池

常用连接池

​ 通过前面的学习,我们已经能够使用所学的基础知识构建自定义的连接池了。其目的是锻炼大家的基本功,帮助大家更好的理解连接池的原理, 但现实是残酷的,我们所定义的 连接池 和第三方的连接池相比,还是显得渺小. 工作里面都会用第三方连接池.

常见的第三方连接池如下:

  • C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。C3P0是异步操作的,所以一些操作时间过长的JDBC通过其它的辅助线程完成。目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能
  • 阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求。
  • DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。dbcp没有自动回收空闲连接的功能。

C3P0

c3p0介绍

image-20230921125710805

  • C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml.
  • 使用C3P0需要添加c3p0-0.9.1.2.jar

c3p0的使用

通过硬编码来编写【了解】

步骤

  1. 拷贝jar
  2. 创建C3P0连接池对象
  3. 从C3P0连接池对象里面获得connection

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Test1_通过硬编码来编写 {
public static void main(String[] args) throws Exception {
// 1.拷贝c3p0jar包到模块下,并添加到classpath路径中
// 2.创建连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 连接池设置参数
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/day21_1");
dataSource.setUser("root");
dataSource.setPassword("root");
// 设置初始化连接数量
dataSource.setInitialPoolSize(5);

// 3.获得连接
Connection connection = dataSource.getConnection();

// 4.书写sql语句,预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 5.设置参数
ps.setInt(1, 3);

// 6.执行sql语句
ResultSet resultSet = ps.executeQuery();

// 封装,处理数据
User user = null;
while (resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
System.out.println(user);

// 7.释放资源---连接归还
JDBCUtils.release(resultSet,ps,connection);
}
}

通过配置文件来编写【重点】

步骤:

  1. 拷贝jar
  2. 拷贝配置文件(c3p0-config.xml)到src目录【名字不要改
  3. 创建C3P0连接池对象【自动的读取】
  4. 从池子里面获得连接

实现:

  • 编写配置文件c3p0-config.xml,放在src目录下(注:文件名一定不要改)
1
2
3
4
5
6
7
8
9
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day21_1</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">5</property>
</default-config>
</c3p0-config>
  • 编写Java代码 (会自动读取src目录下的c3p0-config.xml,所以不需要我们解析配置文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Test2_通过配置文件来编写 {
public static void main(String[] args) throws Exception{
// 1.拷贝c3p0jar包到模块下,并添加到classpath路径中
// 2.创建连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();

// 3.获得连接
Connection connection = dataSource.getConnection();

// 4.书写sql语句,预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 5.设置参数
ps.setInt(1, 3);

// 6.执行sql语句
ResultSet resultSet = ps.executeQuery();

// 封装,处理数据
User user = null;
while (resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
System.out.println(user);

// 7.释放资源---连接归还
JDBCUtils.release(resultSet,ps,connection);
}
}

使用c3p0改写工具类【重点】

​ 我们之前写的工具类(JdbcUtils)每次都会创建一个新的连接, 使用完成之后, 都给销毁了; 所以现在我们要使用c3p0来改写工具类. 也就意味着,我们从此告别了JdbcUtils. 后面会使用c3p0写的工具类

思路:

  1. 创建C3P0Utils这个类
  2. 定义DataSource, 保证DataSource全局只有一个
  3. 定义getConnection()方法从DataSource获得连接
  4. 定义closeAll()方法 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package Utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class C3P0Utils {
// 创建c3p0连接池对象
private static final ComboPooledDataSource DATA_SOURCE = new ComboPooledDataSource();

/**
* 获得连接的方法
* @return 连接
* @throws Exception
*/
public static Connection getConnection() throws Exception{
Connection connection = DATA_SOURCE.getConnection();
return connection;
}

/**
* 获得连接池的方法
* @return
*/
public static DataSource getDataSource(){
return DATA_SOURCE;
}

/**
* 释放资源
*
* @param resultSet
* @param statement
* @param connection
*/
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

}
}

小结

  1. C3P0 配置文件方式使用

    • 拷贝jar
    • 拷贝配置文件到src【配置文件的名字不要改】
    • 创建C3P0连接池对象
  2. C3P0工具类

    • 保证DataSource连接池只有一个【static】
    • 提供connection
    • 释放资源

    代替昨天写的JdbcUtils

DRUID

DRUID介绍

​ Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是国内目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。

Druid的下载地址:https://github.com/alibaba/druid 

DRUID连接池使用的jar包:druid-1.0.9.jar

image-20230921123640012

DRUID的使用

通过硬编码方式【了解】

步骤:

  1. 导入DRUID jar 包
  2. 创建Druid连接池对象, 配置4个基本参数
  3. 从Druid连接池对象获得Connection

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Test1_硬编码方式使用 {
@Test
public void select() throws Exception{
// 1.拷贝jar包,添加classpath路径
// 2.创建Druid连接池对象
DruidDataSource dataSource = new DruidDataSource();
// 设置参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/day21_1");
dataSource.setUsername("root");
dataSource.setPassword("root");

dataSource.setInitialSize(5);

// 3.获得连接
DruidPooledConnection connection = dataSource.getConnection();
78s
// 4.书写sql语句,预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 5.设置参数
ps.setInt(1, 3);

// 6.执行sql语句
ResultSet resultSet = ps.executeQuery();

// 封装,处理数据
User user = null;
while (resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
System.out.println(user);

// 7.释放资源---连接归还
JDBCUtils.release(resultSet,ps,connection);
}
}

通过配置文件方式【重点】

步骤:

  1. 导入DRUID jar 包
  2. 拷贝配置文件到src目录
  3. 根据配置文件创建Druid连接池对象
  4. 从Druid连接池对象获得Connection

实现:

  • 创建druid.properties, 放在src目录下
1
2
3
4
url=jdbc:mysql://localhost:3306/day21_1
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
  • 编写Java代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test2_配置文件方式使用 {
@Test
public void select() throws Exception{
// 1.拷贝jar包,添加classpath路径
// 2.创建Druid连接池对象
Properties prop = new Properties();
// 加载配置文件
InputStream is = Test2_通过配置文件来编写.class.getClassLoader().getResourceAsStream("druid.properties");
prop.load(is);
// 创建Druid连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

// 3.获得连接
Connection connection = dataSource.getConnection();

// 4.书写sql语句,预编译sql语句,得到预编译对象
String sql = "select * from user where id = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 5.设置参数
ps.setInt(1, 3);

// 6.执行sql语句
ResultSet resultSet = ps.executeQuery();

// 封装,处理数据
User user = null;
while (resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setNickname(resultSet.getString("nickname"));
}
System.out.println(user);

// 7.释放资源---连接归还
JDBCUtils.release(resultSet,ps,connection);
}
}

问题

突然无法登录,数据库druid连接池满了,无法获取连接。

wait millis 60000,active 20,maxactive 20

DBUtils

DBUtils的介绍

DBUtils的概述

​ DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能

DBUtils的常用API介绍

  1. 创建QueryRunner对象的API

    public QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection

  2. QueryRunner执行增删改的SQL语句的API

    int update(String sql, Object... params),执行增删改的SQL语句, params参数就是可变参数,参数个数取决于语句中问号的个数

    1
    eg: qr.update("insert into user values(null,?,?,?)","zs","123456","张三");
  3. 执行查询的SQL语句的API

    query(String sql, ResultSetHandler<T> rsh, Object... params),其中ResultSetHandler是一个接口,表示结果集处理者

小结

  1. DBUtils: Apache开发的一个数据库工具包, 用来简化JDBC操作数据库的步骤

JavaBean

目标

  • 理解JavaBean的字段和属性

讲解

  1. JavaBean说白了就是一个类, 用来封装数据用的

  2. JavaBean要求

    • 私有字段
    • 提供公共的get/set方法
    • 无参构造
    • 建议满参构造
    • 实现Serializable
  3. 字段和属性

    • 字段: 全局/成员变量 eg: private String username
    • 属性: 去掉get或者set首字母变小写 eg: setUsername-去掉set->Username-首字母变小写->username

    一般情况下,我们通过IDEA直接生成的set/get 习惯把字段和属性搞成一样而言

小结

  1. JavaBean用来封装数据用的
  2. 字段和属性
    • 字段: 全局/成员变量 eg: private String username
    • 属性: 去掉get或者set首字母变小写 eg: setUsername-去掉set->Username-首字母变小写->username

使用DBUtils完成增删改

目标

  • 掌握使用DBUtils完成增删改

步骤

  1. 拷贝jar包
  2. 创建QueryRunner()对象,传入dataSource
  3. 调用update()方法

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class UpdateTest {
// 插入记录
@Test
public void insert() throws Exception {
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

// 2.调用update方法
String sql = "insert into user values(null,?,?,?)";
int rows = qr.update(sql, "tianqi", "123456", "田七");
System.out.println("受影响的行数:" + rows);

}

// 删除记录 删除id为4的记录
@Test
public void delete() throws Exception {
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

// 2.调用update方法
String sql = "delete from user where id = ?";
int rows = qr.update(sql, 4);
System.out.println("受影响的行数:" + rows);
}

// 修改记录 修改id为5的记录
@Test
public void update() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

// 2.调用update方法
String sql = "update user set password = ? where id = ?";
int rows = qr.update(sql,"abcd",3);
System.out.println("受影响的行数:" + rows);
}
}

小结

  1. 创建QueryRuner()对象, 传入DataSource
  2. 调用update(String sql, Object…params)

使用DBUtils完成查询

掌握使用DBUtils完成查询

步骤

  1. 拷贝jar包
  2. 创建QueryRunner()对象 传入DataSource
  3. 调用query(sql, resultSetHandler,params)方法

ResultSetHandler结果集处理类介绍

image-20230921123715221

1
2
3
4
5
6
7
8
9
10
11
12
13
public queryRunner(DataSource ds);
query(String sql, ResultSetHandler<T> rsh, Object... params) 执行查询语句
参数ResultSetHandler是一个接口,表示结果集处理者(对查询结果的封装):
ResultSetHandler接口的实现类:
ArrayHandler:适合查询结果是一条记录的,会把这条记录的数据封装到一个Object数组中
ArrayListHandler:适合查询结果是多条记录的,会把每条记录的数据封装到一个Object数组中,然后把这些数组添加到List集合中
BeanHandler:适合查询结果是一条记录的,会把这条记录的数据封装到一个javaBean对象中
BeanListHandler:适合查询结果是多条记录的,会把每条记录的数据封装到一个javaBean对象中,然后把这些javaBean对象添加到List集合中
ColumnListHandler:适合查询结果是单列多行的,会把该列的所有数据存储到List集合中
KeyedHandle:适合查询结果是多条记录的,会把每条记录的数据封装到一个Map集合中,然后把这些Map集合添加到另一个Map集合中
MapHandler:适合查询结果是一条记录的,会把这条记录的数据封装到一个Map集合中
MapListHandler:适合查询结果是多条记录的,会把每条记录的数据封装到一个Map集合中,然后把这些Map集合添加到List集合中
ScalarHandler:适合查询结果是单个值的,会把这个值封装成一个对象

查询一条数据封装到JavaBean对象中(使用BeanHandler)

1
2
3
4
5
6
7
8
9
10
11
@Test
public void select1() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

// 2.调用query(sql,resultSetHandler,args)方法
String sql = "select * from user where id = ?";
User user = qr.query(sql, new BeanHandler<User>(User.class), 3);
System.out.println(user);

}

查询多条数据封装到List<JavaBean>中(使用BeanListHandler)

1
2
3
4
5
6
7
8
9
10
11
@Test
public void select2() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
// 2.调用query方法
String sql = "select * from user";
List<User> list = qr.query(sql, new BeanListHandler<User>(User.class));
for (User user : list) {
System.out.println(user);
}
}

查询一条数据,封装到Map对象中(使用MapHandler)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 查询一条数据,封装到Map对象中(使用MapHandler)
// 列名作为key,列中的值作为value
@Test
public void select3() throws Exception {
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
// 2.调用query方法
String sql = "select * from user where id = ?";
Map<String, Object> map = qr.query(sql, new MapHandler(), 3);
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
}

查询多条数据,封装到List<Map>对象中(使用MapListHandler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 查询多条数据,封装到List<Map>对象中(使用MapListHandler)
@Test
public void select4() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
// 2.调用query方法
String sql = "select * from user";
List<Map<String, Object>> list = qr.query(sql, new MapListHandler());
for (Map<String, Object> map : list) {
// map--->每条记录
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
System.out.println("=================================================");
}
}

查询单个数据(使用ScalarHandler())

1
2
3
4
5
6
7
8
9
10
// 查询单个数据(使用ScalarHandler())
@Test
public void select5() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
// 2.调用query方法
String sql = "select count(*) from user";
Long count = (Long) qr.query(sql, new ScalarHandler());
System.out.println("记录数:"+count);
}

查询单列多个值(使用ArrayListHandler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 查询单列多个值(ArrayListHandler)
// 单列多值: 每列的数据封装成一个数组,再把所有的数组存储到List集合中
// 多条记录: 每条记录的数据封装成一个数组,再把所有的数组存储到List集合中
@Test
public void select6() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
// 2.调用query方法
String sql = "select username from user";
List<Object[]> list = qr.query(sql, new ArrayListHandler());
for (Object[] arr : list) {
System.out.println(Arrays.toString(arr));
}
}

查询一条记录(使用ArrayHandler)

1
2
3
4
5
6
7
8
9
10
11
// 查询一条记录(ArrayHandler)
// 一条记录封装成一个数组,数组中的元素就是每列的值
@Test
public void select7() throws Exception{
// 1.创建QueryRunner对象,传入连接池
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
// 2.调用query方法
String sql = "select * from user where id = ?";
Object[] arr = qr.query(sql, new ArrayHandler(), 3);
System.out.println(Arrays.toString(arr));
}

小结

  1. 步骤

    • 创建QueryRunner() 对象传入DataSource
    • 调用query(sql,ResultSetHandler, params…)
  2. ResultSetHandler

    • BeanHandler() 查询一条记录封装到JavaBean对象
    • BeanListHandler() 查询多条记录封装到List<JavaBean> list
    • ScalarHandler() 封装单个记录的 eg:统计数量
    • ColumnListHandler() 封装单列多行记录
    • MapHandler() 查询一条记录封装到Map对象
    • MapListHandler() 查询多条记录封装到List<Map> list
    • ArrayHandler() 查询一条记录封装到数组中

    原理: 使用了反射+内省

  3. 注意实现

    封装到JavaBean条件, 查询出来的数据的列名必须和JavaBean属性一致

自定义DBUtils

元数据

能够说出什么是数据库元数据

​ 我们要自定义DBUtils, 就需要知道列名, 参数个数等, 这些可以通过数据库的元数据库进行获得.元数据在建立框架和架构方面是特别重要的知识,我们可以使用数据库的元数据来创建自定义JDBC工具包, 模仿DBUtils.

什么是元数据

​ 元数据(MetaData),即定义数据的数据。打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。

歌曲:凉凉

作词:刘畅

演唱: 杨宗纬 张碧晨

时长: 3分28秒

简单来说: 数据库的元数据就是 数据库、表、列的定义信息。

① 由PreparedStatement对象的getParameterMetaData ()方法获取的是ParameterMetaData对象。

② 由ResultSet对象的getMetaData()方法获取的是ResultSetMetaData对象。

ParameterMetaData

概述

​ ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,ParameterMetaData可用于获取有关PreparedStatement对象和其预编译sql语句 中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型

image-20230921123730939

​ 获得ParameterMetaData:

 `ParameterMetaData parameterMetaData =  preparedStatement.getParameterMetaData ()`
ParameterMetaData相关的API
  • int getParameterCount(); 获得参数个数
  • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
实例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test1_参数元数据 {
public static void main(String[] args) throws Exception{
/*
ParameterMetaData类:
概述: 是一个参数元数据类,可以用来获取参数的元数据
使用:
1.获取参数元数据类对象
PreparedStatement对象调用getParameterMetaData()方法
2.获取参数的元数据
ParameterMetaData相关的API
- int getParameterCount(); 获得参数个数
- int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)

*/
// 1.获取连接
Connection connection = C3P0Utils.getConnection();

// 2.预编译sql语句
String sql = "select * from user where username = ? and password = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.通过PreparedStatement对象获取参数元数据对象
ParameterMetaData pmd = ps.getParameterMetaData();// 参数元数据对象

// 4.获取参数个数元数据 参数个数就是参数的元数据
int count = pmd.getParameterCount();
System.out.println("sql语句的参数个数:"+count);// 2

// 5.获取参数类型元数据 参数类型也是参数的元数据
//int parameterType = pmd.getParameterType(1);
//System.out.println(parameterType);

//String typeName = pmd.getParameterTypeName(1);
//System.out.println(typeName);

}
}

ResultSetMetaData

概述

​ ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来,ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。

image-20230921123738913

​ 获得ResultSetMetaData:

 `ResultSetMetaData resultSetMetaData =  resultSet.getMetaData()`
resultSetMetaData 相关的API
  • getColumnCount(); 获取结果集中列项目的个数
  • getColumnName(int column); 获得数据指定列的列名
  • getColumnTypeName();获取指定列的SQL类型
  • getColumnClassName();获取指定列SQL类型对应于Java的类型
实例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
     public class Test2_结果集元数据 {
public static void main(String[] args) throws Exception {
/*
ResultSetMetaData类:
概述:是一个结果集元数据类,可以用来获取结果集的元数据
使用:
1.获取结果集元数据类的对象
ResultSet的对象调用getMetaData()方法
2.获取结果集的元数据
ResultSetMetaData 相关的API
- getColumnCount(); 获取结果集中列项目的个数
- getColumnName(int column); 获得数据指定列的列名
- getColumnTypeName();获取指定列的SQL类型
- getColumnClassName();获取指定列SQL类型对应于Java的类型

*/
// 1.获取连接
Connection connection = C3P0Utils.getConnection();

// 2.预编译sql语句
String sql = "select * from user where username = ? and password = ?";
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数
ps.setString(1, "zs");
ps.setString(2, "123456");

// 4.执行sql语句
ResultSet resultSet = ps.executeQuery();

// 5.获取结果集的元数据对象
ResultSetMetaData rsmd = resultSet.getMetaData();

// 1.获取列的数量
int columnCount = rsmd.getColumnCount();
System.out.println("列的个数:" + columnCount);//4

// 2.获取列的名字
for (int i = 1; i <= columnCount; i++) {
System.out.println(rsmd.getColumnName(i));
}
System.out.println("==================================");
// 3.获取列的MySQL类型
for (int i = 1; i <= columnCount; i++) {
System.out.println(rsmd.getColumnTypeName(i));
}
System.out.println("==================================");


// 4.获取列在Mysql中的类型对应于java中的类型
for (int i = 1; i <= columnCount; i++) {
System.out.println(rsmd.getColumnClassName(i));
}
}
}

小结

  1. 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型…
  2. mysql元数据:
    • ==ParameterMetaData==
    • ResultSetMetaData

自定义DBUtils增删改

模仿DBUtils, 完成增删改的功能

  1. 创建MyQueryRunner类, 定义dataSource, 提供有参构造方法
  2. 定义int update(String sql, Object…params)方法
1
2
3
4
5
6
7
//0.非空判断
//1.从dataSource里面获得connection
//2.根据sql语句创建预编译sql语句对象
//3.获得参数元数据对象, 获得参数的个数
//4.遍历, 从params取出值, 依次给参数? 赋值
//5.执行
//6.释放资源

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MyQueryRunner {

private DataSource dataSource;

public MyQueryRunner(DataSource dataSource) {
this.dataSource = dataSource;
}

public MyQueryRunner() {
}

/**
* 增删改的方法
* @param sql
* @param args
* @return rows
*/
public int update(String sql,Object... args) throws Exception{
// 非空判断
// 连接池为空
if (dataSource == null){
throw new RuntimeException("连接池不能为null");
}

if (sql == null){
throw new RuntimeException("sql语句不能为空");
}

// 1.获得连接
Connection connection = dataSource.getConnection();

// 2.预编译sql语句
PreparedStatement ps = connection.prepareStatement(sql);

// 3.设置参数
// 3.1 获得参数元数据类的对象
ParameterMetaData pmd = ps.getParameterMetaData();

// 3.2 根据参数元数据类的对象,获取参数个数(元数据)
int count = pmd.getParameterCount();

// 3.3 给参数赋值
for (int i = 0; i < count; i++) {
ps.setObject(i+1,args[i]);
}

// 4.执行sql语句
int rows = ps.executeUpdate();

// 5.释放资源
C3P0Utils.release(null,ps,connection);

// 6.返回影响的行数
return rows;
}
}

小结

  1. 先模拟DBUtils写出架子
  2. update()
    • 封装了PrepareStatement操作
    • 用到了参数元数据

Servlet

Servlet入门(最重要的东西)

实操-使用IDEA创建web工程配置tomcat

能够在IDEA配置tomcat 并且创建web工程

配置tomcat

我们要将idea和tomcat集成到一起,可以通过idea就控制tomcat的启动和关闭:

image-20230921123753589

image-20230921123759636

image-20230921123805773

image-20230921123810783

创建JavaWeb工程

web工程创建

image-20230921123816386

image-20230921123821036

image-20230921123826418

image-20230921123831574

发布
  • 情况一: 已经关联成功的情况(因为我创建项目的时候, 已经选择了tomcat)

image-20230921123837614

image-20230921123841762

  • 情况二: 如果之前没有选择tomcat, 现在就需要自己关联发布

image-20230921123847441

image-20230921123851836

先看笔记 配置发布一下, 如果看笔记配置不出来, 看视频 ( 自己操作<=30分钟), 问同桌, 老师

Servlet概述

什么是Servlet

​ Servlet **运行在服务端(tomcat)**的Java小程序(对象),是sun公司提供一套规范. 就是动态资源

Servlet作用

​ 用来接收、处理客户端请求、响应给浏览器的动态资源。

​ 但servlet的实质就是java代码,通过java的API动态的向客户端输出内容

servlet与普通的java程序的区别

  1. 必须实现servlet接口
  2. 必须在servlet容器(服务器 tomcat)中运行
  3. servlet程序可以接收用户请求参数以及向浏览器输出数据

小结

  1. Servlet是运行在服务器端java小程序, 动态资源
  2. Servlet的作用: 接收请求,做出响应

案例-Servlet入门案例

在IDEA编写Servlet,发布到Tomcat. 在浏览器输入路径请求, 控制台打印Hello…

配置文件方式实现

  • 在com.itheima.web包下创建一个类实现Servlet接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.nbchen.web;


import javax.servlet.*;
import java.io.IOException;
public class ServletDemo01 implements Servlet {

@Override
//服务方法: 处理请求的
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("hello world...");
}


@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

@Override
public ServletConfig getServletConfig() {
return null;
}



@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

  • web.xml配置(该文件在web/WEB-INF 文件夹下):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

<!--配置Servlet-->
<servlet>
<!--servlet-name: 名字 随便取; 一般就是类名-->
<servlet-name>ServletDemo01</servlet-name>
<!--servlet-class:Servlet这个类的全限定名-->
<servlet-class>com.itheima.web.ServletDemo01</servlet-class>
</servlet>
<servlet-mapping>
<!--servlet-name: 必须和servlet标签里面的servlet-name一样-->
<servlet-name>ServletDemo01</servlet-name>
<!--url-pattern: 配置访问的路径-->
<url-pattern>/demo01</url-pattern>
</servlet-mapping>

</web-app>

注解方式实现

  • 在com.itheima.web包下创建一个类实现Servlet接口, 添加注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.nbchen.web;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/demo02")
public class ServletDemo02 implements Servlet{

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello world...");
}


@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

@Override
public ServletConfig getServletConfig() {
return null;
}



@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

浏览器地址栏输入:http://localhost:8080/day27_servlet/demo02

小结

如果出现实现Servlet报错
  • 检查当前的工程是否依赖了Tomcat

image-20230921124007300

  • 如果没有依赖, 依赖tomcat

image-20230921124014571

image-20230921124023057

配置文件方式与注解方式比较

​ 注解方式简化的javaweb代码开发,可以省略web.xml配置文件.

​ 但是配置文件方式必须掌握的(后面在框架或者大项目里面会使用到的)

步骤回顾
  • xml方式
    • 创建一个类实现Servlet接口
    • 在web.xml配置servlet
  • 注解方式
    • 创建一个类实现Servlet接口
    • 在类上面添加@WebServlet(“访问的路径”)

入门案例原理和路径

Servlet执行原理

image-20230921124033682

通过上述流程图我们重点需要掌握如下几个点:

  • Servlet对象是由服务器创建(反射)
  • request与response对象也是由tomcat服务器创建
  • service()方法也是服务器调用的

Servlet路径的配置url-pattern

url-pattern配置方式共有三种:

  • 完全路径匹配: 以 / 开始.     注: 访问的路径不能多一个字母也不能少一个
1
例如: 配置了/demo01  请求的时候必须是: /demo01  
  • 目录匹配”以 / 开始需要以 * 结束. 注: Servlet里面用的 不多, 但是过滤器里面通常就使用目录匹配
1
例如:  配置/* 访问/a, /aa, /aaa; 配置 /aa/*  访问 /aa/b , /aa/cc
  • 扩展名匹配不能以 / 开始, 以 * 开始的 .
1
例如:  *.action;  访问: aa.action, bb.action, c.action;   错误写法: /*.do, 不可以写*.jsp,*.html

注意的地方:

  • 一个路径只能对应一个servlet, 但是一个servlet可以有多个路径

  • tomcat获得匹配路径时,优先级顺序:完全路径匹配> 目录匹配 > 扩展名匹配

小结

  1. 讲Servlet原理目的让大家对Servlet执行有一个深入认识, 只需要能懂就可以. 具体操作是服务器(创建servlet,执行service), 你【重点】要做的就是写出Servlet
  2. 路径有多种方式, 一般用完全路径匹配

Servlet进阶

Servlet的生命周期

生命周期概述

​ 一个对象从创建到销毁的过程

Servlet生命周期方法

servlet从创建到销毁的过程

  • 出生:(初始化)用户第一次访问时执行。 init(ServletConfig config)

  • 活着:(服务)应用活着。每次访问都会执行。 service(ServletRequest req, ServletResponse res)

  • 死亡:(销毁)应用卸载。 destroy()

Servlet生命周期描述

  1. 常规【重点】

    ​ 默认情况下, 来了第一次请求, 会调用init()方法进行初始化【调用一次】

    ​ 任何一次请求 都会调用service()方法处理这个请求

    ​ 服务器正常关闭或者项目从服务器移除, 调用destory()方法进行销毁【调用一次】

  2. 扩展

    ​ servlet是单例多线程的, 尽量不要在servlet里面使用值可变的全局(成员)变量,可能会导致线程不安全

    ​ 单例: 只有一个对象(init()调用一次, 创建一次)

    ​ 多线程: 服务器会针对每次请求, 开启一个线程调用service()方法处理这个请求

在goGet

ServletConfig【了解】

​ Servlet的配置对象, 可以使用用ServletConfig来获得Servlet的初始化参数,在SpringMVC里面会遇到

  • 先在配置文件里面配置初始化参数

image-20230921124043715

  • 可以通过akey获得aaa

image-20230921124049408

启动项

​ Servlet默认情况下什么是创建? 默认情况下是第一次请求的时候.

​ 如果我想让Servlet提前创建(服务器启动的时候), 这个时候就可以使用启动项 在SpringMVC里面会遇到

image-20230921124053499

小结

  1. Servlet生命周期方法

    • init() 初始化
    • service() 服务
    • distory() 销毁
  2. Servlet生命周期描述【面试题】

    默认情况下, 第一次请求的时候, 调用init()方法进行初始化【调用一次】

    任何一次请求, 都会调用service()方法进行处理这个请求

    服务器正常关闭/项目从服务器移除, 调用destory()方法进行销毁【调用一次】

    Servlet是单例多线程的

Servlet体系结构(重点)

掌握Servlet的继承关系,能够使用IDEA直接创建Servlet

image-20230921124100564

Servlet接口

​ 前面我们已经学会创建一个类实现sevlet接口的方式开发Servlet程序,实现Servlet接口的时候,我们必须实现接口的所有方法。但是,在servlet中,真正执行程序逻辑的是service,对于servlet的初始化和销毁,由服务器调用执行,开发者本身不需要关心。因此,有没有一种更加简洁的方式来开发servlet程序呢?

我们先来查阅API回顾Servlet接口:
image-20230921124107583

​ 由上图可知在servlet接口规范下,官方推荐使用继承的方式,继承GenericServlet 或者HttpServlet来实现接口,那么我们接下来再去查看一下这两个类的API:

GenericServlet 类

image-20230921124113287

​ 阅读上图API可知,GenericServlet 是一个类,它简化了servlet的开发,已经提供好了一些servlet接口所需的方法,我们开发者只需要重写service方法即可

我们来使用GenericServlet 创建servlet:

  1. 创建一个类
  2. 继承GenericServlet
  3. 重写service方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.nbchen.web;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet(name = "GenericDemoServlet",urlPatterns = "/generic")
public class GenericDemoServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("GenericDemoServlet执行.......");
}
}

​ 虽然,GenericServlet已经简化了servlet开发,但是我们平时开发程序需要按照一种互联网传输数据的协议来开发程序——http协议,因此,sun公司又专门提供了HttpServlet,来适配这种协议下的开发。

HttpServlet

image-20230921124119480

阅读上图的API可知,继承HttpServlet,我们需要重写doGet、doPost等方法中一个即可,根据Http不同的请求,我们需要实现相应的方法。

我们来使用HttpServlet创建servlet:

  1. 创建一个类
  2. 继承HttpServlet
  3. 重写doGet方法和doPost方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 包名:com.nbchen.servlet
* Servlet的常用的编写方法:
* 1. 写一个类继承HttpServlet,重写doGet和doPost方法
* 1.1 doGet()方法,是处理来自客户端的get请求
* 1.2 doPost()方法,是处理来自客户端的post请求
*
* 通常情况下:服务器端针对同一个请求(不同的请求方式)不会做不同的处理,所以我们会选择在doGet中调用doPost
* 2. 配置servlet的映射路径
*/
@WebServlet("/demo02")
public class ServletDemo02 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("编写很多代码处理这次post请求");
}
}

​ 通过以上两个API阅读,同学们注意一个细节HttpServlet是GenericServlet的子类,它增强了GenericServlet一些功能,因此,在后期使用的时候,我们都是选择继承HttpServlet来开发servlet程序。

小结

  1. 继承关系

image-20230921124126933

  1. 我们可以直接创建一个类继承HttpServlet, 直接在IDEA里面new Servlet

image-20230921124132332

扩展(Servlet体系源码)

  1. 看HttpServlet的service()方法

image-20191208151858364

补充案例:登录

流程分析

image-20230921124149352

代码

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<!--
怎么在页面上指定提交给LoginServlet
项目的虚拟路径/servlet的映射路径
-->
<form action="/LoginDemo/login" method="post">
用户名<input type="text" name="username"><br>
密码<input type="text" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>

LoginServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.nbchen.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 使用request获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");

//2. 校验用户名和密码
if ("jay".equals(username) && "123456".equals(password)) {
//登录成功
response.getWriter().write("login success");
}else {
//登录失败
response.getWriter().write("login failed");
}
}
}

ServletContext

ServletContext概述

servletContext概述

​ ServletContext: 是一个全局对象, 上下文对象.

​ 服务器为每一个应用(项目)都创建了一个ServletContext对象。 ServletContext属于整个应用(项目)的,不局限于某个Servlet。并且整个项目有且只会有一个ServletContext对象

ServletContext作用

​ 作为域对象存取数据,让Servlet共享(掌握)

​ 获得文件MIME类型(文件下载)(了解)

​ 获得全局初始化参数(了解)

​ 获取web资源路径 ,可以将Web资源转换成字节输入流(掌握)

ServletContext的功能

作为域对象存取值【重点】

image-20191208154333086

  1. API

    • getAttribute(String name) ;向ServletContext对象的map取数据
    • setAttribute(String name, Object object) ;从ServletContext对象的map中添加数据
    • removeAttribute(String name) ;根据name去移除数据
  2. 代码

    • ServletDemo01
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.nbchen.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
- 包名:${PACKAGE_NAME}
*
- 目标: 在ServletDemo02中获取servletDemo01中的一个变量的值
- 1. 获取ServletContext对象:
- getServletContext()
*/
@WebServlet("/demo01")
public class ServletDemo01 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = "周杰棍";
//1. 获取ServletContext对象
ServletContext servletContext = getServletContext();
//2. 往容器ServletContext中存值
servletContext.setAttribute("name",name);
}
}
  • ServletDemo02
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.nbchen.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**

- 包名:${PACKAGE_NAME}
*
*/
@WebServlet("/demo02")
public class ServletDemo02 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取那个容器ServletContext
ServletContext servletContext = getServletContext();
//2. 从容器ServletContext中获取数据
String name = (String) servletContext.getAttribute("name");

System.out.println("在ServletDemo02中获取的name是:" + name);
}
}

获得文件mime-type(了解)

  1. getMimeType(String file)
  2. 代码
1
2
3
4
5
6
7
8
9
10
11
12
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//根据文件名获得文件的mini类型
//1.获得ServletContext
//2.调用getMimeType()方法
String file01 = "a.mp3";
String file02 = "b.png";
String mimeType01 = getServletContext().getMimeType(file01);
String mimeType02 = getServletContext().getMimeType(file02);

response.getWriter().print("ServletDemo05..."+mimeType01+":"+mimeType02);

}

获得全局初始化参数(了解)

  • String getInitParameter(String name) ; //根据配置文件中的key得到value;

在web.xml配置

image-20230921124205238

通过ServletContext来获得

image-20230921124211764

获取web资源路径

  1. API
    • String  getRealPath(String path);根据资源名称得到资源的绝对路径.
    • getResourceAsStream(String path) ;返回制定路径文件的流

注意: filepath:直接从项目的根目录开始写

  1. 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 包名:${PACKAGE_NAME}

* 使用ServletContext获取web里面的资源文件的真实路径
* 目标: 使用字节输入流,读取mm.jpg这张图片
* 方法1: FileInputStream
* 方法2: ClassLoader
* 方法3: 使用ServletContext
*
* 在web项目中,将文件转换成流,有两种方式
* 1. 如果文件在resources里面,使用类加载器
* 2. 如果文件在web里面,使用ServletContext
*/
@WebServlet("/demo04")
public class ServletDemo04 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//方法1: 使用FileInputStream,此时使用磁盘路径
//InputStream is = new FileInputStream("C:/JavaEE_Relation/JavaEE98/itheima98_web/day24_servletcontext_03/web/mm.jpg");
//方法2: 使用ClassLoader将文件转换成流
//InputStream is = ServletDemo04.class.getClassLoader().getResourceAsStream("../../mm.jpg");

//方法3: 使用ServletContext可以获取web里面的资源的真实路径
ServletContext servletContext = getServletContext();
//String realPath = servletContext.getRealPath("mm.jpg");

InputStream is = servletContext.getResourceAsStream("mm.jpg");
System.out.println(is);
}
}

小结

  1. 作为域对象存取数据【共享】

    • setAttribute(String name,Object value) 存
    • getAttribute(String name) 取
    • removeAttribute(String name) 移除
  2. 获得文件的Mime类型

    • getMineType(String fileName);
  3. 获得全局初始化参数

    • 在web.xml配置
    • getInitParameter(String name);
  4. 获得web资源路径【已经在web目录了】

    • getRealPath(String file); 获得文件的绝对路径
    • getReSourceAsStream(String file); 获得文件流

案例-统计网站被访问的总次数

需求

img/

  • 在页面中显示您是第x位访问的用户.

思路分析

image-20230921124251887

代码实现

  • CountServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.nbchen.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 包名:${PACKAGE_NAME}
*

* 每次接收到请求,则往容器中的计数器上面+1
*/
@WebServlet("/count")
public class CountServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取容器中计数器原本的次数
ServletContext servletContext = getServletContext();
Object count = servletContext.getAttribute("count");

//判断ServletContext中是否有计数器
if (count == null) {
//说明当前是第一次访问
//就往ServletContext中添加一个计数器
servletContext.setAttribute("count",1);
}else {
//说明我不是第一次访问,那么就要对原来的计数器+1,再存进去
int number = ((int) count) + 1;
servletContext.setAttribute("count",number);
}
//2. 向客户端响应WelCome
response.getWriter().write("WelCome!!!");
}
}
  • ShowServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.nbchen.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 包名:${PACKAGE_NAME}
*
*/
@WebServlet("/show")
public class ShowServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从容器中获取计数器的值
ServletContext servletContext = getServletContext();

int count = (int) servletContext.getAttribute("count");

//将count响应给客户端
response.getWriter().print("access count is:"+count);
}
}

request

request概述

知道什么是request以及作用

什么是request

    在Servlet API中,定义了一个HttpServletRequest接口,它继承自ServletRequest接口,专门用来封装HTTP请求消息。由于HTTP请求消息分为请求行、请求头和请求体三部分,因此,在HttpServletRequest接口中定义了获取请求行、请求头和请求消息体的相关方法.

image-20230921124308406

​ Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。

request作用

  • 操作请求三部分(行,头,体)
  • 请求转发
  • 作为域对象存数据

小结

  1. request代表请求对象. 原型是HttpServletRequest, 服务器创建好的, 以形参的方式存在doGet()/doPost()方法里面
  2. Request作用
    • 操作请求的三部分(行,头,体)
    • 转发
    • 作为域对象存取数据

操作请求行和请求头

获取客户机信息(操作请求行)

​ 请求方式 请求路径(URI) 协议版本

​ GET /day17Request/WEB01/register.htm?username=zs&password=123456 HTTP/1.1

  • getMethod();获取请求方式
  • getRemoteAddr() ;获取客户机的IP地址(知道是谁请求的)
  • getContextPath(); 获得当前应用工程名(部署的路径);
  • getRequestURI();获得请求地址,不带主机名
  • getRequestURL();获得请求地址,带主机名
  • getServerPort();获得服务端的端口
  • getQueryString();获的请求参数(get请求的,URL的?后面的. eg:username=zs&password=123456)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 包名:${PACKAGE_NAME}
* 获取请求行的信息
*/
@WebServlet("/demo01")
public class ServletDemo01 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//使用request对象获取请求行的信息:
//1. 获取请求的方式(可能会用)
String method = request.getMethod();
//System.out.println("请求方式为:" + method);

//2. 获取客户端的ip地址
String remoteAddr = request.getRemoteAddr();
//System.out.println("客户端的ip地址是:" + remoteAddr);

//3. 获取项目部署的路径(以后可能用到)
String contextPath = request.getContextPath();
//System.out.println("项目部署路径是:" + contextPath);

//4. 获取请求的url: 统一资源定位符 http://localhost:8080/requestDemo/demo01
String url = request.getRequestURL().toString();
//System.out.println("此次请求的url是:" + url);

//5. 获取请求的uri: 统一资源标识符,在url的基础上省略了服务器路径"http://loaclhost:8080"
String uri = request.getRequestURI();
System.out.println(uri);
}
}

获得请求头信息(操作请求头)(了解)

请求头: 浏览器告诉服务器自己的属性,配置的, 以key value存在, 可能一个key对应多个value

image-20230921124316159

getHeader(String name);

  • User-Agent: 浏览器信息
  • Referer:来自哪个网站(防盗链)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
*
* 使用request获取请求头的信息
*/
@WebServlet("/demo02")
public class ServletDemo02 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//根据请求头的name获取value
//目标:获取name为user-agent的请求头的信息
String header = request.getHeader("user-agent");
System.out.println("获取的请求头agent为:" + header);
}
}

小结

  1. 操作请求行(获得客户机信息)
    • getMethod(); 获得请求方式
    • getRemoteAddr(); 获得客户机的IP地址
    • getContextPath(); 获得项目的部署的路径
    • getRequestURI(); 获得URI(不带http,主机,端口)
    • getRequestURL(); 获得URL(带http,主机,端口)
  2. 操作请求头
    • getHeader(String name);
      • User-Agent: 浏览器信息
      • Referer:来自哪个网站(防盗链)

获得请求参数【重点】

获得请求参数

法名 描述
String getParameter(String name) 获得指定参数名对应的值。如果没有则返回null,如果有多个获得第一个。 例如:username=jack
String[] getParameterValues(String name) 获得指定参数名对应的所有的值。此方法专业为复选框提供的。 例如:hobby=抽烟&hobby=喝酒&hobby=敲代码
Map<String,String[]> getParameterMap() 获得所有的请求参数。key为参数名,value为key对应的所有的值。

请求参数乱码处理

​ 我们在输入一些中文数据提交给服务器的时候,服务器解析显示出来的一堆无意义的字符,就是乱码。
那么这个乱码是如何出现的呢?如下图所示:

image-20191209100446323

  1. get方式, 我们现在使用的tomcat>=8.0了, 乱码tomcat已经处理好了
  2. post方式, 就需要自己处理
1
void setCharacterEncoding(String env); //设置请求体的编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 包名:${PACKAGE_NAME}
*
* 使用request获取请求参数
*
* 请求参数的乱码问题:
* 在tomcat8之前,无论是get请求还是post请求,请求参数都会发生乱码
* 在tomcat8之后(包含8),只有post请求才会发生中文乱码
*
* 怎么解决乱码: 只要在获取请求参数之前,调用request.setCharacterEncoding(UTF-8);
*/
@WebServlet("/demo03")
public class ServletDemo03 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
//1. 根据参数名,获取一个参数值
String username = request.getParameter("username");
//System.out.println("获取的请求参数username=" + username);

//2. 根据参数名,获取多个参数值:比如说注册时候的兴趣爱好的复选框
String[] hobbies = request.getParameterValues("hobby");
/*for (String hobby : hobbies) {
System.out.println(hobby);
}*/

//3. 获取所有的请求参数
//getParameterMap()获取所有请求参数,请求参数的参数名就是map的key,请求参数的参数值就是map的value
Map<String, String[]> parameterMap = request.getParameterMap();
//遍历出每一个请求参数
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for (Map.Entry<String, String[]> entry : entries) {
String parameterName = entry.getKey();
String[] values = entry.getValue();
for (String value : values) {
System.out.println("参数名:" + parameterName + ",参数值:" + value);
}
}
}
}

使用BeanUtils封装

​ 现在我们已经可以使用request对象来获取请求参数,但是,如果参数过多,我们就需要将数据封装到对象。

​ 以前封装数据的时候,实体类有多少个字段,我们就需要手动编码调用多少次setXXX方法,因此,我们需要BeanUtils来解决这个问题。

​ BeanUtils是Apache Commons组件的成员之一,主要用于简化JavaBean封装数据的操作。

使用步骤:

  1. 导入jar
  2. 使用BeanUtils.populate(user,map)
  • 表单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<form action="/requestDemo/demo04" method="post">
用户名<input type="text" name="username"><br>
密码<input type="text" name="password"><br>
性别<input type="radio" name="gender" value="male">
<input type="radio" name="gender" value="female">
<br>
兴趣爱好
<input type="checkbox" name="hobby" value="basketball">篮球
<input type="checkbox" name="hobby" value="football">足球
<input type="checkbox" name="hobby" value="ppball">乒乓球
<input type="checkbox" name="hobby" value="yumaoball">羽毛球
<br>
<input type="submit" value="注册">
</form>
</body>
</html>
  • User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 包名:com.nbchen.pojo
*/
public class User implements Serializable {
private String username;
private String password;
private String gender;
private String[] hobby;

public User() {
}

public User(String username, String password, String gender, String[] hobby) {
this.username = username;
this.password = password;
this.gender = gender;
this.hobby = hobby;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", hobby=" + Arrays.toString(hobby) +
'}';
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public String[] getHobby() {
return hobby;
}

public void setHobby(String[] hobby) {
this.hobby = hobby;
}
}
  • ServletDemo04
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 包名:${PACKAGE_NAME}
*
* 我们使用request.getParameterMap()方法获取所有请求参数,是封装在map对象中
*
* 我的需求是将获取到的所有请求参数封装到POJO对象中
* 1. 创建一个POJO类,类中的属性名要和请求参数名对应
* 2. 使用BeanUtils框架,将map中的数据封装到POJO对象中
*/
@WebServlet("/demo04")
public class ServletDemo04 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
//1. 获取所有的请求参数
Map<String, String[]> parameterMap = request.getParameterMap();
//2. 将请求参数封装到User对象中
User user = new User();
//3. 使用BeanUtils框架自动将map中的数据封装到对象中
try {
BeanUtils.populate(user,parameterMap);

System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}

小结

  1. 获取请求参数的方法

    1. getParameter(name),根据参数名获取一个参数值
    2. getParameterValues(name),根据参数名获取多个参数值
    3. getParameterMap(),获取所有的请求参数封装到map中
  2. 使用BeanUtils封装

    • 如果请求参数有多个需要封装到JavaBean里面, 建议先获得Map, 再使用BeanUtils封装到JavaBean对象

    注意: JavaBean属性需要和Map的key一致 说白了也就是JavaBean属性需要和表单的name一致

  3. 解决请求参数的中文乱码问题

    1. Tomcat8之后,使用get方式提交的请求参数不会发生中文乱码
    2. 解决post请求中的中文参数乱码问题:在获取请求参数之前添加一句代码:request.setCharacterEncoding(“UTF-8”)

请求转发【重点】

掌握请求转发

1
request.getRequestDispatcher(url).forward(request, response);//转发

小结

  • 请求转发的作用:跳转页面,比如说添加完数据之后跳转到数据的展示页面,删除完数据之后跳转到展示页面
  • 请求转发的代码
1
request.getRequestDispatcher("转发的路径").forward(request,response); 
  • 请求转发的特征
    • 跳转操作是由服务器执行的,所以客户端地址栏不会发生变化
    • 跳转操作不会发起新的请求
    • 可以跳转到WEB-INF中的资源,但是不能跳转到其它项目的资源

作为域对象存取值

目标

  • 掌握requet作为域对象存取值

讲解

​ ServletContext: 范围 整个应用(无论多少次请求,只要是这个应用里面的都是可以==共享==的)

​ request范围: 一次请求有效

​ 域对象是一个容器,这种容器主要用于Servlet与Servlet/JSP之间的数据传输使用的。

  • Object getAttribute(String name) ;
  • void setAttribute(String name,Object object) ;
  • void removeAttribute(String name) ;

小结

  1. 作为域对象存取数据

    • Object getAttribute(String name) ; 取
    • void setAttribute(String name,Object object) ; 存
    • void removeAttribute(String name) ; 移除
  2. 范围: 一次请求有效(转发可以使用)

    image-20191209110018952

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 包名:${PACKAGE_NAME}
*
* request作为域对象在不同的Servlet之间进行数据的共享,它只能在同一次请求中进行数据共享
*
* request域对象只有和请求转发一起使用才有意义
*/
@WebServlet("/demo06")
public class ServletDemo06 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 目标: 在ServletDemo07中获取ServletDemo06中的变量username的值
// 要求只能是由ServletDemo06跳转到ServletDemo07的时候才能获取
String username = "周杰棍";

//将username存储到request域对象中
request.setAttribute("name",username);

//请求转发跳转到ServletDemo07
request.getRequestDispatcher("/demo07").forward(request, response);
}
}

ServletDemo07的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 包名:${PACKAGE_NAME}
*
*/
@WebServlet("/demo07")
public class ServletDemo07 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//使用request域对象中取出name的值
System.out.println("在ServletDemo07中获取username的值:"+request.getAttribute("name"));
}
}

Request部分的小结

  1. 获取请求行的信息
    1. getMethod()获取请求方式
    2. getRequestURI()获取请求的uri地址
    3. getRequestURL()获取请求的url地址
    4. getContextPath()获取项目的部署路径
    5. getRemoteAddr()获取客户端的IP地址
  2. 获取请求头的信息:getHeader(name) 掌握
  3. 获取请求参数(全部要掌握,最重要)
    1. getParameter(参数名),根据一个参数名获取一个参数值
    2. getParameterValues(参数名),根据一个参数名获取多个参数值
    3. getParameterMap()获取所有的请求参数,封装到Map中
  4. 解决请求参数乱码问题: request.setCharacterEncoding(“UTF-8”);
  5. 使用BeanUtils将map中的数据存储到JavaBean对象中(掌握)
    1. 拷贝BeanUtils的jar包,存放在WEB-INF里面的lib中
    2. map的key要和JavaBean的属性名保持一致,如果不一致那么该字段的值就无法存储
    3. BeanUtils中默认内置一些基本类型的转换器(如果map中的数据是string类型,JavaBean的属性还是int类型那么会自动转换)
  6. 使用request做请求转发: request.getRequestDispatcher(“跳转路径”).forward(request,response); 掌握
    1. 浏览器只会发送一次请求,而且浏览器的地址是跳转之前的路径
    2. 跳转操作由服务器完成的
  7. request对象作为域对象向存取数据,它的作用范围是一次请求中,和请求转发一起使用 掌握
    1. setAttribute(name,value)往域对象中存数据
    2. getAttribute(name)从域对象中获取数据

Response

Response概述

HttpServletResponse概述

​ 在Servlet API中,定义了一个HttpServletResponse接口(doGet,doPost方法的参数),它继承自ServletResponse接口,专门用来封装HTTP响应消息。由于HTTP响应消息分为响应行、响应头、响应体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码、响应头、响应体的方法

作用

  • 操作响应的三部分(响应行,响应头,响应体)

小结

  1. Response代表响应对象. 原型是HttpServletResponse, 服务器创建的, 以形参的形式存在doGet()/doPost()方法
  2. Response的作用
    • 操作响应的三部分(行, 头, 体)

操作响应行

  • 掌握操作响应行的方法
1
HTTP/1.1 200

image-20230921124341452

​ 常用的状态码:

​ 200:成功

​ 302:重定向

​ 304:访问缓存

​ 404:请求路径错误

​ 500:服务器错误

小结

  1. 设置的API: response.setStatus(int code);

  2. 一般不需要设置, 可能302 重定向需要设置

  3. 常见的响应状态码

    • 200 成功
    • 302 重定向
    • 304 读缓存
    • 404 客户端错误
    • 500 服务器错误

操作响应头

  • 掌握操作响应头的方法, 能够进行定时刷新和重定向

操作响应头的API

响应头: 是服务器指示浏览器去做什么

​ 一个key对应一个value

image-20230921124356724

​ 一个key对应多个value

image-20230921124402083

关注的方法: setHeader(String name,String value);

​ 常用的响应头

​ Refresh: 定时跳转 (eg:服务器告诉浏览器5s之后跳转到百度)

​ Location:重定向地址(eg: 服务器告诉浏览器跳转到xxx)

​ Content-Disposition: 告诉浏览器下载

​ Content-Type:设置响应内容的MIME类型(服务器告诉浏览器内容的类型)

定时刷新

1
response.setHeader("refresh","秒数;url=跳转的路径"); //几秒之后跳转到指定的路径上

重定向【重点】

image-20230921124409865

  1. 重定向两次请求
  2. 重定向的地址栏路径改变
  3. 重定向的路径写绝对路径(带域名/ip地址的, 如果是同一个项目里面的,域名/ip地址可以省略)
  4. 重定向的路径可以是项目内部的,也可以是项目以外的(eg:百度)
  5. 重定向不能重定向到WEB-INF下的资源
  6. 把数据存到request里面, 重定向不可用
1
2
3
4
5
6
7
8
9
10
//方式一: 重定向
//1.设置状态码
//response.setStatus(302);
//2.设置重定向的路径(绝对路径,带域名/ip地址的,如果是同一个项目里面的,域名/ip地址可以省略)
//response.setHeader("Location","http://localhost:8080/day28/demo08");
//response.setHeader("Location","/day28/demo08");
//response.setHeader("Location","http://www.baidu.com");

//方式二: 直接调用sendRedirect方法, 内部封装了上面两行
response.sendRedirect("http://localhost:8080/day28/demo08");
  • 重定向
1
response.sendRedirect("重定向的路径");

重定向和请求转发的对比

image-20230921124417787

重定向的特点

  1. 重定向的跳转是由浏览器发起的,在这个过程中浏览器会发起两次请求
  2. 重定向跳转可以跳转到任意服务器的资源,但是无法跳转到WEB-INF中的资源
  3. 重定向跳转不能和request域对象一起使用
  4. 重定向跳转浏览器的地址栏中的地址会变成跳转到的路径

请求转发的特点

  1. 请求转发的跳转是由服务器发起的,在这个过程中浏览器只会发起一次请求

  2. 请求转发只能跳转到本项目的资源,但是可以跳转到WEB-INF中的资源

  3. 请求转发可以和request域对象一起使用

操作响应体

操作响应体的API

image-20230921124427939

​ 页面输出只能使用其中的一个流实现,两个流是互斥的.

使用字符输出流输出字符串

  • 解决字符流输出中文乱码问题
1
response.setContentType("text/html;charset=utf-8");
  • 使用字符输出流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
* 包名:${PACKAGE_NAME}
*
* response设置响应体的信息
* 直接给浏览器要展示的数据
*
* 解决响应体的乱码问题
* response.setContentType("text/html;charset=UTF-8");
* 这句代码底层做了什么?
* 1. 设置服务器响应的字符集为UTF-8
* 2. 设置Content-Type响应头的信息为 "text/html;charset=UTF-8"
* 让浏览器知道了服务器的响应字符集UTF-8,那么浏览器也会使用UTF-8解码
*/
@WebServlet("/demo04")
public class ServletDemo04 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");

//要向浏览器输出响应体的信息,需要通过流来进行操作
//第一种:字符串,输出文本内容
PrintWriter writer = response.getWriter();
//使用字符流往浏览器输出文本
//1. writer()方法,只能输出字符串,如果输出int、float等等类型的话,则会有问题
writer.write("你好世界");

//2. print()方法,可以输出数字、字符串
//writer.print(8);
}
}

使用字节输出流向浏览器输出文件

目标是:向浏览器输出一张图片

注意:需要引入commons-io的jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 使用response的字节流向浏览器输出一个文件(一张图片)
* 目标:浏览器访问ServletDemo05,会在浏览器上显示一张图片
*/
@WebServlet("/demo05")
public class ServletDemo05 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 读取b.jpg图片,将其转换成字节输入流,使用ServletContext
ServletContext servletContext = getServletContext();
InputStream is = servletContext.getResourceAsStream("b.jpg");

//2. 使用字节输出流,将is中的字节都输出到浏览器
ServletOutputStream os = response.getOutputStream();

/*byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1){
os.write(buffer,0,len);
}*/

IOUtils.copy(is,os);

os.close();
is.close();
}
}

路径问题

完整url地址

url的组成部分
  1. 协议 http://
  2. 服务器主机地址 localhost
  3. 服务器的端口号 :8080
  4. 项目的虚拟路径(部署路径) responseDemo
  5. 具体的项目上资源路径 /pages/hello.html 或者 /demo02 Servlet的映射路径
什么时候会使用完整的url
  1. 浏览器地址栏直接访问
  2. 一个项目中,访问另一个项目中的资源

相对路径

相对路径的概念

不以”/“开头的路径写法,它是以目标路径相对当前文件的路径,其中”..”表示上一级目录

它是以目标资源的url,相对当前资源的url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>hello world....</h1>
<!--
目标资源的url: http://localhost:8080/responseDemo/demo05
当前资源的url: http://localhost:8080/responseDemo/pages/demo.html

相对路径的优劣:
1. 优势: 无论部署的项目名怎么改变,我的路径都不需要改变
2. 劣势: 如果当前资源的位置发生改变,那么相对路径就必定要发生改变
-->
<a href="../demo05">访问ServletDemo05</a>
</body>
</html>

绝对路径

绝对路径的概念

绝对路径就是以”/“开头的路径写法,它有如下两种情况

  1. 请求转发的绝对路径写法 “/资源的路径”,不需要写项目路径,例如”/hello.html”
  2. 不是请求转发的绝对路径写法”/项目部署路径/资源路径” 例如 “/responseDemo/hello.html”

案例-完成文件下载

需求分析

  • 创建文件下载的列表的页面,点击列表中的某些链接,下载文件.

image-20230921124438366

文件下载分析

什么是文件下载

​ 将服务器上已经存在的文件,输出到客户端浏览器.

​ 说白了就是把服务器端的文件拷贝一份到客户端, 文件的拷贝—> 流(输入流和输出流)的拷贝

文件下载的方式
  • 第一种:超链接方式(不推荐)

    链接的方式:直接将服务器上的文件的路径写到href属性中.如果浏览器不支持该格式文件,那么就会提示进行下载, 如果浏览器支持这个格式(eg: png, jpg….)的文件,那么直接打开,不再下载了

  • 第二种:手动编码方式(推荐)

    手动编写代码实现下载.无论浏览器是否识别该格式的文件,都会下载.

思路分析

超链接方式
  1. 准备下载的资源(文件)
  2. 编写一个下载页面
  3. 在这个页面上定义超链接,指定href
编码方式

手动编码方式要求

​ 设置两个头和一个流

​ 设置的两个头:

    Content-Dispostion: 服务器告诉浏览器去下载

​ 设置一个流:

    获得要下载的文件的输入流.

思路

image-20191209150057781

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.nbchen.servlet;

import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

/**
* 1. 读取客户端想下载的文件
* 2. 将客户端想下载的文件使用字节输出流的方式响应给客户端
*/
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取到客户端想要下载的文件名
String fileName = request.getParameter("fileName");

//设置下载的文件的mime-type
String mimeType = getServletContext().getMimeType(fileName);
response.setHeader("Content-Type",mimeType);

//2. 使用字节输入流读取客户端要下载的那个文件
InputStream is = getServletContext().getResourceAsStream("file/" + fileName);

//3. 使用字节输出流,将图片输出到浏览器
ServletOutputStream os = response.getOutputStream();

//输出之前,我们通过设置响应头的方式,指示客户端下载文件
//我们先将fileName进行URL编码: 使用URLEncoder
String encodeFileName = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition","attachment;filename="+encodeFileName);

IOUtils.copy(is,os);
os.close();
is.close();
}
}

细节处理

  • 告诉浏览器设置的响应头里面不支持中文的, 抓包来看:

image-20230921125540257

  • 解决方案: 手动进行编码再设置进去就ok了

综合案例

案例-注册

image-20230921124453195

三层架构(今天先不讲)

  • 软件中分层:按照不同功能分为不同层,通常分为三层:表现层(web层),业务层,持久(数据库)层。

    image-20230921124457922

  • 不同层次包名的命名

分层 包名(公司域名倒写)
表现层(web层) com.itheima.web
业务层(service层) com.itheima.service
持久层(数据库访问层) com.itheima.dao
JavaBean com.itheima.bean
工具类 com.itheima.utils
  • 分层的意义:
    1. 解耦:降低层与层之间的耦合性。
    2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
    3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。
    4. 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用。
  • 程序设计的宗旨:
    • 高内聚低耦合
    • 可扩展性强
    • 可维护性强
    • 可重用性强

完成注册案例

注册案例思路

image-20191209154418825

准备工作
  • 数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create database day25;
use day25;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`address` varchar(40) DEFAULT NULL,
`nickname` varchar(40) DEFAULT NULL,
`gender` varchar(10) DEFAULT NULL,
`email` varchar(20) DEFAULT NULL,
`status` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8
  • JavaBean
1
2
3
4
5
6
7
8
9
10
11
public class User implements Serializable{
private Integer id;
private String username;
private String password;
private String address;
private String nickname;
private String gender;
private String email;
private String status;//1 表示已激活 0表示未激活
//...
}
  • 导入jar
    • mysql驱动
    • druid
    • dbutils
    • beanutils
  • 工具类和配置文件
    • DruidUtil
    • druid.properties
注册案例实现

注册页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<form action="/userDemo/register" method="post">
用户名<input type="text" name="username"><br>
密码<input type="text" name="password"><br>
昵称<input type="text" name="nickname"><br>
地址<input type="text" name="address"><br>
邮箱<input type="text" name="email"><br>
性别<input type="radio" name="gender" value="male">
<input type="radio" name="gender" value="female">
<br>
<input type="submit" value="注册">
</form>
</body>
</html>

RegisterServlet的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 包名:${PACKAGE_NAME}
*
*/
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在最前面解决乱码问题:请求参数的中文乱码,响应的中文乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

//1. 获取所有的请求参数
Map<String, String[]> parameterMap = request.getParameterMap();

//2. 使用BeanUtils 将parameterMap中的数据,存储到User对象中
User user = new User();

//设置默认的status为"0"
user.setStatus("0");

try {
BeanUtils.populate(user,parameterMap);

//3. 使用DBUtils将用户信息存储到数据库
//这里需要mysql驱动、druid、dbutils的jar包
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql = "insert into user values (null,?,?,?,?,?,?,?)";

queryRunner.update(sql,user.getUsername(),user.getPassword(),user.getAddress(),
user.getNickname(),user.getGender(),user.getEmail(),user.getStatus());

//如果存储的时候没有出现问题,则说明注册成功,使用重定向跳转到登录页面
response.sendRedirect("/userDemo/login.html");
} catch (Exception e) {
e.printStackTrace();

//如果注册失败,则向浏览器响应一句"注册失败"
response.getWriter().write("注册失败");
}
}
}

小结

  1. 注册本质: 向数据库插入一条记录
  2. 思路(在RegisterServlet)
    • 获得用户提交的数据, 使用BeanUtils封装成User对象
    • 补全User对象(状态)
    • 使用DBUtils向数据库里面插入一条记录
    • 响应

案例-登录

image-20230921124509565

  • 点击登录按钮, 进行登录.
  • 登录成功,显示login Success
  • 登录失败,显示login failed

思路

image-20191209160307597

准备工作

  • 页面的准备 login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="/userDemo/login" method="post">
用户名<input type="text" name="username"><br>
密码<input type="text" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.nbchen.servlet;

import com.itheima.pojo.User;
import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//1. 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");

//2. 连接数据库校验用户名和密码
String sql = "select * from user where username=? and password=?";
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
try {
User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);
if (user != null) {
//登录成功
response.getWriter().write("登录成功");
}else {
//登录失败
response.getWriter().write("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("登录失败");
}
}
}

小结

  1. 本质: 就是根据用户名和密码查询数据库
  2. 思路(LoginServlet)
    • 获得用户输入用户名和密码
    • 使用DBUtils根据用户名和密码查询数据库 封装成User对象
    • 判断是否登录成功(判断User是否为null)
    • 响应

会话

会话的概念

用户打开浏览器,浏览不同的网页(资源),发出多个请求,直到关闭浏览器的过程,称为一次会话(多次请求). 如同打电话。

我们在会话的过程(多次请求)之中,用户可能会产生一些数据,这些数据有的需要保存起来的,我们就可以通过会话技术来保存用户各自的数据。

Servlet技术中,提供了两个用于保存会话数据的对象,分别是Cookie和Session

为什么要使用会话技术

保存**用户各自(以浏览器为单位)**的数据。

常用的会话技术

cookie是客户端(浏览器)端的技术,用户浏览的信息以键值对(key=value)的形式保存在浏览器上。如果没有关闭浏览器,再次访问服务器,会把cookie带到服务端,服务端就可以做响应的处理。

session

session是服务器端的技术。服务器为每一个浏览器开辟一块内存空间,即session。由于内存空间是每一个浏览器独享的,所有用户在访问的时候,可以把信息保存在session对象中。同时,每一个session对象都对应一个sessionId,服务器把sessionId写到cookie中,再次访问的时候,浏览器会把cookie(sessionId)带过来,找到对应的session对象。

小结

  1. 为什么要使用会话技术?

保存用户各自(浏览器为单位)的数据

  1. 常见的会话技术?

    • Cookie 浏览器端的技术
    • Session 服务器端的技术

Cookie的概念和作用

Cookie的概念

image-20230921124519143

Cookie是一种客户端的会话技术,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去。

Cookie的作用

  1. 在浏览器中存放数据
  2. 将浏览器中存放的数据携带到服务器

Cookie的应用场景

1.记住用户名
当我们在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框.
image-20230921124525091

2.自动登录(记住用户名和密码)
当用户在淘宝网站登录成功后,浏览器会记录登录成功的用户名和密码,下次再访问该网站时,自动完成登录功能.
以上这些场景都是使用会话cookie实现的,将上次的信息保存到了cookie中,下次直接从cookie中获取数据信息
image-20230921124530964

3.保存电影的播放进度

​ 在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到cookie中

Cookie就是将一些少量的配置信息保存在”浏览器”

小结

image-20191211091737956

Cookie的快速入门

相关的API

  • 创建一个Cookie对象(cookie只能保存字符串数据。且不能保存中文)
1
new Cookie(String name,String value);
  • 把cookie写回浏览器
1
response.addCookie(cookie); 
  • 获得浏览器带过来的所有Cookie:
1
request.getCookies() ; //得到所有的cookie对象。是一个数组,开发中根据key得到目标cookie
  • cookie的 API
1
2
cookie.getName() ; //返回cookie中设置的key
cookie.getValue(); //返回cookie中设置的value

入门代码

ServletDemo01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet("/demo01")
public class ServletDemo01 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String str = "周杰棍";
//1. 创建一个cookie对象,用于存放str的值
Cookie cookie = new Cookie("username",str);

//2. 将cookie添加到response中
//底层是通过一个名为"Set-Cookie"的响应头携带到浏览器的
response.addCookie(cookie);
}
}

ServletDemo02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@WebServlet("/demo02")
public class ServletDemo02 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 从请求中取出cookie
//底层是由名为"Cookie"的请求头携带的
Cookie[] cookies = request.getCookies();

//2. 遍历出每一个cookie
if (cookies != null) {
for (Cookie cookie : cookies) {
//匹配cookie的name
if (cookie.getName().equals("username")) {
//它就是我们想要的那个cookie
//我们就获取它的value
String value = cookie.getValue();
System.out.println("在ServletDemo02中获取str的值为:" + value);
}
}
}
}
}

小结

  1. cookie特点

    • Cookie保存在客户端(浏览器端的)
    • 第一次请求的时候, 没有Cookie的, 先由服务器写给浏览器.
    • Cookie里面只能保存字符串, 大小有限制
  2. cookie相关API

    • new Cookie(String name,String value); 创建Cookie
    • response.addCookie(cookie); 把Cookie写给浏览器
    • request.getCookies(); 获得所有的Cookie对象
    • cookie.getName() 获得Cookie的key
    • cookie.getValue() 获得Cookie的value

    Cookie本质是请求头,响应头

Cookie进阶

cookie的分类

  • 会话级别cookie

​ 在默认的情况下,当浏览器进程结束(浏览器关闭,会话结束)的时候,cookie就会消失。

  • 持久性cookie

    ​ 给cookie设置有效期.cookie.setMaxAge(int expiry) :时间是秒

    ​  -1:默认。代表Cookie数据存到浏览器关闭(保存在浏览器文件中)。

           正整数:以秒为单位保存数据有有效时间(把缓存数据保存到磁盘中)

    ​  0:代表删除Cookie.如果要删除Cookie要确保路径一致

cookie设置有效路径

1
setPath(String url) ;设置路径

​ 有效路径作用 :

  1. 保证不会携带别的网站/项目里面的cookie到我们自己的项目
  2. 如果路径不一样, cookie的key可以相同
  3. 保证自己的项目可以合理的利用自己项目的cookie

小结

  1. Cookie的类型

    • 会话级别【默认的】 浏览器关闭了就消失了
    • 持久级别 setMaxAge(int 秒)
      • -1 默认值
      • 正整数
      • 0 删除cookie 【必须路径一致】
  2. cookie有效路径 cookie.setPath(String path) 建议设置成当前的项目部署路径; setPath(request.getContextPath())

  3. cookie的弊端 cookie的大小(个数和自身大小)和格式(只能存字符串)有限制,默认不支持中文,解决中文办法

    具体和版本有关,我们在实际项目中,如果要使用cookie的话,一般是不会存储中文的,万一需要存储中文

1
2
URLEncoder.encode(value,"utf-8");//存入的时候(先通过utf-8编码)
URLDecoder.decode(value,"utf-8");//取出 (通过utf-8解码)

补充-封装cookie的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 包名:com.nbchen.utils
*/
public class CookieUtil {
/**
* 创建并且设置cookie
* @param name
* @param value
* @param time
* @param path
* @return
*/
public static Cookie createAndSetCookie(String name,String value,int time,String path){
//1. 创建一个cookie对象,存储键值对
Cookie cookie = new Cookie(name,value);
//设置cookie的有效期
cookie.setMaxAge(time);

//设置cookie有效路径
cookie.setPath(path);
return cookie;
}

/**
* 根据cookie的name获取cookie的value
* @param cookies
* @param name
* @return
*/
public static String getCookieValue(Cookie[] cookies,String name) {
String value = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
//匹配cookie的name
if (cookie.getName().equals(name)) {
//它就是我们想要的那个cookie
//我们就获取它的value
value = cookie.getValue();
}
}
}
return value;
}
}

案例-记录用户各自的上次访问时间

需求

img

​ 在访问一个资源的时候,展示上次访问的时间

​ 若是第一次访问则展示:你是第一次访问,若不是第一次则展示:你上次访问的时间是:xxxx

分析

image-20191211105647723

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.nbchen.servlet;

import com.nbchen.utils.CookieUtil;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;


@WebServlet("/rem")
public class RememberServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
//1. 从cookie中获取上一次访问时间
Cookie[] cookies = request.getCookies();
String lastTime = CookieUtil.getCookieValue(cookies, "lastTime");
if (lastTime == null) {
//说明我是第一次访问
response.getWriter().write("你是第一次访问!!!");
}else {
//说明我不是第一次访问
response.getWriter().write("您的上次访问时间是:"+lastTime);
}

//2. 将当前时间存储到cookie中
lastTime = new SimpleDateFormat("yyyy-MM-dd/HH:mm:ss").format(new Date());
//cookie的value中不能存储空格
Cookie cookie = CookieUtil.createAndSetCookie("lastTime", lastTime, 7 * 24 * 60 * 60, request.getContextPath());
response.addCookie(cookie);
}
}

小结

  1. 案例关键: 判断是否第一次访问【说白了就是判断目标Cookie是否为null】

  2. 取的时候 和存时候的key要一致

  3. 不管是哪一次访问, 都需要记录当前的时间到Cookie

Session

session概述

session概述

​ session是服务器端的技术。服务器为每一个浏览器开辟一块内存空间,即session对象。由于session对象是每一个浏览器特有的,所以用户的记录可以存放在session对象中。同时,每一个session对象都对应一个sessionId,服务器把sessionId写到cookie中,再次访问的时候,浏览器把sessionId带过来,找到对应的session对象

cookie和Session的不同

  • cookie是保存在浏览器端的,大小和个数都有限制。session是保存在服务器端的, 原则上大小是没有限制(实际开发里面也不会存很大大小), 安全一些。
  • cookie不支持中文,并且只能存储字符串;session可以存储基本数据类型,集合,对象等

Session的执行原理

​ 1、获得cookie中传递过来的SessionId(cookie)

​ 2、如果Cookie中没有sessionid,则创建session对象

​ 3、如果Cookie中有sessionid,找指定的session对象

​ 如果有sessionid并且session对象存在,则直接使用

​ 如果有sessionid,但session对象销毁了,则执行第二步

image-20191211112121400

小结

  1. session是服务器端的技术, 数据保存在服务器端的
  2. 只有在服务器端调用了requet.getSession()的时候, 才有session产生
  3. session基于cookie的
    • 创建session的同时 生成sessionId, 服务器自动通过Cookie的方式写给浏览器, 浏览器自己保存
    • 下次的话 浏览器携带cookie(SessionId)找到对应的session使用了

Session的基本使用

范围: 会话(多次请求) 保存用户各自的数据(以浏览器为单位)

  • request.getSession(); 获得session(如果第一次调用的时候其实是创建session,第一次之后通过sessionId找到session进行使用)
  • Object getAttribute(String name) ;获取值
  • void setAttribute(String name, Object value) ;存储值
  • void removeAttribute(String name) ;移除

小结

  1. Session基本使用: 作为域对象存取数据 范围: **一次会话(多次请求, 用户各自的)**不同的浏览器session不一样

    • Object getAttribute(String name) ;获取值

    • void setAttribute(String name, Object value) ;存储值

    • void removeAttribute(String name) ;移除

  2. 浏览器关闭了, session使用不了, 是session销毁了吗?

    ​ session没有销毁.

    ​ session基于cookie, sessionId保存到cookie里面的, 默认情况下cookie是会话级别,浏览器关闭了cookie就是消失了,也就是说sessionId消失了, 从而找不到对应的session对象了, 就不能使用了.

    ​ 解决: 自己获得sessionId, 自己写给浏览器 设置Cookie的有效时长, 这个Cookie的key必须: JSESSIONID

三个域对象比较

三个域对象比较

域对象 创建 销毁 作用范围 应用场景
ServletContext 服务器启动 服务器正常关闭/项目从服务器移除 整个项目 记录网站访问次数,聊天室
HttpSession 没有JSESSIONID这个cookie的时候,调 用request.getSession()方法 session过期(默认闲置30分钟),或者调用session对象的invalidate()方法,或者服务器异常关闭 会话(多次请求) 验证码校验, 保存用户登录状态
HttpServletRequest 来了请求 响应这个请求(或者请求已经接收了) 一次请求 servletA和jsp(servletB)之间数据传递(转发的时候存数据)

C:\Users\用户名\.IntelliJIdea2017.2\system\tomcat\_sz61\work\Catalina\localhost 目录查看

  • 如果是正常关闭服务器,

​ 把session钝化到服务器磁盘上,再次启动,把磁盘上的文件活化到内存里面

​ Session钝化:把内存中的session序列化保存到硬盘上

​ Session活化:从硬盘上读取序列化的session到内存中形成一个session对象

三个域对象怎么选择?

三个域对象怎么选择?

​ 一般情况下, 最小的可以解决就用最小的.

​ 但是需要根据情况(eg: 重定向, 多次请求, 会话范围, 用session; 如果是转发,一般选择request)

小结

  1. session的销毁
    • invalidate()
  2. session应用场景
    • 验证码校验
    • 保存用户的登录状态
    • ….
  3. 选择
    • 一般情况下, 最小的可以解决就用最小的.
      • 转发 一般选择request
      • 重定向 一般选择session

案例-一次性验证码校验

需求

image-20230921124625602

​ 在网站登录的时候,生成一个验证码.登录的时候对验证码进行校验.

分析

生成验证码

  1. 拷贝验证码的jar包
  2. 创建CodeServlet
1
2
//1.生成验证码
//2.响应给客户端(浏览器)

校验验证码

image-20230921124634457

实现

  • 登录页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<center>
<h1>用户登录</h1>
<form action="login" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
验证码:<input type="text" name="checkCode"><br>
<img src="checkCode" id="code"><a href="javascript:;" onclick="changeCheckCode()">换一换</a><br>
<input type="submit" value="登录"/>
</form>

<script>
//实现点击换一换,切换验证码图片
function changeCheckCode() {
//具体怎么去切换验证码呢? 也就是重新设置img标签的src
//如果直接设置src为"checkCode"的话,那么浏览器就会从缓存中获取验证码图片,所以我们要让浏览器避开缓存
//浏览器如果发现每次请求的路径不一样,就不会找缓存
document.getElementById("code").setAttribute("src","checkCode?date="+new Date())
}
</script>
</center>
</body>
</html>
  • CheckCodeServlet(需要引入jar包)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.nbchen.servlet;

import cn.dsna.util.images.ValidateCode;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 动态创建验证码图片的Servlet
*/
@WebServlet("/checkCode")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//目标:创建出一张验证码图片,并且将图片输出到浏览器
ValidateCode validateCode = new ValidateCode(100, 30, 4, 20);

//获取验证码图片上的字
String code = validateCode.getCode();
//将服务器创建的验证码,存储到session中
request.getSession().setAttribute("code",code);

//将验证码图片输出到浏览器
validateCode.write(response.getOutputStream());
}
}
  • LoginServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.nbchen.servlet;

import com.nbchen.pojo.User;
import com.nbchen.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//1. 解决乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

//2. 获取请求参数username和password
String username = request.getParameter("username");
String password = request.getParameter("password");
//获取浏览器传入的验证码(用户输入的验证码)
String checkCode = request.getParameter("checkCode");
//获取服务器生成的验证码,从session里面根据key "code"取出
HttpSession session = request.getSession();
String code = (String) session.getAttribute("code");

//3. 校验验证码
if (code.equalsIgnoreCase(checkCode)) {
//验证码校验通过
//进行用户名和密码的校验

//3. 连接数据库校验用户名和密码,也就是执行查询的SQL语句
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql = "select * from user where username=? and password=?";
//执行查询,查询一条数据,封装到User中
User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);
//判断是否登录成功
if (user != null) {
//登录成功
//跳转到成功页面success.html
response.sendRedirect("/userDemo/success.html");
}else {
//登陆失败,直接向浏览器输出"登陆失败"
response.getWriter().write("用户名或密码错误");
}
}else {
//验证码错误
response.getWriter().write("验证码错误");
}
} catch (Exception e) {
e.printStackTrace();
//登陆失败,直接向浏览器输出"登陆失败"
response.getWriter().write("登陆失败");
}
}
}

小结

  1. 需要在CodeServlet 把生成的验证码存到session里面

    • 不能存到ServletContext, 就共享验证码了
    • 不能存到request, 根本不能用
  2. 思路

    • CodeServlet: 生成验证码存到Session
    • LoginServlet:
      • 获得用户输入的验证码
      • 获得session里面存的验证码
      • 比较是否一致

Filter

Filter概述

什么是filter

​ Filter:一个实现了特殊接口(Filter)的Java类. 实现对请求资源(jsp,servlet,html,)的过滤的功能.

​ 过滤器是一个运行在服务器的程序, 优先于请求资源(Servlet或者jsp,html)之前执行. 过滤器是javaweb技术中最为实用的技术之一.

过滤的作用

​ 对目标资源(Servlet,jsp)进行过滤.

​ 应用场景:登录权限检查,解决网站乱码,过滤敏感字符 …

小结

  1. Filter是运行在服务器端的Java程序, 优先于请求资源(jsp,servlet,html…)之前执行

  2. FIlter应用场景

    • 登录权限校验
    • 处理网站乱码
    • 过滤非法字符

    ….

Filter入门(重点)

配置文件方式

  1. 创建一个类实现Filter接口
  2. 在web.xml配置FIlter的拦截路径

注解方式

  1. 创建一个类实现Filter接口
  2. 在这个类上面添加@WebFilter(“拦截的路径”)

建议:可以直接new Filter

image-20230921124646866

代码

通过xml配置方式
  • 创建一个类实现Filter接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* - 创建一个类实现Filter接口
* - 在web.xml对过滤器进行配置
*/
public class FilterDemo01 implements Filter {
@Override
//过滤的方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo01收到了请求...");
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}


@Override
public void destroy() {

}
}
  • 在web.xml对过滤器进行配置
1
2
3
4
5
6
7
8
9
10
<!--注册Filter-->
<filter>
<filter-name>FilterDemo01</filter-name>
<filter-class>com.itheima.web.filter.FilterDemo01</filter-class>
</filter>
<!--配置Filter过滤路径-->
<filter-mapping>
<filter-name>FilterDemo01</filter-name>
<url-pattern>/demo01</url-pattern>
</filter-mapping>
通过注解方式
  • 创建一个类实现Filter接口
  • 直接在这个类上面添加注解进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebFilter("/demo02")
public class FilterDemo02 implements Filter{

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo02... 收到了请求");
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}


@Override
public void destroy() {

}
}

小结

入门步骤
  1. xml方式

    • 创建一个类实现Filter接口
    • 在web.xml配置Filter
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!--配置filter-->
    <filter>
    <!--filter-name:filter名字 随便取的(一般用类名, 不要重复)-->
    <filter-name>FilterDemo01</filter-name>
    <!--filter-class:filier类的全限定名-->
    <filter-class>com.itheima.web.filter.FilterDemo01</filter-class>
    </filter>

    <!--配置Filter拦截映射的路径-->
    <filter-mapping>
    <!--filter-name:必须和filter里面的filter-name一致-->
    <filter-name>FilterDemo01</filter-name>
    <!--url-pattern:匹配的路径-->
    <url-pattern>/demo01</url-pattern>
    </filter-mapping>
  2. 注解方式

    • 创建一个类实现Filter接口
    • 在这个类上面添加@WebFilter(“拦截的路径”)

    image-20191214094146331

request和response

image-20230921124658205

Filter的生命周期

Filter生命周期介绍

​ 过滤器从创建到销毁的过程

生命周期方法

​ init(FilterConfig):初始化

​ doFilter(ServletReqeust req,ServletResponse resp,FilterChain chain):执行过滤的方法

​ destroy():销毁

Filter生命周期描述

  1. 服务器启动的时候, 会调用init()方法进行初始化【调用一次】
  2. 任何一次请求都会调用doFilter()方法进行过滤【路径相匹配】
  3. 服务器正常关闭或者项目从服务器移除, 调用destory()方法进行销毁【调用一次】

注意: 默认情况下,Servlet是来了第一次请求的时候 调用init()方法进行初始化.我们可以在Servlet里面设置启动项.

FilterConfig【了解】

获得过滤器的初始化参数

  • 配置初始化参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<filter>
<filter-name>myFilter01</filter-name>
<filter-class>com.nbchen.filter.MyFilter01</filter-class>

<!--添加初始化参数-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>myFilter01</filter-name>
<!--
servlet的映射路径的目的是: 指定由哪个servlet去处理对应的请求
过滤器的过滤路径的目的是: 指定过滤哪个或者哪些请求
-->
<url-pattern>/*</url-pattern>
</filter-mapping>
  • 在Filter的init()方法里面获得了
1
2
3
4
5
6
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//在filter对象初始化的时候,使用filterConfig对象获取web.xml配置文件中的初始化参数
String encoding = filterConfig.getInitParameter("encoding");
System.out.println("MyFilter01创建了..."+encoding);
}

映射路径

​ 假设有一个管理员权限的过滤器,它应该对用户发出的管理员功能的请求进行条件的过滤。但是当用户发出登录、注册等请求的时候,不应该进行过滤。所以我们过滤器,应该有选择的过滤器请求。这就需要学习配置过滤器不同的映射路径,从而让过滤器过滤希望过滤器的请求。

完全路径匹配

以”/“开始

1
/demo01 ---> 过滤器只能拦截路径/demo01; 

目录匹配

以”/“开始 以 *结束 .

1
/* --->当前项目下的所有的路径都可以拦截;   /aa/*  ---> 可以拦截 /aa/bb, /aa/bb/cc

扩展名匹配

以”*”开始 例如: *.jsp *.do

1
*.do--->可以拦截路径的后缀是 do的 ;  *.jsp--->拦截所有JSP

缺省匹配

/ 除了jsp以外的资源都匹配

当前Filter里面不支持, Servlet里面可行的. 后面在Servlet里面遇到

小结

  1. 完全路径匹配
1
/demo01
  1. 目录匹配
1
/*  匹配所有的资源   /aa/* 匹配以/aa开头的资源
  1. 扩展名匹配
1
*.jsp  匹配上了所有的jsp   *.html 匹配上了所有的html
  1. 缺省匹配 / 匹配除了jsp以为的所有的资源
1
2
3
/   除了jsp 其它的都匹配

/* 匹配所有

拦截方式

​ 有了上面学习的映射路径,我们可以控制过滤器过滤指定的内容,但是我们在访问资源的时候,并不是每次都是之间访问,有时是以转发的方式访问的,这就需要我们要让过滤器可以区分不同的访问资源的方式,有不同的拦截方式。 是通过 DispatcherType 来指定的.

  • DispatcherType.REQUEST,默认值,过滤从浏览器发送过来的请求和重定向 不过滤转发

  • DispatcherType.FORWARD,只过滤转发过来的请求

小结

  1. 通过dispatcherTypes配置拦截方式

    • DispatcherType.FORWARD: 【只】过滤转发

    • DispatcherType.REQUEST: 除了转发以为其它的都过滤(1.浏览器的直接请求 2.重定向)【默认值】

  2. 拦截方式的这个值,我们可以配置多个

1
@WebFilter(value = {"/demo06"},dispatcherTypes={DispatcherType.FORWARD,DispatcherType.REQUEST})

一般情况下, 转发我们不会过滤的. 转发属于服务器内部的行为. 直接使用默认值的情况偏多

过滤器链(filterChain)

​ 过滤器链作用:一个请求可能被多个过滤器所过滤,只有当所有过滤器都放行,请求才能到达目标资源,如果有某一个过滤器没有放行,那么请求则无法到达后续过滤器以及目标资源

image-20230921124710812

小结

  1. 过滤器链执行顺序
    • 配置文件: 谁先配置filter-mapping 谁先执行
    • 注解方式: 按照Filter的首字母顺序 eg: AFilter BFilter A在B的前面, AFilter先执行
    • 如果既有注解方式配置的过滤器,又有配置文件方式配置的过滤器,那么配置文件方式配置的过滤器先进行过滤

案例一: 统一全网站请求的中文乱码的处理

需求分析

​ 在整个网站中,可能会有get请求或post请求向服务器提交参数.参数中往往有中文信息.在后台每个Servlet中都需要去处理乱码.

​ 我们想做的是:请求到达Servlet中.就可以直接调用getParameter方法获得请求参数,请求参数已经没有乱码了.

思路分析

image-20230921124717683

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.nbchen.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* 包名:${PACKAGE_NAME}
*/
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//统一设置请求的编码方式为UTF-8
req.setCharacterEncoding("UTF-8");
chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {

}
}

案例二: 非法字符过滤

需求分析

​ 当用户发出非法言论的时候,提示用户言论非法。

​ 效果:

image-20230921124730300

第一个版本

思路分析

image-20230921124736687

代码实现

  • IllegalFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.nbchen.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class IllegalCharFilter implements Filter {
@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//1. 获取客户端输入的内容
String content = req.getParameter("content");
if (content != null) {
//2. 判断评论内容中是否有非法字符
if (content.contains("你大爷")) {
resp.setContentType("text/html;charset=UTF-8");
//评论内容中有非法字符
resp.getWriter().write("评论中包含非法字符,请重新评论");

return;
}
}

//如果请求参数中没有content,或者content中没有非法字符,都放行
chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {

}
}

第二个版本

思路分析

image-20230921124748787

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.nbchen.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

@WebFilter("/*")
public class IllegalCharFilter implements Filter {
private List<String> strList = new ArrayList<>();
@Override
public void destroy() {

}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//从strList中遍历出每一个字符串,然后进行比较
//判断请求参数中是否包含非法字符
String content = req.getParameter("content");
if (content != null) {
for (String str : strList) {
if (content.contains(str)) {
//表示请求参数中有非法字符,则拦截
resp.getWriter().write("评论中包含非法字符,请重新评论!!!");
return;
}
}
}
chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
//读取IllegalWords.txt里面的数据
//1. 将IllegalWords.txt转换成字节输入流
InputStream is = IllegalCharFilter.class.getClassLoader().getResourceAsStream("IllegalWords.txt");
//2. 将字节输入流,包装成BufferReader
try {
BufferedReader bfr = new BufferedReader(new InputStreamReader(is,"UTF-8"));
String str = null;
while (( str = bfr.readLine()) != null) {
//每读到一个字符串,就将它存储到strList中
strList.add(str);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

第三个版本

思路分析

  1. 动态代理的作用是:
    1. 在不修改类的源码的基础上,增强类的方法
    2. 可以减少重复代码,在执行方法之前添加前置操作、执行方法之后添加后置操作(Spring的AOP思想)
    3. 我们可以在不写接口的实现类的情况下,创建接口的对象(mybatis框架)
  2. 代理模式的分类:
    1. 静态代理
    2. 动态代理
  3. 静态代理和动态代理的区别:
    1. 静态代理中必须存在代理类
    2. 静态代理的代理关系是在编译的时候确定的,而动态代理的代理关系是在程序运行的时候才确定的

image-20230921124800491

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.nbchen.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
* 包名:${PACKAGE_NAME}
*/
@WebFilter("/*")
public class IllegalCharFilter implements Filter {
private List<String> strList = new ArrayList<>();
@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//放行之前,使用动态代理技术,增强req对象的方法
//将req强转成HttpServletRequest
HttpServletRequest request = (HttpServletRequest) req;
//类加载器
ClassLoader classLoader = req.getClass().getClassLoader();
//被代理的接口: HttpServletRequest
HttpServletRequest requestProxy = (HttpServletRequest) Proxy.newProxyInstance(classLoader, new Class[]{HttpServletRequest.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强getParameter()方法,其它方法还是调用被代理者原本的方法
if (method.getName().equals("getParameter")) {
//要增强getParameter()方法,其实就是将请求参数值中的非法字符替换成*
//1. 获取请求参数值
String value = (String) method.invoke(request, args);
//2. 判断请求参数值中是否包含非法字符
for (String str : strList) {
if (value.contains(str)) {
//2.1 包含非法字符,就要将value中的非法字符替换成*
String start = "";
for (int i=0;i<str.length();i++){
start += "*";
}
value = value.replace(str,start);
}
}
return value;
}
//不用增强的方法,要调用被代理者原本的方法
return method.invoke(request,args);
}
});

//最后肯定要放行
chain.doFilter(requestProxy, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
//读取IllegalWords.txt里面的数据
//1. 将IllegalWords.txt转换成字节输入流
InputStream is = IllegalCharFilter.class.getClassLoader().getResourceAsStream("IllegalWords.txt");
//2. 将字节输入流,包装成BufferReader
try {
BufferedReader bfr = new BufferedReader(new InputStreamReader(is,"UTF-8"));
String str = null;
while (( str = bfr.readLine()) != null) {
//每读到一个字符串,就将它存储到strList中
strList.add(str);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Listener监听器

Listener概述

什么是Listener

​ 监听器就是一个Java类,用来监听其他的JavaBean对象的变化

​ 在javaweb中监听器就是监听三个域对象的状态的。request,session,servletContext(application)

监听器的应用

​ 主要在Swing编程

​ 在Android/ios大量应用

​ JS里面的事件

小结

  1. 什么是监听器? 就是一个类(对象), 用来监听其它JavaBean状态的
  2. 我们学的是web里面的监听器, web里面的监听器是监听三个域对象的
  3. 监听器应用
    • 在Android/ios大量应用
    • JS里面的事件
    • 初始化工作(监听ServletContext)

javaweb中的监听器

javaweb的监听器

​ javaweb的监听器:监听ServletContext,HttpSession,ServletRequest三个域对象状态

​ 事件源和监听器绑定的过程:通过配置web.xml完成

JavaWeb中的监听器类型

  • 三类8个

只讲解监听ServletContext的创建和销毁.

ServletContext是什么时候创建: 服务器启动的时候

ServletContext是什么时候销毁: 服务器关闭的时候

所以监听ServletContext创建和销毁,就能监听服务器的启动和关闭,就可以在服务器启动和关闭的时候执行一些代码(比如在服务器启动的时候读取Spring的配置文件,创建Spring的核心容器)

JavaWeb的监听器使用步骤

  1. 创建一个类实现监听器接口
  2. 在web.xml进行配置(绑定)

小结

  1. 学习监听器就学一个 监听ServletContext的创建和销毁

监听ServletContext的创建和销毁的监听器

说明: 监听ServletContext对象的创建和销毁

  • 方法

    image-20230921124811914

  • 问题

    ServletContext对象何时创建和销毁:应用在,它就在

        创建:服务器启动时候(前提是这个项目在这个服务器里面). 服务器为每个WEB应用创建一个单独的ServletContext.

        销毁:服务器关闭的时候,或者项目从服务器中移除.

  • 企业中应用

    ​ 初始化工作.

    ​ 加载配置文件:Spring框架,ContextLoaderListener

步骤:

  1. 创建一个类实现ServletContextListener
  2. 在web.xml配置

代码:

  • JAVA代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package com.nbchen.listener;

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;

    /**
    * 包名:com.nbchen.listener
    *
    * 一、写一个类实现ServletContextListener接口,并重写方法
    * 方法1: contextInitialized()会在ServletContext对象创建的时候执行,也就是在服务器启动的时候
    * 方法2: contextDestroyed()会在ServletContext对象销毁的时候执行,也就是在服务器关闭的时候
    *
    * 二、在web.xml中配置监听器
    */
    public class MyContextListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    System.out.println("监听到了服务器的启动....创建spring的核心容器");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    System.out.println("监听到了服务器的关闭....销毁spring的核心容器");
    }
    }
  • 配置(web.xml)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <listener>
    <listener-class>com.itheima.listener.MyContextListener</listener-class>
    </listener>
    </web-app>

小结

  1. 步骤
    • 创建一个类实现ServletContextListener接口
    • 在web.xml配置
  2. 应用: Spring框架里面, 服务器启动时候, 就加载Spring框架 初始化

邮箱(了解)

邮件流程

image-20230921124823877

邮件服务器

服务器

  • 硬件+软件(eg: mysql, tomcat…)

邮件服务器

  1. 租(企业邮箱)
  2. 自己搭建

邮件软件安装

  1. 服务端

image-20230921124834128

  1. 客户端

image-20230921124846806

邮件的发送

  1. 直接通过Foxmail发送
  2. 通过java代码发送
    • 拷贝jar
    • 拷贝工具类
    • 使用工具类发送

MailUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.nbchen.utils;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import java.util.Properties;

/**
* 发送邮件工具类
*/
public class MailUtil {
private MailUtil(){}
/**
* 发送邮件
* 参数一:发送邮件给谁
* 参数二:发送邮件的内容
*/
public static void sendMail(String toEmail, String emailMsg) throws Exception {
//1_创建Java程序与163邮件服务器的连接对象
Properties props = new Properties();
props.put("mail.smtp.host", "localhost");
props.put("mail.smtp.auth", "true");
Authenticator auth = new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("service", "123456");
}
};
Session session = Session.getInstance(props, auth);
//2_创建一封邮件
Message message = new MimeMessage(session);
//设置发件人地址
message.setFrom(new InternetAddress("service@itheima104.com"));
//设置收件人地址
message.setRecipient(RecipientType.TO, new InternetAddress(toEmail));
//设置邮件的主题
message.setSubject("用户激活");
//设置邮件的内容
message.setContent(emailMsg, "text/html;charset=UTF-8");
//3_发送邮件
Transport.send(message);
}
}

Jsp

JSP入门

JSP概述

什么是JSP

​ Java server page(java服务器页面). JSP本质就是Servlet

​ 它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术。

​ JSP=html(js,css)+java(servlet)+jsp(内置对象、指令、动作标签等等)特有的内容

JSP产生的原因

需求: 我们要向页面动态输出一个表格. 发现特别的繁琐

servlet在展示页面的时候,相当的繁琐。sun公司为了解决这个问题,参照asp开发了一套动态网页技术jsp。

JSP执行原理

JSP会翻译(通过默认的JspServlet,JSP引擎)成Servlet(.java),Servlet编译成class文件

​ JSP执行流程

​ 第一次访问的xxx.jsp时候,服务器收到请求,JspServlet会去查找对应的jsp文件

​ 找到之后,服务器会将这个jsp文件转换成java文件(Servlet)

​ 服务器编译java文件,生成class文件

​ 服务器运行class文件,生成动态的内容

​ 服务器收到内容之后,返回给浏览器

image-20230921124855962

小结

  1. JSP: java 服务器 页面, sun公司定义的动态资源, 本质就是Servlet
  2. JSP产生的原因: Servlet在动态展示很麻烦, jsp展示方便一点

JSP基本语法

JSP脚本

我们可以通过JSP脚本在JSP页面上编写Java代码. 一共有三种方式:

类型 翻译成Servlet对应的部分 注意
<%…%>:Java程序片段 翻译成Service()方法里面的内容, 局部的
<%=…%>:输出表达式 翻译成Service()方法里面的内容,相当于调用out.print() 输出表达式不能以;结尾
<%!…%>:声明成员变量(肯定不用) 翻译成Servlet类里面的内容
  • eg
1
2
3
4
5
6
7
8
<%
for(int i = 0; i < 10;i++){
out.print("i="+i);
%>
<hr/>
<%
}
%>

JSP注释

注释类型
HTML注释
JAVA注释 //; /* */
JSP注释; <%–注释内容–%>

注释快捷键:Ctrl+Shift+/

小结

  1. 脚本
    • <%%> 翻译成了service()方法里面的局部内容
    • <%=%> 输出, 翻译成了service()方法里面的out.print()
    • <%!%> 翻译成了servlet类里面的全局内容
  2. 注释
    • 注释快捷键:Ctrl+Shift+/

登录案例的优化

优化登录失败的效果

LoginServlet的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.nbchen.servlet;

import com.nbchen.bean.User;
import com.nbchen.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.SQLException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//0. 解决乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//1. 获取客户端提交的用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");

// 获取客户端输入的验证码
String checkCode = request.getParameter("checkCode");

// 获取服务器生成的验证码
//从session中获取服务器端生成的验证码
HttpSession session = request.getSession();
String code = (String) session.getAttribute("code");
//校验验证码
if (code.equalsIgnoreCase(checkCode)) {
//验证码正确,当验证码正确了,才进行用户名和密码的校验
//2. 使用DBUtils执行SQL语句校验用户名和密码
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql = "select * from user where username=? and password=?";
try {
User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);

if (user != null) {
//登录成功
response.getWriter().write("登录成功");
}else {
//登录失败:用户名或密码错误
String errorMsg = "用户名或密码错误";
//将errorMsg字符串存储到哪个域对象??? request
request.setAttribute("errorMsg",errorMsg);

//优化登录:跳转回到登录页面,并且进行错误信息的提示
//跳转方式有两种:1. 重定向 2. 请求转发

request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (SQLException e) {
e.printStackTrace();
//登录失败: 登录失败
//登录失败:验证码错误
String errorMsg = "登录失败";
//将errorMsg存储到request域对象
request.setAttribute("errorMsg",errorMsg);
//跳转到login.jsp
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}else {
//登录失败:验证码错误
String errorMsg = "验证码错误";
//将errorMsg存储到request域对象
request.setAttribute("errorMsg",errorMsg);
//跳转到login.jsp
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}

login.jsp的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/8/26
Time: 15:15
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<center>
<%
//从request域对象中取出errorMsg
String errorMsg = (String) request.getAttribute("errorMsg");
if (errorMsg == null) {
errorMsg = "";
}
%>
<h1>用户登录</h1>
<span style="color: red"><%=errorMsg%></span>
<form action="login" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
验证码:<input type="text" name="checkCode"><br>
<img src="checkCode" id="code"><a href="javascript:;" onclick="changeCheckCode()">换一换</a><br>
<input type="submit" value="登录"/>
</form>

<script>
//实现点击换一换,切换验证码图片
function changeCheckCode() {
//具体怎么去切换验证码呢? 也就是重新设置img标签的src
//如果直接设置src为"checkCode"的话,那么浏览器就会从缓存中获取验证码图片,所以我们要让浏览器避开缓存
//浏览器如果发现每次请求的路径不一样,就不会找缓存
document.getElementById("code").setAttribute("src","checkCode?date="+new Date())
}
</script>
</center>
</body>
</html>

优化登录成功的效果

LoginServlet的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//1. 解决乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

//2. 获取请求参数username和password
String username = request.getParameter("username");
String password = request.getParameter("password");
//获取浏览器传入的验证码(用户输入的验证码)
String checkCode = request.getParameter("checkCode");

//获取服务器生成的验证码,从session里面根据key "code"取出
HttpSession session = request.getSession();
String code = (String) session.getAttribute("code");
//3. 校验验证码
if (code.equalsIgnoreCase(checkCode)) {
//验证码校验通过
//进行用户名和密码的校验
//3. 连接数据库校验用户名和密码,也就是执行查询的SQL语句
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql = "select * from user where username=? and password=?";
//执行查询,查询一条数据,封装到User中
User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);
//判断是否登录成功
if (user != null) {
//登录成功
//将user存储到session中
session.setAttribute("user",user);
//跳转到成功页面success.jsp
response.sendRedirect("/userDemo/success.jsp");
}else {
//往request域对象中存放"用户名或密码错误"
request.setAttribute("msg","用户名或密码错误");
//请求转发跳转到login.jsp页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}else {
//往request域对象中存放"验证码错误"
request.setAttribute("msg","验证码错误");
//请求转发跳转到login.jsp页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
//往request域对象中存放"服务器异常请稍后再试"
request.setAttribute("msg","服务器异常请稍后再试");
//请求转发跳转到login.jsp页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}

success.jsp的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<%@ page import="com.itheima.pojo.User" %><%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/14
Time: 16:02
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>成功页面</title>
</head>
<body>
<%
//从session域对象中取出user
User user = (User) session.getAttribute("user");
//取出nickname
String nickname = null;
if (user != null) {
nickname = user.getNickname();
}
%>
<h1>欢迎回来,<%=nickname%></h1>
</body>
</html>

案例-记住用户名案例

需求

image-20230921124911463

分析

  1. 在LoginServlet里面, 如果用户登录成功:

    ​ //判断用户是否勾选了记住用户名

    ​ //勾选了, 把用户名存到Cookie

    ​ //未勾选则清除已经保存的用户名

  2. 在login.jsp页面 从cookie取出展示

实现

  • LoginServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.nbchen.servlet;

import com.nbchen.pojo.User;
import com.nbchen.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;


@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//1. 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");

//获取是否记住用户名
String remember = request.getParameter("remember");
//判断是否记住用户名
Cookie cookie = new Cookie("username",username);
cookie.setPath(request.getContextPath());
if (remember != null) {
//表示勾上了,那么就记住用户名: 将用户名存储到cookie中,发送给客户端
cookie.setMaxAge(7*24*60*60);
}else {
//表示没有勾上,就要清除之前保存在cookie中的username
cookie.setMaxAge(0);
}
response.addCookie(cookie);

//获取验证码: 用户输入的验证码
String checkCode = request.getParameter("checkCode");
//从session中获取服务器生成的验证码
HttpSession session = request.getSession();
String code = (String) session.getAttribute("code");

//校验验证码: 使用用户输入的验证码和服务器生成的验证码进行校验
if (code.equalsIgnoreCase(checkCode)) {
//验证码正确: 才会去校验用户名和密码
//2. 连接数据库校验用户名和密码
String sql = "select * from user where username=? and password=?";
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
try {
User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);
if (user != null) {
//登录成功
//将当前用户的信息存起来:session
session.setAttribute("user",user);
//登录成功之后,要跳转到success.jsp页面,并且显示"欢迎XXX登录"
request.getRequestDispatcher("success.jsp").forward(request, response);
}else {
//登录失败
String errorMsg = "用户名或密码错误";
//将errorMsg存储到域对象中
request.setAttribute("errorMsg",errorMsg);
//跳转回到登录页面
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
String errorMsg = "用户名或密码错误";
request.setAttribute("errorMsg",errorMsg);
request.getRequestDispatcher("login.jsp").forward(request, response);
//response.getWriter().write("用户名或密码错误");
}
}else {
//验证码错误
String errorMsg = "验证码错误";
request.setAttribute("errorMsg",errorMsg);
request.getRequestDispatcher("login.jsp").forward(request, response);
//response.getWriter().write("验证码错误");
}
}
}
  • login.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/10/15
Time: 14:35
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<%
//从request域对象中取出errorMsg的值,并且显示在页面上
String errorMsg = (String) request.getAttribute("errorMsg");
if (errorMsg == null) {
errorMsg = "";
}

String username = "";
//获取名为username的cookie的值
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("username")) {
username = cookie.getValue();
}
}
}
%>
<div style="color: red"><%=errorMsg%></div>
<form action="login" method="post">

<%--将变量username的值显示到用户名输入框--%>
用户名<input type="text" name="username" value="<%=username%>"><br>
密码<input type="password" name="password"><br>
验证码<input type="text" name="checkCode"><br>
<img id="checkCode" src="checkCode"/>
<a href="javascript:;" onclick="changeCheckCode()">换一换</a>
<br>
<input type="checkbox" name="remember">记住用户名<br>
<input type="submit" value="提交">
</form>
<script>
//声明一个方法,切换验证码图片
function changeCheckCode() {
//怎么切换验证码图片呢? 再一次请求验证码图片路径,也就是重新设置img标签的src
//因为我们的访问路径没有改变,所以服务器认为我们发送的是同一次请求,所以服务器会返回一个状态码304,所以我们会从缓存中获取数据
//如果每次访问的url路径有变化,就绝不会从缓存中获取数据
document.getElementById("checkCode").setAttribute("src","checkCode?a="+new Date())
}
</script>
</body>
</html>

小结

  1. 用户勾选了记住用户名,我们把用户名存到Cookie里面
  2. 在login.jsp里面 从cookie取出用户名展示

EL表达式

EL表达式概述

什么是El表达式

​ Expression Language: 表达式语言, jsp2.0之后内置在jsp里面

​ 目的:为了使JSP写起来更加简单, 取值(取的域对象里面存的值)更加简单。(代替脚本 <% %>)

EL语法

1
${el表达式}

EL表达式的用途

​ 1.获取数据. 获取的是域(request,session,ServletContext)对象中存储的数据

​ 2.EL执行运算

  1. 使用EL表达式获取cookie中的数据

小结

  1. EL表达式:表达式语言
  2. 语法:${el表达式}
  3. 作用:1. 获取域对象里面的数据 2. 执行运算

El获取数据

获取简单数据类型数据

​ 语法:${requestScope|sessionScope|applicationScope.属性名};

​ 快捷写法:${属性名}, 属性名就是存在域对象里面的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 8:52
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>使用EL表达式获取域对象中的简单类型的数据</title>
</head>
<body>
<%
//往域对象存值
application.setAttribute("msg","applicationValue");
//session.setAttribute("msg","sessionValue");
//request.setAttribute("msg","requestValue");

//在我们开发过程中,是否会往多个域对象中存放同一个key??? 这是不会的
//所以我们用el表达式获取域对象里面的数据,还可以简化成${key} 获取范围最小的域对象中的这个key对应的值
%>
获取的application域对象中的msg=${applicationScope.msg}<br>
获取session域对象中的msg=${sessionScope.msg}<br>
获取request域对象中的msg=${requestScope.msg}<br>
获取存放在域对象中的msg=${msg}
</body>
</html>

获取数组

​ 语法: ${key[下标]} key就是域对象里面存的key

获取list

语法:${list属性名[index]}或者${list属性名.get(index)};list属性名就是存入域对象里面的key

获取Map

语法:${map属性名.键}或者${map属性名.get("键")},map属性名就是存入域对象里面的key

获取bean

语法:${key.javabean属性}

​ 依赖getxxx()方法; eg: getPassword()—去掉get–>Password()—-首字母小写—>password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Map" %>
<%@ page import="com.itheima.pojo.User" %><%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 9:05
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>使用EL表达式获取存储在域对象中的复杂类型的数据</title>
</head>
<body>
<%
//往域对象中存放数组类型的数据
String[] arr = {"张三","李四","王五","赵六","田七","狗娃"};
request.setAttribute("arr", arr);

//往域对象中存放一个集合
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("田七");
request.setAttribute("list", list);

//往域对象中存放一个map
Map<String,String> map = new HashMap<String,String>();
map.put("name", "张三");
map.put("password", "123456");
map.put("nickname", "张三丰");
request.setAttribute("map", map);

//往域对象中存放一个pojo对象
User user = new User(1, "jay", "台湾省");
request.setAttribute("user",user);
%>
获取存放在request域对象中的数组中的第三个元素:${arr[2]}<br>
<%--
在el表达式中,只要是根据下标获取元素,都可以写[index]
--%>
获取存放在request域对象中的集合中的第四个元素:${list[3]}<br>

<%--
在el表达式中,只要是根据对应的属性的get方法去获取数据,都可以写成".属性名" 或者 ["属性名"]
--%>
获取存储在request域对象中的map中的nickname:${map.nickname}<br>

获取存放在request域对象中的user的address属性的值:${user.address}
</body>
</html>

小结

语法

  1. 获得简单类型的
1
${key}
  1. 获得数组类型的
1
${key[下标]}
  1. 获得List类型的
1
${key.get(index)} 或者${key[index]}
  1. 获得Map类型的
1
${key.get(键)} 或者${key.键} key是存到域对象里面的key
  1. 获得JavaBean类型的
1
${key.javaBean属性}

EL执行运算

image-20230921124930686

算数运算

+,-,*,/

  • +不能拼接字符串,如果+两端是字符串,那么会将字符串转换成数字之后再进行加法运算,如果+两端的字符串无法转换成数字,则会报错

逻辑运算

< >= <= != ==

关系运算

&& || !

非空判断【重点】

​ empty,1. 判断一个对象是否为null, 2. 判断集合长度是否为0, 3. 判断一个字符串是否为空字符串

​ not empty

​ 语法: ${empyt 属性名};属性名 就是域对象里面的key值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page import="com.itheima.pojo.User" %><%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 9:35
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>empty 运算符的介绍</title>
</head>
<body>
<%
//el表达式中的empty可以判断一个字符串是否为空字符串,一个对象是否为null,一个集合的长度是否为0
List<String> list = new ArrayList<String>();
list.add("张三丰");
request.setAttribute("list", list);

request.setAttribute("msg","requestValue");

User user = new User();
request.setAttribute("u",user);
%>
判断域对象中的list集合的长度是否为0: ${empty list}<br>
判断域对象中的msg字符串是否为空字符串: ${empty msg}<br>
判断域对象中的user是否为null : ${empty u}<br>
</body>
</html>

小结

  1. 注意的地方: +只能做加法运算,不能拼接字符串

  2. empty【重点】

    • 语法
      • ${empty key}
      • ${not empty key}
  • 作用
    • 判断一个对象是否为null
    • 判断一个集合长度是否为0
    • 判断一个字符串是否是””
    • 注意
      • 如果是集合, 集合为null 是empty
      • 如果是集合, 集合不为null 但是长度为0 还是empty

使用EL表达式获取存放在cookie中的数据(扩展)

​ 语法:${cookie.cookie的name.value}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>使用el表达式获取存储在cookie中的数据</title>
</head>
<body>
<%--
jsp里面是内置session对象,有了session对象,那么浏览器就会携带一个名为"JSESSIONID"的cookie
我们的目标就是获取"JSESSIONID"的值
--%>
<%
Cookie[] cookies = request.getCookies();
String cookieValue = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("JSESSIONID")) {
cookieValue = cookie.getValue();
}
}
}
%>
使用原始方式获取的JSESSIONID的值为: <%=cookieValue%><br>

<%--
${cookie}表示获取这次请求中的所有cookie对象
${cookie.JSESSIONID}表示获取名为"JSESSIONID"的cookie对象
${cookie.JSESSIONID.value}表示获取名为"JSESSIONID"的cookie对象的value
--%>
使用EL表达式获取JSESSIONID的值为: ${cookie.JSESSIONID.value}
</body>
</html>

JSTL标签库

JSTL标签库概述

什么是JSTL标签库

​ JSTL(JSP Standard Tag Library,JSP标准标签库)是一个不断完善的开放源代码的JSP标签库,是由apache的jakarta小组来维护的。这个JSTL标签库没有集成到JSP的, 要使用的话, 需要导jar包.

JSTL标签库的作用

​ 为了简化在jsp页面上操作数据; eg: 遍历数据 判断数据等

JSTL标签库的类别

image-20230921124939615

JSTL核心标签库

核心标签库使用步骤

image-20230921125727248

  1. 导入jar包

  2. 在JSP页面上导入核心标签库<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

if标签

image-20230921124946017

  • 语法
1
2
<c:if test="el表达式${..}">
</c:if>
  • 实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/8/27
Time: 10:10
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>使用jstl中的if标签进行判断</title>
</head>
<body>
<%
//往域对象中存储一个age
request.setAttribute("age",17);

//目标:判断age的值,如果大于等于18,则在浏览器页面上输出"已成年",否则输出"未成年"
//jstl的使用步骤:1. 导入jar包 2. 在jsp页面通过taglib指令引入核心标签库 3. 使用标签
%>
<%--
if标签有一个属性叫做test,它表示判断表达式,需要结合el一起使用
如果要表示相反的判断,则再添加一个if标签,然后写相反的条件就行

if标签还有一个属性叫做var,表示将判断结果存储进域对象时候的key(了解)

if标签的第三个属性叫做scope,表示将判断结果存储进哪个域对象(了解)
--%>
<c:if test="${age >= 18}" var="flag" scope="request">
已成年
</c:if>

<c:if test="${age < 18}">
未成年
</c:if>
<br>
${flag}
</body>
</html>
  • 小结

    • 语法
    1
    <c:if test="${} "></c:if>
    • 特点
      • 如果test里面的是true, if标签体里面的就会执行
      • 如果test里面的是false, if标签体里面的就不会执行
      • 没有else的

choose标签

  • 实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 9:59
To change this template use File | Settings | File Templates.
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>jstl中的choose标签的使用介绍</title>
</head>
<body>
<%
request.setAttribute("course","PHP");
%>
<c:choose>
<%--
一个when标签表示一个条件
--%>
<c:when test="${course == 'Java'}">
学习Java
</c:when>

<c:when test="${course == 'Android'}">
学习Android
</c:when>

<c:when test="${course == 'C++'}">
学习C++
</c:when>

<c:otherwise>
学习,学个屁!!!
</c:otherwise>
</c:choose>
</body>
</html>

foreach标签

image-20230921125001020

  • 简单的使用:

1
2
3
4
5
6
7
8
9
10
11
12
 <%--
jstl中的forEach标签是用来代替for循环语句

目标1: 在浏览器上显示0-9的数字
begin属性: 从哪个下标开始遍历, 如果不写默认是从0开始
end属性: 到哪个下标结束遍历,如果不写默认是遍历到集合/数组的最后一个元素
step属性: 表示遍历时候的步长,默认步长是1
var属性: 表示将遍历的结果存放进域对象时候的key
--%>
<c:forEach begin="0" end="9" step="1" var="i">
${i}
</c:forEach><br>

  • 复杂的使用遍历集合:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <%
    //往域对象存储一个集合
    List<String> list = new ArrayList<String>();
    list.add("张三");
    list.add("李四");
    list.add("王五");
    list.add("赵六");
    list.add("田七");
    request.setAttribute("list", list);
    %>

    <c:forEach begin="0" end="9" step="1" var="i">
    ${i}
    </c:forEach><br>

    <%--
    通过items属性指定遍历域对象里面的list
    通过var属性指定遍历出来的每个数据存储到域对象时候的key
    --%>
    <c:forEach items="${list}" var="username">
    ${username}
    </c:forEach>
  • c:forEach中的varStatus属性。

    指向一个字符串,该字符串引用一个对象。  map.put(“vs”,一个对象);

        这个对象记录着当前遍历的元素的一些信息:

           index:返回索引。从0开始

          count:返回计数。从1开始

            last:是否是最后一个元素

             first:是否是第一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 10:15
To change this template use File | Settings | File Templates.
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>forEach 标签的varStatus属性的介绍</title>
</head>
<body>
<%
//往域对象存储一个集合
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("田七");
request.setAttribute("list", list);
%>

<%--
forEach标签的varStatus属性:指定将遍历出来的每一个元素的状态存储进域对象时候的key
遍历出来的每一个元素都有一些状态(属性),比如:
下标 index:
计数 count:
当前元素的值 current:
是否是第一个元素:
是否是最后一个元素
--%>
<table border="1" cellspacing="0" width="700" align="center">
<tr>
<th>下标</th>
<th>计数</th>
<th>姓名</th>
<th>是否是第一个元素</th>
<th>是否是最后一个元素</th>
</tr>
<c:forEach items="${list}" varStatus="vst">
<tr>
<td>${vst.index}</td>
<td>${vst.count}</td>
<td>${vst.current}</td>
<td>${vst.first}</td>
<td>${vst.last}</td>
</tr>
</c:forEach>
</table>
</body>
</html>

小结

  1. foreach标签
  • 简单使用
1
2
3
<c:foreach begin="从哪里开始" end="到哪里结束" var="每次遍历的赋值变量" step="步长">
//每遍历一次 foreach里面就执行一次
</c:foreach>
  • 复杂使用
1
2
3
<c:foreach items="使用el从域对象里面取出集合" var="每次遍历的赋值变量" varStatus="遍历的状态">
//每遍历一次 foreach里面就执行一次
</c:foreach>

回顾el和JSTL的内容

el

  1. el的语法: ${el表达式}
  2. el的作用:
    1. 获取域对象中的数据,${key}
    2. 执行运算,el表达式中可以使用运算符:+-*/><与或非等等运算符都能使用,它还有一个特殊的运算符叫做empty
    3. 获取cookie中的数据: ${cookie.cookie的名字.value}

jstl

  1. jstl的使用步骤:
    1. 导入jar包
    2. 在要使用jstl的jsp页面通过tablib指令引入核心标签库
    3. 使用jstl的核心标签库中的标签
  2. jstl的常用标签:
    1. if
    2. choose
    3. forEach
  3. if标签
    1. 作用: 代替jsp页面中的if语句
    2. 属性:
      1. test(必须的属性): 结合el表达式编写判断条件
      2. var: 将判断结果存储进域对象时候的key
      3. scope:将判断条件存储进哪个域对象
  4. choose标签:
    1. 作用:代替多条件判断
    2. 它需要和when标签以及otherwise标签一起使用
  5. forEach标签
    1. 作用: 代替jsp页面的for循环语句
    2. 属性:
      1. begin 开始遍历的下标,如果没写,则从0开始遍历
      2. end 结束遍历的下标,如果没写,则遍历到数组或者集合的最后一个元素
      3. step 遍历的步长,如果没写就是1
      4. var 遍历出来的每一个数据存储进域对象时候的key
      5. varStatus 遍历出来的每一个数据的状态
        1. index 下标
        2. count 序号
        3. first 是否是第一个元素
        4. last 是否是最后一个元素
        5. current 遍历出来的当前元素

综合案例和开发模式

案例-完成转账的案例v1

需求

  • 当单击提交按钮,付款方向收款方安照输入的金额转账。

image-20230921125017578

分析

image-20230921125027358

实现

案例的准备工作
  • 数据库的准备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    create database day29;
    use day29;
    create table account(
    id int primary key auto_increment,
    name varchar(20),
    money double
    );

    insert into account values (null,'jay',1000);
    insert into account values (null,'aobama',1000);
    insert into account values (null,'ww',1000);
  • 页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>

    <body>
    <form method="post" action="">
    <table border="1px" width="500px" align="center">
    <tr>
    <td>付款方</td>
    <td><input type="text" name="from"></td>
    </tr>
    <tr>
    <td>收款方</td>
    <td><input type="text" name="to"></td>
    </tr>
    <tr>
    <td>金额</td>
    <td><input type="text" name="money"></td>
    </tr>
    <tr>
    <td colspan="2"><input type="submit"></td>
    </tr>
    </table>
    </form>

    </body>
    </html>

  • jar包

  • 工具类

  • 配置文件

代码实现
  • AccountServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.itheima.web.servlet;

import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 包名:${PACKAGE_NAME}
*
* @author Leevi
* 日期2020-07-15 10:55
*/
@WebServlet("/account")
public class AccountServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//1. 获取请求参数
String fromName = request.getParameter("from");
String toName = request.getParameter("to");
Double money = Double.valueOf(request.getParameter("money"));

//2. 执行转账的SQL语句
//2.1 转出账户扣款
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql1 = "update account set money=money-? where name=?";

try {
queryRunner.update(sql1,money,fromName);

//2.2 转入账户收款
String sql2 = "update account set money=money+? where name=?";
queryRunner.update(sql2,money,toName);

//3. 转账成功
response.getWriter().write("转账成功!!!");
} catch (Exception e) {
e.printStackTrace();
//转账失败
response.getWriter().write("转账失败!!!");
}
}
}

小结

  1. 转账: 一个用户的钱减少, 一个用户的钱增加

开发模式

  • 理解模式二(MVC)和模式三(三层架构)

JSP的开发模式一【了解】

 javaBean:实体类。特点:私有化的属性、公共的getter setter方法、无参的构造。

image-20230921125042327

JSP的开发模式二

​ JSP + Servlet + JavaBean 称为MVC的开发模式.

MVC:开发模式

​ M:model 模型 (javaBean:封装数据)

​ V:View 视图  (JSP:展示数据)

​ C:controller 控制器 (Servlet:处理逻辑代码,做为控制器)

image-20230921125048746

模式三: 三层架构

  • 软件中分层:按照不同功能分为不同层,通常分为三层:表现层(web层),业务层,持久(数据库)层。

    image-20230921125101488

  • 不同层次包名的命名

分层 包名(公司域名倒写)
表现层(web层) com.itheima.web
业务层(service层) com.itheima.service
持久层(数据库访问层) com.itheima.dao
JavaBean com.itheima.bean
工具类 com.itheima.utils
  • 分层的意义:
    1. 解耦:降低层与层之间的耦合性。 (以后面向接口编程)
    2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
    3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。
    4. 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用。

image-20230921125106928

小结

  1. 模式一: JSP+JavaBean【了解】

  2. 模式二: MVC

    • M model JavaBean
    • V View JSP
    • C Controller Servlet
  3. 三层架构

    • WEB层
      • 获得请求参数
      • 调用业务
      • 响应
    • 业务层
      • 处理业务
      • 调用Dao
    • 持久层
      • 操作数据库
    • 三层架构中的包名:
      • 表现层: web
      • 业务层: service
      • 数据访问层/持久层: dao

案例-完成转账的案例v2

使用三层架构改写转账案例

分析

image-20230921125249342

实现

  • UserServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.itheima.web.servlet;

import com.itheima.service.AccountService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 包名:${PACKAGE_NAME}
*
* @author Leevi
* 日期2020-07-15 10:55
*/
@WebServlet("/account")
public class AccountServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//1. 获取请求参数
String fromName = request.getParameter("from");
String toName = request.getParameter("to");
Double money = Double.valueOf(request.getParameter("money"));

try {
//2. 调用业务层的AccountService的方法处理请求执行转账
AccountService accountService = new AccountService();
accountService.transfer(fromName,toName,money);

//3. 转账成功
response.getWriter().write("转账成功!!!");
} catch (Exception e) {
e.printStackTrace();
//转账失败
response.getWriter().write("转账失败!!!");
}
}
}
  • AccountService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itheima.service;

import com.itheima.dao.AccountDao;

import java.sql.SQLException;

/**
* 包名:com.itheima.service
*
* @author Leevi
* 日期2020-07-15 11:19
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
public void transfer(String fromName,String toName,Double money) throws SQLException {
//2.1 调用AccountDao的方法进行转出账户扣款
accountDao.updateAccount(fromName,-money);
//2.2 调用AccountDao的方法进行转入账户收款
accountDao.updateAccount(toName,money);
}
}
  • UserDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itheima.dao;

import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;

import java.sql.SQLException;

/**
* 包名:com.itheima.dao
*
* @author Leevi
* 日期2020-07-15 11:19
*/
public class AccountDao {
private QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
/**
* 修改账户信息
*/
public void updateAccount(String name,Double money) throws SQLException {
String sql = "update account set money=money+? where name=?";
queryRunner.update(sql,money,name);
}
}

小结

  • WEB层 com.itheima.web
    • 获得请求参数
    • 调用业务
    • 响应
  • 业务层 com.itheima.service xxService
    • 处理业务
    • 调用Dao
  • 持久层 com.itheima.dao xxDao
    • 操作数据库

案例-完成转账的案例v3

需求:当单击提交按钮,付款方向收款方安照输入的金额转账。 使用手动事务进行控制

DBUtils实现事务管理

API 说明
QueryRunner() 创建QueryRunner对象. 手动提交事务时使用
query(connection,String sql, Object[] params, ResultSetHandler<T> rsh) 查询(需要传入Connection)
update(connection,String sql, Object… params) 更新

思路

image-20230921125616460

实现

  • UserService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.itheima.service;

import com.itheima.dao.AccountDao;
import com.itheima.utils.DruidUtil;

import java.sql.Connection;
import java.sql.SQLException;

/**
* 包名:com.itheima.service
*
* @author Leevi
* 日期2020-07-15 11:19
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
public void transfer(String fromName,String toName,Double money) {
Connection conn = null;
try {
//逻辑操作开始之前开启事务: connection.setAutoCommit(false)
conn = DruidUtil.getDataSource().getConnection();
conn.setAutoCommit(false);

//2.1 调用AccountDao的方法进行转出账户扣款
accountDao.updateAccount(conn,fromName, -money);

//模拟转账过程中出现异常
int num = 10 / 0;

//2.2 调用AccountDao的方法进行转入账户收款
accountDao.updateAccount(conn,toName, money);

//逻辑操作执行完毕没有异常,提交事务: connection.commit()
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//逻辑操作执行过程中遇到异常,则在catch里面回滚事务: connection.rollback()
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
throw new RuntimeException("转账失败");
}
}
}
  • UserDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.nbchen.dao;

import com.nbchen.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;

import java.sql.Connection;
import java.sql.SQLException;

/**
* 包名:com.nbchen.dao

*/
public class AccountDao {
private QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
/**
* 修改账户信息
* 指定使用某个连接Connection执行SQL语句
*/
public void updateAccount(Connection connection,String name, Double money) throws SQLException {
String sql = "update account set money=money+? where name=?";
queryRunner.update(connection,sql,money,name);
}
}

小结

  1. 思想1: service层将异常try起来了,怎么才能让servlet还能够获取异常呢?

    1
    在catch里面抛运行时异常
  2. 思想2: 如果在service和dao共享一个Connection对象

    1
    通过调用方法的时候将connection作为参数传递给Dao
  3. 技术点1 : 怎么开启、提交、回滚事务

    1
    2
    3
    4
    connection.setAutoCommit(false)开启事务
    connection.commit()提交事务
    connection.rollback()回滚事务
    注意: 开启事务的连接和执行SQL语句的连接要是同一个
  4. 技术点2 : 在使用DBUtils执行SQL语句的时候,怎么才能指定使用哪个连接呢?

    1
    调用queryRunner对象的update或者query方法的时候,可以传入connection

案例-完成转账的案例v4

  • 当单击提交按钮,付款方向收款方安照输入的金额转账。 使用事务进行控制

    image-20230921125438942

ThreadLocal

​ 在“事务传递参数版”中,我们必须修改方法的参数个数,传递链接,才可以完成整个事务操作。如果不传递参数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。

​ java.lang.ThreadLocal,该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是一个Map,key存放的当前线程,value存放需要共享的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.itheima;

/**
* 包名:com.itheima
*
* @author Leevi
* 日期2020-07-15 14:58
* ThreadLocal是线程本地变量,它的作用是用于在保证同一个线程中的对象使用的是同一份数据
*/
public class TestMain {
public static void main(String[] args) {
//ThreadLocal的使用
ThreadLocal<String> threadLocal = new ThreadLocal<>();
//在主线程中往ThreadLocal对象中存储一个字符串"jay"
threadLocal.set("jay");
//在主线称重往ThreadLocal中存入一个字符串"aobama"
threadLocal.set("aobama");

//新线程
new Thread(new Runnable() {
@Override
public void run() {
//在新线程中,往ThreadLocal中存入"jay"
threadLocal.set("jay");

//在新线程中获取ThreadLocal中的值
String s = threadLocal.get();
System.out.println("在新线程中获取ThreadLocal中的值为:" + s);
}
}).start();


//在主线程中取出ThreadLocal中的值
String str = threadLocal.get();
System.out.println("在主线程中获取ThreadLocal对象中的值:" + str);
}
}

思路

image-20230921125649005

代码

  • DruidUtil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.itheima.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
* 包名:com.itheima.utils
*
* @author Leevi
* 日期2020-07-06 11:45
*/
public class DruidUtil {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private static DataSource dataSource;
static {
try {
//1. 创建Properties对象
Properties properties = new Properties();
//2. 将配置文件转换成字节输入流
InputStream is = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
//3. 使用properties对象加载is
properties.load(is);
//druid底层是使用的工厂设计模式,去加载配置文件,创建DruidDataSource对象
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource;
}

/**
* 让该方法获取的连接对象是同一个对象
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection connection = threadLocal.get();
if (connection == null) {
//说明threadLocal中还没有存储连接
connection = dataSource.getConnection();
//将连接存储到threadLocal中
threadLocal.set(connection);
}
return connection;
}

public static void clear() throws SQLException {
//先将conn的autoCommit属性设置回true
threadLocal.get().setAutoCommit(true);
threadLocal.get().close();
//将conn从ThreadLocal中移除
threadLocal.remove();
}
}
  • AccountService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.itheima.service;

import com.itheima.dao.AccountDao;
import com.itheima.utils.DruidUtil;

import java.sql.Connection;
import java.sql.SQLException;

/**
* 包名:com.itheima.service
* @author Leevi
* 日期2020-08-27 11:50
* 业务层的类:处理请求(执行具体的业务逻辑)
* 如果在处理请求的过程中,需要执行SQL语句,则调用dao层的方法执行SQL语句
*
* service层为什么要try异常: 为了在catch里面进行回滚操作
* service层又要把异常抛给servlet
*
* 怎么添加事务:
* 1. 开启事务: 业务逻辑开始之前,connection.setAutoCommit(false);
* 2. 提交事务: 业务逻辑执行完毕,没有出现异常,connection.commit()
* 3. 回滚事务: 业务逻辑执行过程中出现异常,connection.rollback()
*
* 注意点: 执行事务的connection对象和执行SQL语句的connection对象必须是同一个connection
*
* 进一步优化
* 1.使用ThreadLocal存储连接,实现业务层和持久层使用的是相同连接对象
* 2.
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
public void transfer(String fromName,String toName, Double money){
Connection conn = null;
try {
//开启事务
conn = DruidUtil.getConnection();
conn.setAutoCommit(false);

//1. 调用dao层的方法,执行付款方扣款
accountDao.updateAccount(fromName, -money);

//出现异常
int num = 10 / 0;

//2. 调用dao层的方法,执行收款方收款
accountDao.updateAccount(toName, money);

//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//回滚事务
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
throw new RuntimeException(e.getMessage());
}finally {
try {
//1. 设置连接的autoCommit为true 2. 将连接对象归还到连接池 3. 清除ThreadLocal中的连接对象
DruidUtil.clear();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
  • AccountDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.itheima.dao;

import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;

import java.sql.SQLException;

/**
* 包名:com.itheima.dao
* @author Leevi
* 日期2020-08-27 11:51
* 数据访问层的类:执行数据库的增删改查的SQL语句
*/
public class AccountDao {
private QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());

/**
* 根据账户的name,更新账户的money
* @param name
* @param money
* @throws SQLException
*/
public void updateAccount(String name, Double money) throws SQLException {
String sql = "update account set money=money+? where name=?";
queryRunner.update(DruidUtil.getConnection(),sql,money,name);
}
}

小结

  1. TheadLocal: jdk提供的一个对象. 只要是在同一个线程里面, 是可以共用的.
  2. 抽取了DruidUtil的getConnectionFromThreadLocal()方法, service和Dao里面的Connection都是从getConnectionFromThreadLocal()方法获取

补充案例: 显示所有用户

在list.jsp页面中显示user表中的所有用户的信息

image-20230921125451155

  1. 拷贝jar、配置文件、工具类
  2. 创建index.jsp和list.jsp
  3. 创建包结构、pojo类
  4. 创建ShowAllServlet、UserService、UserDao

index.jsp代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 12:08
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<a href="/showAll">查看所有用户信息</a>
</body>
</html>

ShowAllServlet代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.itheima.web.servlet;

import com.itheima.pojo.User;
import com.itheima.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
* 包名:${PACKAGE_NAME}
*
* @author Leevi
* 日期2020-07-15 12:08
*/
@WebServlet("/showAll")
public class ShowAllServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//1. 调用业务层的方法,处理查询所有用户的请求
UserService userService = new UserService();
List<User> userList = userService.findAll();

//2. 将user的集合存储到request域对象中
request.setAttribute("list",userList);

//3. 跳转到list.jsp页面
request.getRequestDispatcher("/list.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}

}
}

UserService代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima.service;

import com.itheima.dao.UserDao;
import com.itheima.pojo.User;

import java.sql.SQLException;
import java.util.List;

/**
* 包名:com.itheima.service
*
* @author Leevi
* 日期2020-07-15 12:09
*/
public class UserService {
private UserDao userDao = new UserDao();
public List<User> findAll() throws SQLException {
//调用dao对象的findAll()方法查询所有用户信息
List<User> userList = userDao.findAll();
return userList;
}
}

UserDao代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.itheima.dao;

import com.itheima.pojo.User;
import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

/**
* 包名:com.itheima.dao
*
* @author Leevi
* 日期2020-07-15 12:09
*/
public class UserDao {
private QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
public List<User> findAll() throws SQLException {
String sql = "select * from user";
List<User> userList = queryRunner.query(sql, new BeanListHandler<>(User.class));
return userList;
}
}

list.jsp代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<%--
Created by IntelliJ IDEA.
User: Fanyi Xiao
Date: 2020/7/15
Time: 12:08
To change this template use File | Settings | File Templates.
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>展示页面</title>
</head>
<body>
<table border="1" cellspacing="0" width="800px" align="center">
<tr>
<th>序号</th>
<th>用户名</th>
<th>密码</th>
<th>地址</th>
<th>昵称</th>
<th>性别</th>
<th>邮箱</th>
</tr>
<%--
遍历出域对象里面的list中的每一个user
--%>
<c:forEach items="${list}" var="user" varStatus="vst">
<tr>
<td>${vst.count}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<td>${user.address}</td>
<td>${user.nickname}</td>
<td>${user.gender}</td>
<td>${user.email}</td>
</tr>
</c:forEach>
</table>
</body>
</html>

注册登录案例改成三层架构

RegisterServlet代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.itheima.web.servlet;

import com.itheima.bean.User;
import com.itheima.service.UserService;
import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
* @author Leevi
* 日期2020-08-24 16:13
* 处理注册的Servlet
*/
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
private UserService userService = new UserService();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//0. 解决请求参数的中文乱码问题
request.setCharacterEncoding("UTF-8");
//0. 解决响应的中文乱码问题
response.setContentType("text/html;charset=utf-8");
//1. 获取所有请求参数
Map<String, String[]> map = request.getParameterMap();
//2. 将所有请求参数封装到user对象中
User user = new User();
try {
BeanUtils.populate(user,map);
//设置激活状态为0
user.setStatus("0");

//3. 调用业务层的方法,处理注册请求
userService.register(user);

//如果在这个过程中没有出现异常,则注册成功
//跳转到登录页面---->重定向跳转
response.sendRedirect("login.html");
} catch (Exception e) {
e.printStackTrace();
//如果在这个过程中出现了异常,则注册失败
response.getWriter().write("注册失败");
}
}
}

LoginServlet代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.itheima.web.servlet;
import com.itheima.bean.User;
import com.itheima.service.UserService;
import com.itheima.utils.CookieUtil;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
/**
* @author Leevi
* 日期2020-08-24 16:24
*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private UserService userService = new UserService();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//0. 解决乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//1. 获取客户端提交的用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");

// 获取客户端输入的验证码
String checkCode = request.getParameter("checkCode");

// 获取服务器生成的验证码
//从session中获取服务器端生成的验证码
HttpSession session = request.getSession();
String code = (String) session.getAttribute("code");

//获取是否记住用户名
String remember = request.getParameter("remember");
//校验验证码
if (code.equalsIgnoreCase(checkCode)) {
//验证码正确,当验证码正确了,才进行用户名和密码的校验
try {
//调用业务层的方法,校验登录(校验用户名和密码)
User user = userService.login(username, password);

if (user != null) {
//登录成功
//判断是否需要记住用户名
if (remember != null) {
//需要记住用户名: 将用户名存储到cookie中,并且传到浏览器保存
Cookie cookie = CookieUtil.createAndSetCookie("username", username, 7 * 24 * 60 * 60, request.getContextPath());
response.addCookie(cookie);
}else {
//说明不需要记住用户: 需要将之前保存在cookie中的用户名删除
//往浏览器存储一个同名、同路径但是maxAge为0 的cookie
Cookie cookie = CookieUtil.createAndSetCookie("username", username, 0, request.getContextPath());
response.addCookie(cookie);
}

//保存登录状态: 目的是在这次会话中访问任何页面都是已登录状态
//其实就是保存当前登录的user,保存在session域对象中
session.setAttribute("user",user);

//跳转到success.jsp页面: 重定向
response.sendRedirect("success.jsp");
}else {
//登录失败:用户名或密码错误
String errorMsg = "用户名或密码错误";
//将errorMsg字符串存储到哪个域对象??? request
request.setAttribute("errorMsg",errorMsg);

//优化登录:跳转回到登录页面,并且进行错误信息的提示
//跳转方式有两种:1. 重定向 2. 请求转发

request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
//登录失败: 登录失败
//登录失败:验证码错误
String errorMsg = "登录失败";
//将errorMsg存储到request域对象
request.setAttribute("errorMsg",errorMsg);
//跳转到login.jsp
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}else {
//登录失败:验证码错误
String errorMsg = "验证码错误";
//将errorMsg存储到request域对象
request.setAttribute("errorMsg",errorMsg);
//跳转到login.jsp
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}

UserService代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima.service;

import com.itheima.bean.User;
import com.itheima.dao.UserDao;

/**
* 包名:com.itheima.service
* @author Leevi
* 日期2020-08-27 12:09
*/
public class UserService {
private UserDao userDao = new UserDao();
public void register(User user) throws Exception {
//调用dao层的方法,将用户信息存储到数据库
userDao.saveUser(user);
}

public User login(String username,String password) throws Exception {
//调用dao层的方法校验用户名和密码
return userDao.findUser(username,password);
}
}

UserDao代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.itheima.dao;

import com.itheima.bean.User;
import com.itheima.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import java.sql.SQLException;

/**
* 包名:com.itheima.dao
* @author Leevi
* 日期2020-08-27 12:09
*/
public class UserDao {
/**
* 执行添加用户的SQL语句的方法
* @param user
* @throws SQLException
*/
public void saveUser(User user) throws Exception {
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql = "insert into user values (null,?,?,?,?,?,?,?)";
queryRunner.update(sql,user.getUsername(),user.getPassword(),user.getAddress(),user.getNickname(),user.getGender(),user.getEmail(),user.getStatus());
}

/**
* 根据username和password执行查询用户信息
* @param username
* @param password
* @return
* @throws SQLException
*/
public User findUser(String username,String password) throws Exception {
QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
String sql = "select * from user where username=? and password=?";
User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);
return user;
}
}