第一章 MySQL

一 、数据库

1.什么是数据库?

所谓的数据库就是指存储管理数据的仓库

2.数据库有哪些分类?

3.什么是关系型数据库?

底层以二维表的形式保存数据的库就是关系型数据库

stu-学生表

4.常见的关系型数据库有哪些?

  • Sql Server:微软提供,收费,适用于一些中型或大型的项目中,在java中的使用占比不高(.NET中使用的较多)

  • Oracle:甲骨文公司提供,收费,适用于一些大型或者超大型的项目中,在java中的使用占比非常高

  • mysql:瑞典MySQLAB公司提供,免费开源,适用于一些小型或者中型的项目中,在Java中的使用占比较高(小巧轻量)
    mariadb其实就是MySQL的一个分支,用法和MySQL完全一样。

  • DB2:IBM公司提供,收费,在一些银行、金融等行业中使用较多。在java中的使用占比也不高。

  • Sqlite:迷你数据库,嵌入式设备中(安卓、苹果手机、pad)

二 、数据库相关概念(数据库服务器、数据库、表、表记录)

1.什么是数据库服务器

数据库服务器就是一个软件(比如mysql软件)将数据库软件安装在电脑上,当前电脑就是一个数据库服务器。就可以对外提供存取数据的服务

2.什么是数据库

数据库就是存储和管理数据的仓库,通常情况下,一个网站的中的所有数据会存放在一个数据库中。例如:

3.什么是表

一个数据库中可以创建多张表,每张表用于存储一类信息(数据库),例如:

jd中的用户数据 tb_user(表)

jd中的商品数据 tb_product(表)

jd中的订单数据 tb_order(表)

4.什么表记录

一张表中可以包含多行表记录,每一行表记录用于存储某一个具体的数据,例如:

三 、SQL语言

SQL是一们用于操作关系型数据库的通用的语言(使用SQL可以操作所有的关系型数据库)

使用SQL可以操作数据库、表、表记录

(1)创建数据库、删除数据库、修改数据库、查询数据库

(2)创建表、删除表、修改表、查询表

(3)新增表记录、删除表记录、修改表记录、查询表记录

使用SQL也可以操作存储过程/视图/索引等。

提示:SQL是一个标准通用的操作关系型数据库的语言(普通话),每个数据库厂商为了增强自己数据库的功能,都提供了支持自己数据库的语言,称之为数据库的方言。方言不通用!

四 、连接mysql服务器

通过命令行工具可以登录MySQL客户端,连接MySQL服务器,从而访问服务器中的数据。

1.连接mysql服务器:


-u: 后面的root是用户名,这里使用的是超级管理员root;

-p:(小写的p) 后面的root是密码,这是在安装MySQL时就已经指定的密码;

2.连接mysql服务器并指定IP和端口:


-h: 后面给出的127.0.0.1是服务器主机名或ip地址,可以省略的,默认连接本机;

-P:(大写的P) 后面的3306是连接端口,可以省略,默认连接3306端口;

3.退出客户端命令:quit或exit或 \q

五 、数据库及表操作(创建、删除、查看数据库)

提示:
(1) SQL语句对大小写不敏感。推荐关键字使用大写,自定义的名称(库名,表名,列名等)使用小写。

SHOW DATABASES; -- 查看当前数据库服务器中的所有库
CREATE DATABASE mydb1; -- 创建mydb1库

(2) 并且在自定义名称时,针对多个单词不要使用驼峰命名,而是使用下划线连接。(例如:tab_name,而不是 tabName )

1.查看mysql服务器中所有数据库

show databases;

2.进入某一数据库(进入数据库后,才能操作库中的表和表记录)

语法:USE 库名;

use test;

查看已进入的库

SELECT DATABASE();

3.查看当前数据库中的所有表

先进入某一个库,再查看当前库中的所有表
show tables;

4.删除mydb1库

语法:DROP DATABASE 库名;

drop database mydb1;

思考:当删除的库不存在时,如何避免错误产生?

drop database if exists mydb1;

5.重新创建mydb1库,指定编码为utf8

语法:CREATE DATABASE 库名 CHARSET 编码;

需要注意的是,mysql中不支持横杠(-),所以utf-8要写成utf8;

如果存在mydb1,则先删除,再重建
create database mydb1 charset utf8;
如果不存在则创建mydb1;
create database if not exists mydb1 charset utf8;

6.查看建库时的语句(并验证数据库库使用的编码)

语法:SHOW CREATE DATABASE 库名;

show create database mydb1; -- 查看建库时的语句
show create table stu; -- 查看建表时的语句

创建、删除、查看表

7.进入mydb1库,删除stu学生表(如果存在)

语法:DROP TABLE 表名;

use mydb1;
drop table if exists stu;

8.创建stu学生表(编号[数值类型]、姓名、性别、出生年月、考试成绩[浮点型]),建表的语法:

CREATE TABLE 表名(
	列名 数据类型,
	列名 数据类型,
	...
  列名 数据类型
);

SQL语句:

drop table if exists stu;
create table stu(
	id int primary key auto_increment, -- 将id设置为主键(唯一且不能为空),并且设置为自增
  name varchar(50),
  gender varchar(10),
  birthday date,
  score double
);

9.查看stu学生表结构

desc stu;

六 、新增、更新、删除表记录

1.往学生表(stu)中插入记录(数据)

语法:INSERT INTO 表名(列名1,列名2,列名3…) VALUES(值1,值2,值3…);

如果是在cmd中执行插入记录的语句,set names gbk; 再插入记录!
insert into stu(id,name,gender,birthday,score) value (null,'tom','male','1985-1-1',78);
insert into stu value(null,'孙悟空','female','1985-2-2',88);
insert into stu value(null,'猪八戒','male','1999-3-3',68);

提示:

(1)当为所有列插入值时,可以省写列名,但值的个数和顺序必须和声明时列的个数和顺序保持一致!
(2)SQL语句中的值为字符串或日期时,值的两边要加上单引号(有的版本的数据库双引号也可以,但推荐使用单引号)。
(3)(针对cmd窗口)在插入数据之前,先设置编码:set names gbk;

或者用一下命令连接mysql服务器:

mysql --default-character-set=gbk -uroot -proot

等价于:

mysql -uroot -proot

set names gbk;

2.查询stu表所有学生的信息

语法:SELECT 列名 | * FROM 表名

select * from stu;

3.修改stu表中所有学生的成绩,加10分特长分

修改语法: UPDATE 表名 SET 列=值,列=值,列=值…[WHERE子句];

update stu set score=score+10;

4.修改stu表中编号为1的学生成绩,将成绩改为83分。

update stu set score=83 where id=1;

提示:where子句用于对记录进行筛选过滤,保留符合条件的记录,将不符合条件的记录剔除。

5.删除stu表中所有的记录

删除记录语法: DELETE FROM 表名 [where条件]

delete from stu;

仅删除符合条件的

delete from stu where id>1;

七 、查询表记录

准备数据: 以下练习将使用db10库中的表及表记录,请先进入db10数据库!!!

1.基础查询

SELECT 语句用于从表中选取数据。结果被存储在一个结果表中(称为结果集)。

语法:SELECT 列名称 | * FROM 表名

提示:
(1) *(星号)为通配符,表示查询所有列。
(2)但使用 *(星号)有时会把不必要的列也查出来了,并且效率不如直接指定列名

查询emp表中的所有员工,显示姓名,薪资,奖金

select name,sal,bonus from emp;

查询emp表中的所有部门和职位

select dept,job from emp;

思考:如果查询的结果中,存在大量重复的记录,如何剔除重复记录,只保留一条?

在select之后、列名之前,使用DISTINCT 剔除重复的记录

select distinct dept,job from emp;

2.WHERE子句查询

WHERE子句查询语法:SELECT 列名称 | * FROM 表名称 WHERE 列 运算符 值

WHERE子句后面跟的是条件,条件可以有多个,多个条件之间用连接词(or | and)进行连接

下面的运算符可在 WHERE 子句中使用:

查询emp表中薪资大于3000的所有员工,显示员工姓名、薪资

select name,sal from emp 
where sal>3000;

查询emp表中总薪资(薪资+奖金)大于3500的所有员工,显示员工姓名、总薪资

select name,sal+bonus from emp
where sal+bonus > 3500; -- 结果是错误的!

ifnull(列, 值)函数: 判断指定的列是否包含null值,如果有null值,用第二个值替换null值

select name,ifnull(sal,0)+ifnull(bonus,0) from emp
where ifnull(sal,0)+ifnull(bonus,0) > 3500;

注意查看上面查询结果中的表头,如何将表头中的 sal+bonus 修改为 “总薪资”

使用as可以为表头指定别名

select name as 姓名,ifnull(sal,0)+ifnull(bonus,0) as 总薪资 from emp
where ifnull(sal,0)+ifnull(bonus,0) > 3500;

另外as可以省略

select name 姓名,ifnull(sal,0)+ifnull(bonus,0) 总薪资 from emp
where ifnull(sal,0)+ifnull(bonus,0) > 3500;

查询emp表中薪资在3000和4500之间的员工,显示员工姓名和薪资

select name,sal from emp
where sal>=3000 and sal<=4500;

提示: between…and… 在…之间

select name,sal from emp
where sal between 3000 and 4500;

查询emp表中薪资为 1400、1600、1800的员工,显示员工姓名和薪资

select name,sal from emp
where sal=1400 or sal=1600 or sal=1800;

或者

select name,sal from emp
where sal in(1400,1600,1800);

查询薪资不为1400、1600、1800的员工

方式一:

select name,sal from emp
where not(sal=1400 or sal=1600 or sal=1800);

方式二:

select name,sal from emp
where sal!=1400 and sal!=1600 and sal!=1800;

方式三:

select name,sal from emp
where sal not in(1400,1600,1800);

查询emp表中薪资大于4000和薪资小于2000的员工,显示员工姓名、薪资。

select name,sal from emp
where sal>4000 or sal<2000;

查询emp表中薪资大于3000并且奖金小于600的员工,显示员工姓名、薪资、奖金。

select name,sal,bonus from emp
where sal>3000 and bonus<600;

处理null值

select name,sal,ifnull(bonus,0) from emp
where sal>3000 and ifnull(bonus,0)<600;

查询没有部门的员工(即部门列为null值)

select * from emp where dept is null;

思考:如何查询有部门的员工(即部门列不为null值)

select * from emp where dept is not null;

3.模糊查询

LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。

可以和通配符(%、_)配合使用,其中"%"表示0或多个任意的字符。’_'表示一个任意的字符。

语法:SELECT 列 | * FROM 表名 WHERE 列名 LIKE 值

示例:

查询emp表中姓名中包含"涛"字的员工,显示员工姓名。

select name from emp
where name like '%涛%';
select name from emp
where name like '%王%';

查询emp表中姓名中以"刘"字开头的员工,显示员工姓名

select name from emp
where name like '刘%'; -- 查询以'刘'开头的

select name from emp
where name like '%涛'; -- 查询以'涛'结尾的

查询emp表中姓名以"刘"开头,并且姓名为两个字的员工,显示员工姓名。

select name from emp
where name like '刘_'; -- 以"刘"开头,并且姓名为两个字
select name from emp
where name like '刘_ _'; -- 以"刘"开头,并且姓名为三个字

3.多行函数查询

多行函数也叫做聚合(聚集)函数,根据某一列或所有列进行统计。

常见的多行函数有:

提示:
(1)多行函数不能用在where子句中

(2)多行函数和是否分组有关,分组与否会直接影响多行函数的执行结果。

(3)多行函数在统计时会对null进行过滤,直接将null丢弃,不参与统计。

统计emp表中薪资大于3000的员工个数

select count(*) from emp where sal>3000;
select count(id) from emp where sal>3000;

求emp表中的最高薪资

select sal from emp; -- 查询emp表中的所有薪资
select max(sal) from emp; -- 统计emp表中的最高薪资
select min(sal) from emp; -- 统计emp表中的最低薪资

统计emp表中所有员工的薪资总和(不包含奖金)

select sum(sal) from emp; -- 统计sal这一列中的所有值的和
select sum(bonus) from emp; -- 统计bonus这一列中的所有值的和

select sum(sal)+sum(bonus) from emp; -- 统计 薪资总和+奖金总和
select sum(sal+bonus) from emp; -- 因为bonus列中有null值,所以结果有误差
select sum(sal+ifnull(bonus,0)) from emp; -- 处理null值,结果正确

统计emp表员工的平均薪资(不包含奖金

select avg(sal) from emp;

多行函数需要注意的问题:

  • 多行函数和是否分组有关,如果查询结果中的数据没有经过分组,默认整个查询结果是一个组,多行函数就会默认统计当前这一个组的数据。产生的结果只有一个。

  • 如果查询结果中的数据经过分组(分的组不止一个),多行函数会根据分的组进行统计,有多少个组,就会统计出多少个结果。

select * from emp;

例如:统计CGB2003班的人数

select count(*) from emp;

结果返回的就是CGB2003班的所有人数

再例如:根据性别进行分组,再统计CGB2003每组的人数

select gender,count(*) from emp group by gender;

4.分组查询

select * from emp group by dept; -- 按照部门分组, 部门相同的是一组

对emp表按照职位进行分组,并统计每个职位的人数,显示职位和对应人数

select * from emp group by job; -- 按照职位分组, 职位相同的是一组
select job,count(*) from emp group by job; -- 按照职位分组, 统计每个职位的人数

对emp表按照部门进行分组,求每个部门的最高薪资(不包含奖金),显示部门名称和最高薪资

select dept,max(sal) from emp group by dept;

5.排序查询

使用 ORDER BY 子句将结果集中记录根据指定的列排序后再返回

语法:SELECT 列名 FROM 表名 ORDER BY 列名 [ASC|DESC]

ASC(默认)升序,即从低到高;DESC 降序,即从高到低。

对emp表中所有员工的薪资进行升序(从低到高)排序,显示员工姓名、薪资。

select name,sal from emp order by sal asc; -- 升序排序
select name,sal from emp order by sal; -- 默认是升序排序,因此asc可以省略

对emp表中所有员工的奖金进行降序(从高到低)排序,显示员工姓名、奖金。

select name,bonus from emp order by bonus desc; -- 按照奖金降序排序

-- 按照奖金降序排序,如果奖金相同,则按照薪资升序排序
select name,bonus,sal from emp order by bonus desc, sal asc;
-- 按照奖金降序排序,如果奖金相同,则按照薪资奖金排序
select name,bonus,sal from emp order by bonus desc, sal desc;

6.分页查询

在mysql中,通过limit进行分页查询:

limit (页码-1)*每页显示记录数, 每页显示记录数

查询emp表中的所有记录,分页显示:每页显示3条记录,返回第 1 页。

select * from emp limit 0,3; -- 每页显示3条, 查询第1页
select * from emp limit 3,3; -- 每页显示3条, 查询第2页
select * from emp limit 6,3; -- 每页显示3条, 查询第3页

查询emp表中的所有记录,分页显示:每页显示3条记录,返回第 2 页。

select * from emp limit 3,3; -- 每页显示3条, 查询第2页

求emp表中薪资最高的前3名员工的信息,显示姓名和薪资

-- 按照薪资降序排序查询员工信息
select name,sal from emp order by sal desc;
-- 在上面查询的基础上分页查询,每页显示3条, 查询第1页
select name,sal from emp order by sal desc limit 0,3; 

7.其他函数


查询emp表中所有在1993和1995年之间出生的员工,显示姓名、出生日期。

select name,birthday from emp
where birthday>='1993-1-1' and birthday<='1995-12-31';
-- 或 将birthday中的年份取出来, 和1993及1995进行比较!
select name,birthday from emp
where year(birthday)>=1993 and year(birthday)<=1995;

查询emp表中下个月过生日的所有员工

select * from emp
where month(birthday)=month(now()); -- 查询本月过生日的员工
select * from emp
where month(birthday)=month(now())+1; -- 查询下个月过生日的员工

查询emp表中员工的姓名和薪资(薪资格式为: xxx(元) )

select name, sal from emp;
select name, concat(sal,'(元)') from emp;

查询emp表中员工的姓名和薪资(薪资格式为: xxx/元 )

select name, concat_ws('/', sal, '元') from emp;

八 、mysql的数据类型

1.数值类型

tinyint:占用1个字节,相对于java中的byte

smallint:占用2个字节,相对于java中的short

int:占用4个字节,相对于java中的int

bigint:占用8个字节,相对于java中的long

其次是浮点类型即:float和double类型

float:4字节单精度浮点类型,相对于java中的float

double:8字节双精度浮点类型,相对于java中的double

2.字符串类型

1、char(n) 定长字符串,最长255个字符。n表示字符数,例如:

创建user表,指定用户名为char类型,字符长度不超过10

create table user(
    username char(10),
    ...
);

所谓的定长,是当插入的数据的长度小于指定的长度时,剩余的空间会用空格填充。(这样会浪费空间)

2、varchar(n) 变长字符串,最长不超过65535个字节,n表示字符数,一般超过255个字符,会使用text类型,例如:

iso8859-1码表:一个字符占用1个字节,1*n <= 65535, n最多等于 65535
utf8码表:一个中文汉字占用3个字节,3*n<=65535,n最多等于 65535/3
GBK码表:一个中文汉字占用2个字节,2*n<65535,n最多等于 65535/2

创建user表,指定用户名为varchar类型,长度不超过10

create table user(
	username varchar(10)
);

所谓的不定长,是当插入的数据的长度小于指定的长度时,剩余的空间可以留给别的数据使用。(节省空间)

3.大文本(长文本)类型

最长65535个字节,一般超过255个字符列的会使用text。

创建user表:

create table user(
	resume text
);

另,text也分多种,其中bigtext存储数据的长度约为4GB。

char(n)、varchar(n)、text都可以表示字符串类型,其区别在于:

(1)char(n)在保存数据时,如果存入的字符串长度小于指定的长度n,后面会用空格补全,因此可能会造成空间浪费,但是char类型的存储速度较varchar和text快。

因此char类型适合存储长度固定的数据,这样就不会有空间浪费,存储效率比后两者还快!

(2)varchar(n)保存数据时,按数据的真实长度存储,剩余的空间可以留给别的数据用,因此varchar不会浪费空间。

因此varchar适合存储长度不固定的数据,这样不会有空间的浪费。

(3)text是大文本类型,一般文本长度超过255个字符,就会使用text类型存储。

4.日期类型

date:年月日

time:时分秒

datetime:年月日 时分秒

timestamp:时间戳(实际存储的是一个时间毫秒值),与datetime存储日期格式相同。两者的区别是:

  • timestamp最大表示2038年,而datetime范围是1000~9999

  • timestamp在插入数据、修改数据时,可以自动更新成系统当前时间(后面用到时再做讲解)

九 、mysql的字段约束

字段约束/列约束 --> 约束: 限制

1.主键约束

主键约束:如果为一个列添加了主键约束,那么这个列就是主键,主键的特点是唯一且不能为空。

主键的作用: 作为一个唯一标识,唯一的表示一条表记录(作用类似于人的身份证号,可以唯一的表示一个人一样。)

① 添加主键约束,例如将id设置为主键:

create table stu(
	id int primary key,
	...
);

如果主键是数值类型,为了方便插入主键(并且保证插入数据时,主键不会因为重复而报错),可以设置一个主键自增策略

主键自增策略是指:设置了自增策略的主键,可以在插入记录时,不给id赋值,只需要设置一个null值,数据库会自动为id分配一个值(AUTO_INCREMENT变量,默认从1开始,后面依次+1),这样既可以保证id是唯一的,也省去了设置id的麻烦。

② 将id主键设置为自增:

create table stu(
	id int primary key auto_increment,
	...
);

2.非空约束

非空约束:如果为一个列添加了非空约束,那么这个列的值就不能为空,但可以重复。

① 添加非空约束,例如为password添加非空约束:

create table user(
	password varchar(50) not null,
	...
);

3.唯一约束

唯一约束:如果为一个列添加了唯一约束,那么这个列的值就必须是唯一的(即不能重复),但可以为空。

① 添加唯一约束,例如为username添加唯一约束及非空约束:

create table user(
	username varchar(50) unique not null,
	...
);

4.外键约束

① 创建表的同时添加外键

create table emp(
	id int,
	name varchar(50),
	dept_id int,
	foreign key(dept_id) references dept(id)
);


(1)如果是要表示两张表的数据之间存在对应关系,只需要在其中的一张表中添加一个列,保存另外一张表的主键,就可以保存两张表数据之间的关系。

但是添加的这个列(dept_id)对于数据库来说就是一个普通列,数据库不会知道两张表存在任何关系,因此数据库也不会帮我们维护这层关系。

(2)如果将dept_id列设置为外键,等同于通知数据库,部门表和员工表之间存在对应关系,dept_id列中的数据要参考部门的主键,数据库一旦知道部门和员工表之间存在关系,就会帮我们维护这层关系。

十 、表关系

常见的表关系分为以下三种:

一对多(多对一)、一对一、多对多

1.一对多

2.一对一

3.多对多

十一、多表查询

准备数据: 以下练习将使用db30库中的表及表记录,请先进入db30数据库!!!

1.连接查询

查询部门和部门对应的员工信息

select * from dept,emp;

上面的查询中存在大量错误的数据,一般我们不会直接使用这种查询。

笛卡尔积查询:所谓笛卡尔积查询就是指,查询两张表,其中一张表有m条记录,另一张表有n条记录,查询的结果是m*n条。

虽然笛卡尔积查询中包含大量错误数据,但我们可以通过where子句将错误数据剔除,保留下来的就是正确数据。

-- 条件: 员工所属的部门编号等于部门的编号
select * from dept,emp 
where emp.dept_id=dept.id;

通过where子句将笛卡尔积查询中的错误数据剔除,保留正确的数据,这就是连接查询!

上面的查询可以换成下面的查询:

select * from dept inner join emp 
on emp.dept_id=dept.id; -- 内连接查询

2.左外连接查询

查询所有部门和部门下的员工,如果部门下没有员工,员工显示为null

select * from dept left join emp 
on emp.dept_id=dept.id; -- 左外连接查询


左外连接查询:可以将左边表中的所有记录都查询出来,右边表只显示和左边相对应的数据,如果左边表中某些记录在右边没有对应的数据,右边显示为null即可。

3.右外连接查询

查询部门和所有员工,如果员工没有所属部门,部门显示为null

select * from dept right join emp
on emp.dept_id=dept.id;


右外连接查询:可以将右边表中的所有记录都查询出来,左边表只显示和右边相对应的数据,如果右边表中某些记录在左边没有对应的数据,可以显示为null。

扩展:如果想将两张表中的所有数据都查询出来(左外+右外并去除重复记录),可以使用全外连接查询,但是mysql又不支持全外连接查询。

select * from dept left join emp on emp.dept_id=dept.id
union
select * from dept right join emp on emp.dept_id=dept.id;

可以使用union将左外连接查询的结果和右外连接查询的结果合并在一起,并去除重复的记录。例如:

需要注意的是:union可以将两条SQL语句执行的结果合并有前提:

(1)两条SQL语句查询的结果列数必须一致

(2)两条SQL语句查询的结果列名、顺序也必须一致

并且union默认就会将两个查询中重复的记录去除(如果不希望去除重复记录,可以使用union all)

4.子查询练习

准备数据:以下练习将使用db40库中的表及表记录,请先进入db40数据库!!!

列出薪资比’王海涛’的薪资高的所有员工,显示姓名、薪资

-- 求出'王海涛'的薪资
select sal from emp where name='王海涛'; -- 2450
-- 列出比'王海涛'薪资还高的员工
select name,sal from emp 
where sal>(select sal from emp where name='王海涛');

列出与’刘沛霞’从事相同职位的所有员工,显示姓名、职位

-- 求出'刘沛霞'的职位
select job from emp where name='刘沛霞'; -- 推销员
-- 求出和'刘沛霞'从事相同职位的员工
select name,job from emp
where job=(select job from emp where name='刘沛霞');

列出薪资比’大数据部’部门(已知部门编号为30)所有员工薪资都高的员工信息,显示员工姓名、薪资和部门名称。

如果不考虑没有部门的员工

-- 1、连接查询员工和部门
select e.name,e.sal,d.name from dept d,emp e
where e.dept_id=d.id;
-- 2、求出大数据部门的最高薪资(查询员工表中部门编号为30的最高薪资)
select max(sal) from emp where dept_id=30;
-- 3、求出比大数据部门最高薪资还高的员工信息
select e.name,e.sal,d.name from dept d,emp e
where e.dept_id=d.id 
	and sal>(select max(sal) from emp where dept_id=30);

如果加上没有部门的员工

-- 1、用外连接查询所有员工和对应的部门
select e.name,e.sal,d.name from dept d right join emp e
on e.dept_id=d.id;
-- 2、求出大数据部门的最高薪资
select max(sal) from emp where dept_id=30;
-- 3、求出比大数据部门最高薪资还高的员工信息
select e.name,e.sal,d.name from dept d right join emp e
on e.dept_id=d.id and
sal>3000;

5.多表查询练习

列出在’培优部’任职的员工,假定不知道’培优部’的部门编号,显示部门名称,员工名称。

-- 关联查询两张表
select d.name,e.name from dept d, emp e
where e.dept_id=d.id;
-- 求出在培优部的员工
select d.name,e.name from dept d, emp e
where e.dept_id=d.id and d.name='培优部';

(自查询)列出所有员工及其直接上级,显示员工姓名、上级编号,上级姓名

/* emp e1 员工表, emp e2 上级表
 * 查询的表: emp e1, emp e2
 * 显示的列: e1.name, e2.id, e2.name
 * 连接条件: e1.topid=e2.id
 */
select e1.name, e2.id, e2.name
from emp e1, emp e2
where e1.topid=e2.id;

列出最低薪资大于1500的各种职位,显示职位和该职位的最低薪资

-- 根据职位进行分组,求出每种职位的最低薪资
select job,min(sal) from emp group by job;
-- 求出最低薪资大于1500的职位
select job,min(sal) from emp where min(sal)>1500 group by job; -- 错误写法

select job,min(sal) from emp group by job having min(sal) > 1500; -- 正确

where和having子句的区别:

(1)相同点: wherehaving都可以对记录进行筛选过滤。
(2)区别:where是在分组之前,对记录进行筛选过滤,并且where子句中不能使用多行函数以及列别名(但是可以使用表别名)
(3)区别:having是在分组之后,对记录进行筛选过滤,并且having子句中可以使用多行函数以及列别名、表别名。

列出在每个部门就职的员工数量、平均工资。显示部门编号、员工数量,平均薪资。

-- 根据部门对员工进行分组, 统计每个组(部门)的人数和平均薪资
select dept_id, count(*), avg(sal) from emp group by dept_id;

查出至少有一个员工的部门,显示部门编号、部门名称、部门位置、部门人数。

-- 连接查询部门表和员工表
select d.id,d.name,d.loc from emp e,dept d
where e.dept_id=d.id;
-- 按照部门进行分组,统计部门人数
select d.id,d.name,d.loc,count(*) from emp e,dept d
where e.dept_id=d.id 
group by d.id 
having count(*) >= 1;

列出受雇日期早于直接上级的所有员工,显示员工编号、员工姓名、部门名称。

/* emp e1 员工表, emp e2 上级表, dept d 部门表
 * 显示的列: e1.id, e1.name, d.name
 * 查询的表: emp e1,emp e2,dept d
 * 连接条件: e1.topid=e2.id  e1.dept_id=d.id
 * 筛选条件: e1.hdate<e2.hdate
 */
select e1.id, e1.name, d.name
from emp e1,emp e2,dept d
where e1.topid=e2.id and e1.dept_id=d.id
	and e1.hdate<e2.hdate;

查询员工表中薪资最高的员工信息

select name, max(sal) from emp; -- 错误写法
select name,sal from emp order by sal desc limit 0,1; -- 正确写法
-- 求出emp表中的最高薪资
select max(sal) from emp;
-- 根据最高薪资到emp表中查询, 该薪资对应的员工信息
select * from emp where sal=(select max(sal) from emp);

十二 、数据库备份与恢复

1.备份数据库

在cmd窗口中(未登录的状态下),可以通过如下命令对指定的数据库进行备份:

mysqldump -u用户名 -p 数据库的名字 > 备份文件的位置

示例1: 对db40库中的数据(表,表记录)进行备份,备份到 d:/db40.sql文件中

mysqldump -uroot -p db40 > d:/db40.sql

键入密码,如果没有提示,即表示备份成功!

也可以一次性备份所有库,例如:

对mysql服务器中所有的数据库进行备份,备份到 d:/all.sql文件中

mysqldump -uroot -p --all-database > d:/all.sql

键入密码,如果没有提示错误(警告信息不是错误,可以忽略),即表示备份成功!

2.恢复数据库

① 恢复数据库方式一:

在cmd窗口中(未登录的状态下),可以通过如下命令对指定的数据库进行恢复:

mysql -u用户名 -p 数据库的名字 < 备份文件的位置

示例:将d:/db40.sql文件中的数据恢复到db60库中

– 在cmd窗口中(已登录的状态下),先创建db60库:

create database db60 charset utf8;

– 在cmd窗口中(未登录的状态下)

mysql -uroot -p db60 < d:/db40.sql

② 恢复数据库方式二:

在cmd窗口中(已登录的状态下),可以通过source执行指定位置的SQL文件:

source sql文件的位置

示例:将d:/db40.sql文件中的数据恢复到db80库中

– 在cmd窗口中(已登录的状态下),先创建db80库,进入db80库:

create database db80 charset utf8;
use db80;

– 再通过source执行指定位置下的sql文件:

source d:/db40.sql

十三 、Navicat软件的使用

Navicat Premium是一套带图形用户界面的数据库管理工具,让你从单一应用程序中同时连接MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite数据库。使用Navicat可以快速、轻松地创建、管理和维护数据库。

1、使用navicat连接mysql服务器(使用cmd连接mysql服务器)
2、查看所有库、进入数据库、创建数据库、删除数据库、修改数据库
3、创建表、查看表、修改表、删除表
4、新增表记录、查询表记录、修改表记录、删除表记录
5、使用navicat书写SQL语句操作数据库、表和表记录
...

十四 、扩展内容

现创建学生表:

use test; -- 进入test库
drop table if exists stu; -- 删除学生表(如果存在)
create table stu( -- 创建学生表
    id int, -- 学生id
    name varchar(20), -- 学生姓名
    gender char(1), -- 学生性别
    birthday date -- 出生年月
);

1.修改表—新增列

语法:ALTER TABLE tabname ADD col_name datatype [DEFAULT expr][,ADD col_name datatype…];

1、往stu表中添加score列,double类型

alter table stu add score double;

2.修改表—修改列

语法:ALTER TABLE tabname MODIFY (col_name datatype [DEFAULT expr][,MODIFY col_name datatype]…);

1、修改id列,将id设置为主键

alter table stu modify id int primary key;

2、修改id列,将id主键设置为自动增长

alter table stu modify id int auto_increment;

3.修改表—删除列

语法:ALTER TABLE tabname DROP [COLUMN] col_name;

1、删除stu表中的score列

alter table stu drop score;

4.添加或删除主键及自增

思考:a) 在建表时,如何为id指定主键约束和自增?

b) 建好的表,如何通过修改添加主键约束和自增?

c) 如何删除表中的主键约束和自增?

1、创建stu学生表,不添加主键自增, 查看表结果

use mydb1; -- 切换到mydb1库
drop table if exists stu; -- 删除stu学生表(如果存在)
create table stu( -- 重建stu学生表,没有主键自增
    id int,
    name varchar(20),
    gender char(1),
    birthday date
);
desc stu; -- 查看表结构

表结构如下: 没有主键约束和自增。

2、如果表没有创建,或者要删除重建,在创建时可以指定主键或主键自增

drop table if exists stu; -- 删除stu表
create table stu( -- 重新创建stu表时,指定主键自增
    id int primary key auto_increment,
    name varchar(20),
    gender char(1),
    birthday date
);
desc stu; -- 查看表结构

表结构如下: 已经添加了主键约束和自增。

3、如果不想删除重建表,也可以通过修改表添加主键或主键自增

再次执行第1步,创建stu学生表,不添加主键自增,查看表结果

– 例如: 将stu学生表中的id设置为主键和自动增长

alter table stu modify id int primary key auto_increment;
desc stu; -- 查看表结构


如果只添加主键约束,不设置自增

alter table stu modify id int **primary key**;

如果已经添加主键约束,仅仅设置自增,但需注意:

(1)如果没有设置主键,不可添加自增

(2)只有当主键是数值时,才可以添加自增

alter table stu modify id int **auto_increment**;

4、如果想删除主键自增

– 删除主键自增时,要先删除自增

alter table stu modify id int;

– 再删除主键约束

alter table stu drop primary key;
desc stu; -- 查看表结构

5.添加外键约束

① 添加外键方式一:建表时添加外键

现有部门表如下:

– 创建部门表

create table dept(
	id int primary key auto_increment, -- 部门编号
	name varchar(20) -- 部门名称
);

要求创建员工表,并在员工表中添加外键关联部门主键

– 创建员工表

create table emp(
    id int primary key auto_increment, -- 员工编号
    name varchar(20), -- 员工姓名
    dept_id int, -- 部门编号
    foreign key(dept_id) references dept(id) -- 指定dept_id为外键
);

② 添加外键方式二:建表后添加外键

现有部门表和员工表:

– 创建部门表

create table dept(
    id int primary key auto_increment, -- 部门编号
    name varchar(20) -- 部门名称
);

– 创建员工表

create table emp(
    id int primary key auto_increment, -- 员工编号
    name varchar(20), -- 员工姓名
    dept_id int -- 部门编号
);

– 如果表已存在,可以使用下面这种方式:

alter table emp add constraint fk_dept_id foreign key(dept_id) references dept(id);

其中 fk_dept_id (名字由自己定义),是指外键约束名称,也可以将【constraint fk_dept_id】省略,MySQL会自动分配一个外键名称,将来可以通过该名称删除外键。

foreign key(dept_id)中的dept_id为外键

6. 删除外键约束

1、首先通过 “show create table 表名”语法,查询含有外键表的建表语句,例如:

show create table emp;

显示结果如下:

其中,emp_ibfk_1是在创建表时,数据库为外键约束指定的一个名字,删除这个名字即可删除外键关系,例如:

alter table emp drop foreign key emp_ibfk_1;


外键删除成功!

7.添加外键约束(多对多)

现有学生(stu)表和教师(tea)表:

创建学生表

create table stu(
    stu_id int primary key auto_increment, -- 学生编号
    name varchar(20) -- 学生姓名
);

创建教师表

create table tea(
	tea_id int primary key auto_increment, -- 教师编号
	name varchar(20) -- 教师姓名
);

添加第三方表(stu_tea)表示学生表和教师表关系

创建学生和教师关系表

create table stu_tea(
    stu_id int, -- 学生编号
    tea_id int, -- 教师编号
    primary key(stu_id,tea_id), -- 设置联合主键
    foreign key(stu_id) references stu(stu_id), -- 添加外键
    foreign key(tea_id) references tea(tea_id) -- 添加外键
);

其中为了防止重复数据,将stu_id和tea_id设置为联合主键。

将stu_id设置为外键,参考stu表中的stu_id列

并将tea_id设置为外键,参考tea表中的tea_id列

8.级联更新、级联删除

创建db20库、dept表、emp表并插入记录

删除db20库(如果存在),并重新创建db20库

drop database if exists db20;
create database db20 charset utf8;
use db20;

创建部门表, 要求id, name字段

create table dept(
	id int primary key auto_increment, -- 部门编号
	name varchar(20) -- 部门名称
);

往部门表中插入记录

insert into dept values(null, '财务部');
insert into dept values(null, '人事部');
insert into dept values(null, '科技部');
insert into dept values(null, '销售部');

创建员工表, 要求id, name, dept_id

create table emp(
    id int primary key auto_increment, -- 员工编号
    name varchar(20), -- 员工姓名
    dept_id int, -- 部门编号
    foreign key(dept_id) references dept(id) -- 指定外键
    on update cascade -- 级联更新
    on delete cascade -- 级联删除
);
insert into emp values(null, '张三', 1);
insert into emp values(null, '李四', 2);
insert into emp values(null, '老王', 3);
insert into emp values(null, '赵六', 4);
insert into emp values(null, '刘能', 4);

级联更新:主表(dept表)中的主键发生更新时(例如将销售部的id改为40),从表(emp表)中的记录的外键数据也会跟着该表(即赵六和刘能的部门编号也会更新为40)

级联删除:如果不添加级联删除,当删除部门表中的某一个部门时(例如删除4号部门),若该部门在员工表中有对应的员工(赵六和刘能),删除会失败!

若果添加了级联删除,当删除部门表中的某一个部门时,若该部门在员工表中有对应的员工,会在删除部门的同时,将员工表中对应的员工也删除!

9.where中不能使用列别名

① SQL语句的书写顺序

select * | 列名 -- 确定要查询的列有哪些
from 表名 -- 确定查询哪张表
where 条件 -- 通过筛选过滤,剔除不符合条件的记录
group by 分组的列 -- 指定根据哪一列进行分组
having 条件 -- 通过条件对分组后的数据进行筛选过滤
order by 排序的列 -- 指定根据哪一列进行排序
limit (countPage-1)*rowCount, rowCount -- 指定返回第几页记录以及每页显示多少条

② SQL语句的执行顺序:

from 表名 -- 确定查询哪张表
where 条件 -- 通过筛选过滤,剔除不符合条件的记录
select * | 列名 列别名 -- 确定要查询的列有哪些,
group by 分组的列 -- 指定根据哪一列进行分组
having 条件 -- 通过条件对分组后的数据进行筛选过滤
order by 排序的列 -- 指定根据哪一列进行排序
limit (countPage-1)*rowCount, rowCount

关于where中不能使用列别名但是可以使用表别名?

是因为,表别名是声明在from中,from先于where执行,先声明再使用没有问题,但是列别名是声明在select中,where先于select执行,如果先使用列别名,再声明,这样执行会报错!!

第二章 JDBC

一 、JDBC概述

1.什么是JDBC?为什么要学习JDBC?

JDBC(Java DataBase Connectivity) Java数据库连接

其实就是利用Java语言/程序连接并访问数据库的一门技术

之前我们可以通过CMD或者navicat等工具连接数据库

但在企业开发中,更多的是通过程序(Java程序)连接并访问数据库,通过Java程序访问数据库,就需要用到JDBC这门技术。

2.如何通过JDBC程序访问数据库?

① 提出需求:

创建一个 jt_db 数据库,在库中创建一个account表,并插入三条记录,然后利用Java程序查询出account表中所有的记录,并将查询的结果打印在控制台上。

② 开发步骤:

(1)准备数据, 创建jt_db库, 创建account表

drop database if exists jt_db;
create database jt_db charset utf8;
use jt_db;
create table account(
    id int primary key auto_increment,
    name varchar(50),
    money double
);
insert into account values(null, 'tom', 1000);
insert into account values(null, 'andy', 1000);
insert into account values(null, 'tony', 1000);

③ 代码实现:

public static void main(String[] args) throws Exception {
    //1.注册数据库驱动
    Class.forName("com.mysql.jdbc.Driver");
    //2.获取数据库连接
    Connection conn = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8",
        "root", "root");
    //3.获取传输器
    Statement stat = conn.createStatement();
    //4.发送SQL到服务器执行并返回执行结果
    String sql = "select * from account";
    ResultSet rs = stat.executeQuery( sql );
    //5.处理结果
    while( rs.next() ) {
        int id = rs.getInt("id");
        String name = rs.getString("name");
        double money = rs.getDouble("money");
        System.out.println(id+" : "+name+" : "+money);
    }
    //6.释放资源
    rs.close();
    stat.close();
    conn.close();
    System.out.println("TestJdbc.main()....");
}

④ 执行结果:

3.JDBC API总结

① 注册数据库驱动

Class.forName(“com.mysql.jdbc.Driver”);

所谓的注册驱动,就是让JDBC程序加载mysql驱动程序,并管理驱动

驱动程序实现了JDBC API定义的接口以及和数据库服务器交互的功能,加载驱动是为了方便使用这些功能。

② 获取连接之数据库URL

Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8",
    "root", "root" );

DriverManager.getConnection() 用于获取数据连接,返回的Connection连接对象是JDBC程序连接数据库至关重要的一个对象。

参数2参数3分别是所连接数据库的用户名和密码。

参数1:“jdbc:mysql://localhost:3306/jt_db” 是连接数据库的URL,用于指定访问哪一个位置上的数据库服务器及服务器中的哪一个数据库,其写法为:


当连接本地数据库,并且端口为3306,可以简写为如下形式:

jdbc:mysql:///jt_db

③ Statement传输器对象

Statement stat = conn.createStatement();
该方法返回用于向数据库服务器发送sql语句的Statement传输器对象

该对象上提供了发送sql的方法:

executeQuery(String sql) --
用于向数据库发送查询类型的sql语句,返回一个ResultSet对象中
executeUpdate(String sql) --
用于向数据库发送更新(增加、删除、修改)类型的sql语句,返回一个int值,表示影响的记录行数

④ ResultSet结果集对象

ResultSet对象用于封装sql语句查询的结果,也是一个非常重要的对象。该对象上提供了遍历数据及获取数据的方法。

(1)遍历数据行的方法

next() – 使指向数据行的箭头向下移动一行,并返回一个布尔类型的结果,true表示箭头指向了一行数据,false表示箭头没有指向任何数据(后面也没有数据了)

(2)获取数据的方法

getInt(int columnIndex)
getInt(String columnLable)
getString(int columnIndex)
getString(String columnLable)
getDouble(int columnIndex)
getDouble(String columnLable)
getObject(int columnIndex)
getObject(String columnLable)

⑤ 释放资源

rs.close();
stat.close();
conn.close();

此处释放资源必须按照一定的顺序释放,越晚获取的越先关闭。所以先关闭
rs对象,再关闭stat对象,最后关闭conn对象。

另,为了避免上面的程序抛出异常,释放资源的代码不会执行,应该把释放资源的代码放在finally块中.

try{
	...
}catch(Exception e){
	...
}finally{
    if (rs != null) {
        try {
        	rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            rs = null;
        }
    }
    if (stat != null) {
        try {
        	stat.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            stat = null;
        }
    }
    if (conn != null) {
        try {
        	conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            conn = null;
        }
    }
}

二 、增删改查

1.新增:往account表中添加一个名称为john、money为3500的记录

JDBCUtil工具类

package com.tedu.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/** JDBC工具类 */
public class JdbcUtil {
	/**
	 * 获取数据库连接对象并返回
	 * @return Connection对象
	 * @throws Exception 
	 */
	public static Connection getConn() throws Exception{
		//1.注册驱动
		Class.forName( "com.mysql.jdbc.Driver" );
		//2.获取连接
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql:///jt_db?characterEncoding=utf-8", 
				"root", 
				"root");
		return conn;
	}
	
	/**
	 * 释放JDBC程序中的资源
	 * @param conn 连接对象
	 * @param stat 传输器对象
	 * @param rs 结果集对象
	 */
	public static void close(Connection conn, 
			Statement stat, ResultSet rs){
		if(rs != null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally{
				rs = null;
			}
		}
		if(stat != null){
			try {
				stat.close();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally{
				stat = null;
			}
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally{
				conn = null;
			}
		}
	}
}
/* 1、新增:往account表中添加一个名称为john、money为3500的记录 */
@Test
public void testInsert() {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		//注册驱动并获取连接
		conn = JdbcUtil.getConn();
		//获取传输器
		stat = conn.createStatement();
		//发送sql语句到服务器执行,并返回执行结果
		String sql = "insert into account values(null, 'john', 3500)";
		int rows = stat.executeUpdate( sql );
		//处理结果
		System.out.println( "影响行数: "+rows );
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		//通过JdbcUtil工具类中的close方法释放资源
		JdbcUtil.close(conn, stat, rs);
	}
}

2.修改:将account表中名称为john的记录,money修改为1500

/* 2、修改:将account表中名称为john的记录,money修改为1500 */
@Test
public void testUpdate() {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		//注册驱动并获取连接
		conn = JdbcUtil.getConn();
		//获取传输器
		stat = conn.createStatement();
		//发送sql语句到服务器执行,并返回执行结果
		String sql = "update account set money=1500 where name='john'";
		int rows = stat.executeUpdate( sql );
		//处理结果
		System.out.println( "影响行数: "+rows );
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		//通过JdbcUtil工具类中的close方法释放资源
		JdbcUtil.close(conn, stat, rs);
	}
}

3.查询:查询account表中名称为john的记录

/* 3、查询:查询account表中id为1的记录 */
@Test
public void testFindById() {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		//注册驱动并获取连接
		conn = JdbcUtil.getConn();
		//获取传输器
		stat = conn.createStatement();
		//执行sql语句,返回执行结果
		String sql = "select * from account where id=1";
		rs = stat.executeQuery( sql );
		//处理结果
		if( rs.next() ) {
			int id = rs.getInt("id");
			String name = rs.getString("name");
			double money = rs.getDouble("money");
			System.out.println( id+" : "+name+" : "+money);
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		JdbcUtil.close(conn, stat, rs);
	}
}

4.删除:删除account表中名称为john的记录

/* 4、删除:删除account表中名称为john的记录 */
@Test
public void testDelete() {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		//注册驱动并获取连接
		conn = JdbcUtil.getConn();
		//获取传输器
		stat = conn.createStatement();
		//发送sql语句到服务器执行,并返回执行结果
		String sql = "delete from account where name='john'";
		int rows = stat.executeUpdate( sql );
		//处理结果
		System.out.println( "影响行数: "+rows );
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		//通过JdbcUtil工具类中的close方法释放资源
		JdbcUtil.close(conn, stat, rs);
	}
}

5.单元测试补充

单元测试:不用创建新的类,也不用提供main函数,也不用创建类的实例,就可以直接执行一个方法

加了@Test注解的方法,可以通过单元测试(junit)框架测试该方法。底层会创建该方法所在类的实例,通过实例调用该方法。

@Test
public void testInsert() {
	System.out.println("TestPreparedStatement.testInsert()");
}

能够使用@Test单元测试测试的方法必须满足如下几个条件:

(1)方法必须是公共的
(2)方法必须是非静态的
(3)方法必须是无返回值的
(4)方法必须是无参数的
(5)进行单元测试的方法或类,命名时不要命名为 Test/test

三、 PreparedStatement

在上面的增删改查的操作中,使用的是Statement传输器对象,而在开发中我们用的更多的传输器对象是PreparedStatement对象,PreparedStatement是Statement的子接口,比Statement更加安全,并且能够提高程序执行的效率。

Statement 父对象

PreparedStatement 子对象

1.模拟用户登录案例

(1)准备数据

use jt_db;
create table user(
    id int primary key auto_increment,
    username varchar(50),
    password varchar(50)
);
insert into user values(null,'张三','123');
insert into user values(null,'李四','234');

(2)创建LoginUser 类,提供 main 方法 和 login 方法。

public static void main(String[] args) {
	/* 1、提示用户登录,提示用户输入用户名并接收用户名
	 *  2、提示用户输入密码并接收密码
	 *  3、根据用户名和密码查询用户信息
	 */
	// 1、提示用户登录,提示用户输入用户名并接收用户名
	Scanner sc = new Scanner(System.in);
	System.out.println( "请登录:" );
	System.out.println( "请输入用户名:" );
	String user = sc.nextLine();
	
	// 2、提示用户输入密码并接收密码
	System.out.println( "请输入密码:" );
	String pwd = sc.nextLine();
	
	// 3、根据用户名和密码查询用户信息
	login( user, pwd );
}
/**
 * 根据用户名和密码查询用户信息
 * @param user 用户名
 * @param pwd 密码
 */
private static void login(String user, String pwd) {
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
		//1.注册驱动并获取连接
		conn = JdbcUtil.getConn();
		//2.获取传输器,执行sql并返回执行结果
		stat = conn.createStatement();
		String sql = "select * from user where username='"+user+"' and password='"+pwd+"'";
		rs = stat.executeQuery(sql);
		System.out.println( sql );
		//3.处理结果
		if( rs.next() ) { //有数据 -- 用户名密码都正确
			System.out.println("恭喜您登录成功!");
		}else { //没数据 -- 用户名或密码不正确
			System.out.println("登录失败, 用户名或密码不正确!");
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		//4.释放资源
		JdbcUtil.close(conn, stat, rs);
	}
}

执行时,输入:

请登录:
请输入用户名:
张三'#'
请输入密码:

select * from user where username='张三'#'' and password=''
恭喜您登录成功了!

或输入

请登录:
请输入用户名:
张三' or '1=1
请输入密码:

select * from user where username='张三' or '1=1' and password=''
恭喜您登录成功了!

或输入

请登录:
请输入用户名:

请输入密码:
' or '2=2
select * from user where username='' and password='' or '2=2'
恭喜您登录成功了!

2.SQL注入攻击

通过上面的案例,我们发现在执行时,不输入密码只输入用户名也可以登陆成功。这就是SQL注入攻击。

SQL注入攻击产生的原因: 由于后台执行的SQL语句是拼接而来的:

select * from user where username='"+user+"' and password='"+pwd+"'

其中的参数是用户提交过来的,如果用户在提交参数时,在参数中掺杂了一些SQL关键字(比如or)或者特殊符号(#、-- 、’ 等),就可能会导致SQL语句语义的变化,从而执行一些意外的操作(用户名或密码不正确也能登录成功)!

3.防止SQL注入攻击

如何防止SQL注入攻击?

(1)使用正则表达式对用户提交的参数进行校验。如果参数中有(# – ’ or等)这些符号就直接结束程序,通知用户输入的参数不合法

(2)使用PreparedStatement对象来替代Statement对象。

下面通过第二种方式解决SQL注入攻击:添加loginByPreparedSatement方法,在方法中,使用PreparedStatement来代替Statement作为传输器对象使用,代码示例:

/**
 * 根据用户名和密码查询用户信息
 * @param user 用户名
 * @param pwd 密码
 */
private static void login(String user, String pwd) {
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
		//1.注册驱动并获取连接
		conn = JdbcUtil.getConn();
		//2.获取传输器,执行sql并返回执行结果
		String sql = "select * from user where username=? and password=?";
		ps = conn.prepareStatement( sql );
		//设置SQL语句中的参数
		ps.setString( 1 , user );
		ps.setString( 2 , pwd );
		//执行SQL语句
		rs = ps.executeQuery();//这里不要再传输SQL语句
		
		//3.处理结果
		if( rs.next() ) { //有数据 -- 用户名密码都正确
			System.out.println("恭喜您登录成功!");
		}else { //没数据 -- 用户名或密码不正确
			System.out.println("登录失败, 用户名或密码不正确!");
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		//4.释放资源
		JdbcUtil.close(conn, ps, rs);
	}
}

再次执行程序,按照上面的操作登录。此时,已经成功的防止了SQL注入攻击问题了。

PreparedStatement对象是如何防止SQL注入攻击的:

使用PreparedStatement对象是先将SQL语句的骨架发送给服务器编译并确定下来,编译之后SQL语句的骨架和语义就不会再被改变了,再将SQL语句中的参数发送给服务器,即使参数中再包含SQL关键字或者特殊符号,也不会导致SQL语句的骨架或语义被改变,只会被当作普通的文本来处理!

使用PreparedStatement对象可以防止SQL注入攻击

而且通过方法设置参数更加的方便且不易出错!

还可以从某些方面提高程序执行的效率!

四、 数据库连接池

1.什么是连接池

池:指内存中的一片空间(容器,比如数组、集合)

连接池:就是将连接存放在容器中,供整个程序共享,可以实现连接的复用,减少连接创建和关闭的次数,从而提高程序执行的效率!

2.为什么要使用连接池


3.如何使用C3P0连接池

dbcp/c3p0/druid

使用C3P0连接池开发步骤:

① 导入开发包

② 创建数据库连接池(对象)

ComboPooledDataSource cpds = new ComboPooledDataSource();

③ 设置连接数据库的基本信息(推荐使用方式二、三)

(1)方式一:(不推荐) 直接将参数通过 pool.setXxx方法设置给c3p0程序

这种方式直接将参数写死在了程序中,后期一旦参数发生变化,就要修改程序,要重新编译项目、重新发布项目,非常麻烦。

//设置连接数据库的基本信息
pool.setDriverClass( "com.mysql.jdbc.Driver" );
pool.setJdbcUrl( "jdbc:mysql:///jt_db?characterEncoding=utf-8" );
pool.setUser( "root" );
pool.setPassword( "root" );

(2)方式二:将连接参数提取到properties文件中(推荐)

文件必须放在src(源码根目录)目录下 !

文件名必须叫做 c3p0.properties !

在类目录下(开发时可以放在src或者类似的源码目录下),添加一个c3p0.properties文件,配置内容如下:

c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql:///jt_db?characterEncoding=utf-8
c3p0.user=root
c3p0.password=root

这种方式由于是c3p0到指定的位置下寻找指定名称的properties文件,所以文件的位置必须是放在src或其他源码根目录下,文件名必须是c3p0.properties。

(3)方式三:将连接参数提取到xml文件中(推荐)

文件必须放在src(源码根目录)目录下 !

文件名必须叫做 c3p0-config.xml

在类目录下(开发时可以放在src或者类似的源码目录下),添加一个c3p0-config.xml文件,配置内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property
        name="jdbcUrl">jdbc:mysql:///jt_db?characterEncoding=utf-8</property>
        <property name="user">root</property>
        <property name="password">root</property>
    </default-config>
</c3p0-config>

这种方式由于是c3p0到指定的位置下寻找指定名称的xml文件,所以文件的位置必须是放在src或其他源码根目录下,文件名必须是c3p0-config.xml。

④ 从连接池中获取一个连接对象并进行使用

Connection conn = pool.getConnection();

⑤ 用完连接后将连接还回连接池中

JdbcUtil.close(conn, ps, rs);
//conn.close()
/* 如果是自己创建的连接对象,这个连接对象没有经过任何的改动,调用
* conn.close方法,是将连接对象关闭
* 如果是从连接池中获取的连接对象,该连接对象在返回时就已经被连接池
* 改造了,将连接对象的close方法改为了还连接到连接池中
*/

五、 切换工作空间,修改默认编码

1.切换新的工作空间,设置工作空间编码为utf-8

(1)切换到新的工作空间目的:若旧的工作空间内容过多,可能会导致eclipse不编译,甚至进入休眠状态。

(2)设置工作空间编码为utf-8,此后在当前工作空间下创建的项目编码也默认是utf-8。

2.设置JSP文件的编码为utf-8

六、 JDBC实现学生信息管理系统

1.准备数据

建库建表语句如下:

-- 1、创建数据库jt_db数据库(如果不存在才创建)
create database if not exists jt_db charset utf8;
use jt_db; -- 选择jt_db数据库
-- 2、在 jt_db 库中创建 stu 表(学生表)
drop table if exists stu;
create table stu(
    id int,
    name varchar(50),
    gender char(2),
    addr varchar(50),
    score double
);
-- 3、往 stu 表中, 插入记录
insert into stu values(1001,'张三','男', '北京', 86);

2.查询所有学生信息

3.添加学生信息

4.根据id修改学生信息

5.根据id删除学生信息


代码如下:
JDBCUtil工具类

package com.tedu;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/** JDBC工具类 */
public class JdbcUtil {
	/**
	 * 获取数据库连接对象并返回
	 * @return Connection对象
	 * @throws Exception 
	 */
	public static Connection getConn() throws Exception{
		//1.注册驱动
		Class.forName( "com.mysql.jdbc.Driver" );
		//2.获取连接
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql:///jt_db?characterEncoding=utf-8", 
				"root", 
				"root");
		return conn;
	}
	
	/**
	 * 释放JDBC程序中的资源
	 * @param conn 连接对象
	 * @param stat 传输器对象
	 * @param rs 结果集对象
	 */
	public static void close(Connection conn, 
			Statement stat, ResultSet rs){
		if(rs != null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally{
				rs = null;
			}
		}
		if(stat != null){
			try {
				stat.close();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally{
				stat = null;
			}
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			} finally{
				conn = null;
			}
		}
	}
}

6.实现以上功能代码

package com.tedu;

//1.查询所有学生信息,将查询到的学生信息直接打印到控制台
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

import com.mysql.jdbc.Connection;
import com.mysql.jdbc.PreparedStatement;

/**
 * 
 * 基于JDBC+MySQL完成学生信息管理系统
 *
 */
public class StuManagerFindAllAddStuUpdateStu {
	private static Scanner sc = new Scanner(System.in);

	public static void main(String[] args) {
		while (true) {//加while循环语句是为了,每次执行的时候,不用重新点击运行运行
			System.out.print("a:查询学生信息");
			System.out.print("b:添加学生信息");
			System.out.print("c:修改学生信息");
			System.out.println("d:删除学生信息");
			System.out.print("请输入操作,abcd任选一项:");
			// 接收用户输入
			String opt = sc.next();// a、b、c、d
			if (opt.equals("a")) {
				// 查询学生信息
				findAll();
			} else if (opt.equals("b")) {
				// 新增学生信息
				addStu();
			} else if (opt.equals("c")) {
				// 根据id修改学生信息
				updateStu();
			} else if (opt.equals("d")) {
				deleteStu();
			} else {
				System.out.println("您的输入有误,请重新输入");
			}
		}
	}

	// 根据id删除学生信息
	private static void deleteStu() {
		System.out.println("请输入要删除的学生编号:");
		int id = sc.nextInt();

		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			// 注册驱动并获取连接
			conn = (Connection) JdbcUtil.getConn();// 小转大 ,强转
			// 获取传输器,执行SQL语句
			String sql = "delete from stu where id=?";
			ps = (PreparedStatement) conn.prepareStatement(sql);
			// 设置SQL语句
			ps.setInt(1, id);
			int rows = ps.executeUpdate();
			if (rows > 0) {
				System.out.println("学生信息删除成功");
				System.out.println("=====================");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			JdbcUtil.close(conn, ps, rs);
		}

	}

	// 3.根据id修改学生信息
	private static void updateStu() {
		// 提示用户输入修改后的学生信息
		System.out.println("请输入要修改的学生编号、姓名、性别、地址、成绩(以空格分隔):");
		int id = sc.nextInt();// 获取用户输入的id
		String name = sc.next();// 获取用户输入的name
		String gender = sc.next();// 获取用户输入的gender
		String addr = sc.next();// 获取用户输入的addr
		double score = sc.nextDouble();// 获取用户输入的score

		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			// 注册驱动并获取连接
			conn = (Connection) JdbcUtil.getConn();
			// 获取传输器,并执行SQL语句
			String sql = "update stu set name=?,gender=?,addr=?,score=? where id=?";
			ps = (PreparedStatement) conn.prepareStatement(sql);

			// 设置SQL语句

			ps.setString(1, name);
			ps.setString(2, gender);
			ps.setString(3, addr);
			ps.setDouble(4, score);
			ps.setInt(5, id);

			// 执行SQL语句
			int rows = ps.executeUpdate();// 不需要二次传入SQL
			if (rows > 0) {
				System.out.println("学生信息修改成功");
				System.out.println("=====================");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			JdbcUtil.close(conn, ps, rs);
		}

	}

	// 2.接受用户输入学生信息,并添加学生信息
	private static void addStu() {
		System.out.println("请输入要添加的学生编号、姓名、性别、地址、成绩(以空格分隔):");
		int id = sc.nextInt();// 获取用户输入的id
		String name = sc.next();// 获取用户输入的name
		String gender = sc.next();// 获取用户输入的gender
		String addr = sc.next();// 获取用户输入的addr
		double score = sc.nextDouble();// 获取用户输入的score
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			// 注册驱动并获取连接
			conn = (Connection) JdbcUtil.getConn();
			// 获取传输器,执行SQL语句
			String sql = "insert into stu value(?,?,?,?,?)";
			ps = (PreparedStatement) conn.prepareStatement(sql);
			// 设置SQL语句
			ps.setInt(1, id);
			ps.setString(2, name);
			ps.setString(3, gender);
			ps.setString(4, addr);
			ps.setDouble(5, score);
			// 执行SQL语句
			int rows = ps.executeUpdate();// 不需要二次传入SQL
			if (rows > 0) {
				System.out.println("学生信息添加成功");
				System.out.println("=====================");
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			JdbcUtil.close(conn, ps, rs);
		}

	}

	private static void findAll() {
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try {
			// 注册驱动并获取链接
			conn = (Connection) JdbcUtil.getConn();
			// 获取传输器,执行SQL语句
			stat = conn.createStatement();
			String sql = "select * from stu";
			rs = stat.executeQuery(sql);
			// 处理结果,因为信息不止一条,所以需要遍历
			while (rs.next()) {
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String gender = rs.getString("gender");
				String addr = rs.getString("addr");
				double score = rs.getDouble("score");
				System.out.println(id + "," + name + "," + gender + "," + addr + "," + score);
				System.out.println("=====================");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			JdbcUtil.close(conn, stat, rs);
		}
	}

}

第三章 HTML、CSS

HTML: 用于开发网页的一门技术

CSS: 用于修饰、渲染网页的一门技术

HTML+CSS可以开发一个非常美观、非常漂亮的网页

开发网页 盖房子

HTML标签搭建网页的结构 砖块(搭建房子的结构)

CSS属性 石灰、油漆等涂料

一、 HTML概述

1.HTML是什么

HTML(Hyper Text Markup Language): 超文本标记语言

超文本: 超级文本、超过文本(其中可以包含除了文本以外的其他数据,例如图片、音频、视频等各种格式)

标记:也叫标签、元素、节点等,就是用尖括号(<>)括起来的一组内容,例如:

<head> <body> <div> <span> <table>等

HTML是最基础的开发网页的语言。

HTML由W3C组织提供(CSS/xml)

关于HTML的细节:

(1)使用HTML开发的网页文件通常以 .htm或.html 为后缀!

(2)使用HTML开发的网页文件由浏览器负责解析并显示(浏览器就是一个html解析器)

(3)HTML是文档的一种(txt/word/ppt/pdf等)

总结: HTML就是用于开发网页的一门语言!!

2.HTML的结构

1、案例:编写我的第一个HTML网页,并用浏览器打开

新建一个txt文档,将后缀名改为.html,代码实现如下:

<!DOCTYPE html>
<html>
	<head>
		<title>网页的标题</title>
	</head>
	<body>
    <h1>Hello CGB2003...</h1>
	</body>		
</html>

2、HTML结构详解

(1)<!DOCTYPE HTML> 文档声明, 用来声明HTML文档所遵循的HTML规范和版本
	上面是html5.0的声明, 也是目前最常用的版本
(2)<head></head> 头部分, 用来存放HTML文档的基本属性信息, 比如网页的标题, 文档使用的编码等, 这部分信息会被浏览器优先加载.
(3)<body></body> 体部分, 用来存放网页可视化数据. 即真正的网页数据
(4)<title></title> 声明网页的标题
(5)<meta charset="utf-8"/> 用来通知浏览器使用哪一个编码来打开HTML文档, 这个编码一定要和文件保存时的编码保持一致, 才不会出现中文乱码问题.

3.HTML语法

① html标签

标签:也叫做标记、元素等,标签分为开始标签,例如:

<head><body>

和结束标签,例如:

</head></body>

开始标签和结束标签之间还可以包含其他内容。

<head>
	<titile>声明网页的标题</title>
	<meta charset="utf-8"/>
</head>

有些标签开始标签和结束标签之间没有内容要包裹,通常可以写成自闭标签,例如:

<meta/> <br/> <hr/> <input/> <img/>

② html属性

在标签上可以声明属性(属性不能独立存在,必须声明在标签上)

<div id="d1" class="c1" style="color:red;"></div>

标签上可以声明多个属性,多个属性之间用空格分隔

标签上的属性的值可以使用单引号或者双引号引起来

<meta charset="UTF-8" id="m1"/>
<meta charset='UTF-8' id='m1'/>

③ html注释

格式: <!-- 注释内容 -->

注释的作用: (1)为代码添加解释说明

(2)将一些暂时不需要执行的代码注释

浏览器对于html注释中的内容不会解析,也不会显示!

④ html空格和换行

在浏览器中,多个连续的空白字符(空格、制表符tab、换行)会被浏览器显示为一个空格。那么:

如何在网页中做一个换行:可以使用 <br/> 标签做换行

如何在网页中做一个空格:可以使用&nbsp;&emsp;做空格

补充: HTML中是不区分大小写的!

HTML中对语法要求非常不严格!(比如大小写混用,或者标签只有开始没有结束,或者标签的不合理嵌套),但是我们在书写HTML时要按照规范来写。

二、 HTML标签

1.图像标签

通过img标签可以在网页中插入一副图像

<!-- 
	不推荐写带盘符的绝对路径,因为将来项目发布,位置会改变,到时还要修改图片的路径
<img src="D:/JavaDevelop/WS_CGB2003_WEB/day05-htmlcss/WebContent/imgs/meinv.jpg"/>
 -->
<!-- 
	./: 表示当前目录(当前文件所在的目录),由于当前html在WebContent目录下
	因此 ./叫表示WebContent目录, 另外 ./ 可以省略!
	../: 表示当前目录的上一级目录里(也就是当前目录的父目录)
	../../: 表示当前目录的上一级目录的上一级目录(也就是当前目录的父目录的父目录)
 -->
<img src="./imgs/meinv.jpg" width="50%"/>
<img src="./imgs/lpx.jpg"  width="50%"/>

其中src属性用于指定图片的路径(图片的路径不要是带盘符的绝对路径,推荐使用相对路径)

width属性用于指定图片的宽度

height属性用于指定图片的高度

2.超链接标签

超链接就是a标签,通过a标签可以在网页中创建指向另外一个文档的超链接

点击超链接可以跳转到另外一个网页(图片/下载路径等),示例:

<!-- 跳转到本地的一个网页 -->
<a href="01-第一个网页.html">01-第一个网页.html</a>
<!-- 跳转到百度首页 -->
<a href="http://www.baidu">百度一下,你就不知道</a>
<br/>
<!-- 点击图片跳转到tmooc -->
<a target="_blank" href="http://www.tmooc">
	<img alt="tmooc" src="imgs/tmooc.png" />
</a>

其中 href 属性用于指定点击超链接后将要跳转到的URL地址

target属性用于指定以何种方式打开超链接

_self:默认值, 表示在当前窗口中打开超链接

_blank:表示在新的窗口中打开超链接

3.表格标签

①案例:在网页中插入一个3*3的表格

<h1>案例:在网页中插入一个3*3的表格</h1>
<table><!-- 用于在网页中定义一个表格 -->
    <tr><!-- 用于定义一个表格行 -->
        <th>表头1</th>
        <th>表头2</th>
        <th>表头3</th>
    </tr>
    <tr>
        <td>11</td><!-- 用于定义一个单元格 -->
        <td>12</td>
        <td>13</td>
    </tr>
    <tr>
        <td>21</td>
        <td>22</td>
        <td>23</td>
    </tr>
    <tr>
        <td>31</td>
        <td>32</td>
        <td>33</td>
    </tr>
</table>

在浏览器中显示效果如下:

在head标签内添加如下内容:

<style>
    /* style标签内只能书写css注释和css代码 */
    table{
        border:*2px solid red; /* 为表格添加边框 */
        border-collapse: collapse; /* 设置边框合并 */
        background-color: pink; /* 为表格设置背景颜色 */
        width: 70%; /* 为表格设置宽度 */
        /* margin-left: 15%; */
        /* 设置表格的左外边距和右外边距自适应(保证两者始终相等) */
        margin-left: auto;
        margin-right: auto;
    }
    td,th{
        border:2px solid red; /* 为单元格添加边框 */
        border-collapse: *collapse*; /* 设置边框合并 */
        padding: 5px; /* 设置单元格边框和内容的距离(内边距) */
    }
    h1{
        /* border: 2px solid blue; */
        text-align: *center*; /* 设置元素中的内容水平居中 */
    }
</style>

再次刷新浏览器显示效果为:

② 表格标签介绍

table -- 用于在网页中定义一个表格
tr -- 用于定义表格中的行
td -- 用于定义表格中的单元格
th -- 用于定义表头行中的单元格(th中的文本默认居中,并且加粗)

4.表单标签

① 表单的作用: 用于向服务器提交数据

向服务器提交数据的两种方式:

(1)通过表单向服务器提交数据

表单中可以包含表单项标签,在表单项中可以填写数据(比如用户名、密码等),填写完成后通过提交表单,可以将表单中的数据提交给相应的服务器。

② 通过超链接向服务器提交数据

http://www.baidu?username=张三&pwd=123&like=篮球

<a href="http://www.baidu?username=张三&pwd=123&like=篮球" target="_blank">百度一下,你就不知道!</a>

在地址栏URL地址的后面通过问号(?)可以拼接参数,参数可以有多个,多个参数之间用&分隔,参数还分为参数名(例如:username/pwd/like)以及参数值(例如:张三/123/篮球),在回车后访问百度服务器的同时,就可以将问号后面拼接的参数一并带给百度服务器。

2、表单标签

<form action="url地址" method="提交方式"></form>

其中action属性用于**指定表单的提交地址,**例如,将action指向百度服务器,就意味着将来填写完表单后,提交表单将会把表单中的数据提交给百度服务器。

method=“GET/POST” 属性是用于指定表单的提交方式,常用的就是GET和POST提交。

5.表单项标签(input元素、select、option标签、textarea多行文本输入区域)

1、input元素:

① 普通文本输入框(比如:用户名/昵称/邮箱/验证码等)

<input type="text" name="username"/>

② 密码输入框(比如:密码/确认密码等)

<input type="password" name="pwd"/>

③ 单选框(比如:性别/部门等)

<input type="radio" name="gender"/>

④ 复选框/多选框(比如:爱好/岗位等)

<input type="checkbox" name="like"/>

⑤ 普通按钮(比如:换一张图片)

<input type="button" value="换一张"/>

普通按钮本身没有功能,但我们可以通过js为按钮添加功能或添加行为

⑥ 提交按钮(比如:提交/注册/登录)

<input type="submit" value="提交/注册/登录"/>

提交按钮用于提交表单中的数据到服务器中!

⑦ select、option标签:

<select name="city">
    <option value="beijing">北京</option>
    <option value="shanghai">上海</option>
    <option selected="selected">广州</option>
    <option>深圳</option>
</select>

select用于定义一个下拉选框

option用于定义下拉选框上的选项

selected设置当前option选项默认被选中

⑧ textarea多行文本输入区域:

<textarea name="description" cols="30" rows="5"
placeholder="请输入描述信息..."></textarea>

cols属性: 用于设置文本输入框的列数(宽度)

rows属性: 用于设置文本输入框的行数(高度)

placeholder属性:设置输入框中的提示消息!

6.表单细节问题

① 提交表单时,表单中的数据为什么没有被提交?

对于表单中的表单项标签,只要是需要向服务器提交数据,该表单项上必须添加name属性;如果表单项标签上没有name属性,在表单提交时,该项将会别忽略。例如:

<input type="text" name="username"/>
<input type="password" name="pwd"/>

② 如何让多个单选框只能有一个被选中?

要求多个单选框必须具有相同的name属性值,如果多个单选框name属性值相同,则说明是一个组的内容,一个组中的单选框只能选择其中的一个!

<td>性别:</td>
<td>
    <input type="radio" name="gender"/><input type="radio" name="gender"/></td>

③ 为什么单选框、复选框选择某一项后提交的值都是on?

因为单选框、复选框只能选择,不同于用户名、密码输入框,可以输入内容。

因此我们需要通过value属性为单选框或复选框设置提交的值(如果不设置默认值都是on),例如:

<input type="radio" name="gender" value="male"/><input type="radio" name="gender" value="female"/>

④ 如何设置单选框或复选框默认选中某一项?

可以在单选框或复选框标签上添加一个checked="checked"属性,就可以让当前单选框或复选框默认被选中。例如:

<input type="radio" checked="checked" name="gender" value="male"/><input type="radio" name="gender" value="female"/><!-- 爱好复选框/多选框 -->
<input type="checkbox" name="like" value="basketball"/>篮球
<input type="checkbox" checked="checked" name="like" value="football"/>足球
<input type="checkbox" name="like" value="volleyball"/>排球

⑤ 如何设置下拉选框默认选中某一项?

在option标签上添加一个selected="selected"属性,可以让当前option选项默认被选中,例:

<select name=*"city"*>
    <option>北京</option>
    <option>上海</option>
    <option selected="selected">广州</option>
    <option>深圳</option>
</select>

⑥ 下拉选框中option选项上的value属性的作用是什么?

<select name="city">
	<option value="beijing">北京</option>
	<option value="shanghai">上海</option>
	<option selected="selected">广州</option>
	<option>深圳</option>
</select>

如果某一个选项被选中,并且该选项上添加了value属性,在提交表单时,将会提交value属性的值。

如果某一个选项被选中,该选项上没有添加value属性,在提交表单时,将会提交标签中的内容

三、 注册表单案例

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<style>
/* style标签内只能书写css注释和css代码 */
table {
	border: 2px solid red; /* 为表格添加边框 */
	border-collapse: collapse; /* 设置边框合并 */
	background-color: lightgrey; /* 为表格设置背景颜色 */
	/* margin-left: 15%; */
	/* 设置表格的左外边距和右外边距自适应(保证两者始终相等) */
	margin-left: auto;
	margin-right: auto;
}

td, th {
	border: 2px solid red; /* 为单元格添加边框 */
	border-collapse: collapse; /* 设置边框合并 */
	padding: 5px; /* 设置单元格边框和内容的距离(内边距) */
}

h1 {
	/* border: 2px solid blue; */
	text-align: center; /* 设置元素中的内容水平居中 */
}
</style>
</head>
<body>
	<h1>欢迎注册</h1>
	<form action="#">
		<table>
			<tr>
				<!-- 用户名输入框 -->
				<td>用户名:</td>
				<td><input type="text" name="username" /></td>
			</tr>
			<tr>
				<!-- 密码输入框 -->
				<td>密码:</td>
				<td><input type="password" name="pwd" /></td>
			</tr>
			<tr>
				<!-- 性别单选框 -->
				<td>性别:</td>
				<td>
					<input type="radio" name="gender" value="male" /><input type="radio" name="gender" value="female" /></td>
			</tr>
			<tr>
				<!-- 爱好复选框/多选框 -->
				<td>爱好:</td>
				<td><input type="checkbox" name="like" value="basketball" />篮球
					<input type="checkbox" checked="checked" name="like"
					value="football" />足球
					<input type="checkbox" name="like"
					value="volleyball" />排球
				</td>
			</tr>
			<tr>
				<!-- 城市下拉选框 -->
				<td>城市:</td>
				<td>
					<select name="city">
						<option value="beijing">北京</option>
						<option value="shanghai">上海</option>
						<option selected="selected">广州</option>
						<option>深圳</option>
					</select>
				</td>
			</tr>
			<tr>
				<!-- 自我描述 多行文本输入框 -->
				<td>自我描述:</td>
				<td><textarea name="description" cols="30" rows="5"
						placeholder="请输入描述信息..."></textarea></td>
			</tr>
			<tr>
				<!-- 提交按钮 ,作用:用于提交表单中的数据,到action属性所指定的地址-->
				<!-- colspan: 设置单元格横跨的列数 
						rowspan:设置单元格竖夸的行数
				-->
				<td colspan="2" style="text-align: center;"><input
					type="submit" value="提交" /></td>
			</tr>
		</table>
	</form>
</body>
</html>

第四章 CSS

一 、 css概述

1.什么是CSS?

2.在HTML中引入CSS

方式1:通过style属性引入css(不推荐)

<!-- 
	1.通过标签上的style属性给div设置样式 
	边框:2px solid red 
	字体大小:26px
	背景颜色为:pink
-->
<div style="border:2px solid red;font-size:26px;background:pink;">这是一个div...</div>

由于上面这种方式是将css样式代码,直接写在标签上的style属性内部,如果属性太多,容易造成页面结构的混乱,不利于后期的维护。

将样式代码写在标签上,样式代码只对当前标签生效,代码无法复用!

并且使用这种方式无法将html标签和css样式进行分离!

因此不推荐使用这种方式!(这种通过style属性添加的样式,叫做行内样式/内联样式)

方式2:通过style标签引入css

在head标签内部可以添加一个style标签,在style标签内部可以直接书写css样式

这种方式是将所有的css样式代码集中在一个style标签内部统一管理,这种方式不会造成页面结构的混乱,并且可以实现代码的复用!

初步的实现了将html标签代码和css样式代码进行了分离!

代码示例 :

<!-- 2.通过style标签给span设置样式如下: 
	边框: 2px solid green
	字体大小: 30px
	字体加粗
-->
<head>
<meta charset="utf-8" />
<style type="text/css">
	/* ****** CSS样式 ****** */
	span{ /* 为当前html中的所有span标签设置样式 */
		border:2px solid green;
		font-size:30px;
		font-weight:bolder; /* bold/bolder */
	}
</style>
</head>

方式3:通过link链接引入外部的css文件

在head标签内部,通过一个link标签可以引入外部的CSS文件

这种方式是将所有的css代码集中在一个单独的css文件中统一管理,真正的实现了将css代码和html代码的分离,实现了代码的复用。

代码示例:html中引入demo.css文件

<!-- 3.通过link标签引入外部的css文件,为p元素设置样式如下: 
	边框: 2px solid blue;
	字体颜色:red
	字体设置为华文琥珀
	设置首行文本缩进50px
-->
<!-- 引入demo.css文件(中的样式) -->
<link rel="stylesheet"  href="demo.css"  />

demo.css文件

@charset "UTF-8";
p{
	border: 2px solid blue;
	color: red;
	font-family: 华文琥珀;
	text-indent: 50px;
}

CSS: 层叠样式表,用于修饰、渲染网页的一门技术

使用css样式修饰网页,可以实现将设置样式的css代码和展示数据的html代码进行分离,增强了网页的展示能力!

二 、 CSS选择器

所谓的选择器就是能够在html中帮助我们选中元素进行修饰的一门技术。

1.标签选择器

通过元素名称(或标签名称)选中指定名称的所有标签

格式: 元素名/标签名{ css样式… }

/* ----- 1.标签名选择器练习 ----- 
将所有span标签的背景颜色设置为#efbdef, 设置字体大小为22px,字体加粗;*/
span{ /* 选中所有的span元素 */
	background-color:#efbdef; 
	font-size: 22px;
	font-weight: bolder;
}

2.class选择器

可以为元素添加一个通用的属性 – class,通过class属性为元素设置所属的组,class值相同的元素则为一组。通过class值可以选中这一组的元素,为元素添加样式。

格式:.class值{ css样式… }

实例:

/* ----- 2.类选择器练习 ----- 
(1)将所有的span(但是不包括div和p标签下的span)的背景颜色设置为#faf77b,边框改为2px solid cyan;
(2)将div下的span和内容为"span111"的span,背景颜色设置为#5eff1e、字体颜色设置
#ec0e7e;*/
.s1{ /* 选中所有class值为s1的元素 */
	background: #faf77b;
	border: 2px solid cyan;
}
.s2{ /* 选中所有class值为s2的元素 */
	background: #5eff1e;
	color: #ec0e7e;
}

另外,一个元素也可以设置多个class值,多个class值中间用空格分隔,例如:

<span class="s1 s2" >span111</span>

表示当前元素同时属于多个分组,多个分组上设置的样式也会同时作用在当前元素上。

如果多个分组设置了相同的样式(但是值不一样),样式会发生冲突,写在后面的样式会覆盖前面的样式!

3. id选择器

通过标签上通用的属性id,可以为标签设置一个独一无二的编号(id值应该是唯一的),通过id值可以唯一的选中一个元素。

格式:#id值{ css样式 }

/* ----- 3.id选择器练习 -----
用id选择器将第一个p标签设置字体大小为24px,字体颜色为#a06649, 首行文本缩进20px。*/
#p1{ /* 选中id值为p1的元素 */
	font-size:24px;
	color: #a06649;
	text-indent: 20px;
}
<p id="p1">这是一个p元素!!!</p>

4.后代选择器

选中指定元素内部的指定后代元素

格式: 祖先 后代{ css样式… }

/* ----- 4.后代选择器练习 ----- 
为p元素内部的所有span元素,设置字体大小为18px,字体颜色为红色,背景颜色为pink;*/
p span{ /* 匹配所有p元素内部的所有span元素 */
	font-size:18px;
	color: red;
	background: pink;
}
/* p,span{} 匹配所有的p元素和所有的span元素 */
<p id="p1">
	这是一个p元素!!!
	<span>这是一个span元素!!!</span>
</p>

5.属性选择器

在选择器选中元素的基础上,根据元素的属性条件筛选/过滤元素

格式:选择器[属性条件]…{ css样式 }

/* ----- 5.属性选择器 ----- 
为所有的文本输入框,设置背景颜色为#FF7CCC、字体大小22px,首行文本缩进15px;*/
input[type='text']{ /* 匹配所有的input并且type值为text的元素 */
	background: #FF7CCC;
	font-size: 22px;
	text-indent: 15px;
}
input[type='text'][name='email']{ 
	/* 选中所有的input并且type值为text、并且name为email的元素 */
	background : yellow;
}

6.选择器优先级顺序

(1)如果是同一类选择器,同时给某些元素设置了样式,如果样式冲突了,那么写在后面的样式会覆盖前面的样式。

(2)如果是不同的选择器,设置的样式优先级顺序是:id选择器(100) > 类选择器(10) > 元素名选择器(1)

三、 常用属性总结

1.文本属性

1、text-align:设置元素中文本水平对齐方式,其常用取值为:

left: 默认值。左对齐
right: 右对齐
center: 居中对齐
justify: 两端对齐

2、text-decoration:设置文本的下划线样式,其常用取值为:

underline: 有下划线
none: 没有下划线

3、text-indent:设置文本首行缩进,单位: 像素/百分比

4、letter-spacing:设置字符间隔/间距,其常用取值为:

normal
像素值

5、text-shadow:设置字体阴影,其取值为:

像素值 像素值 像素值 颜色值
第一个值为阴影水平位移,第二个值为阴影垂直位移,第三个值为阴影扩散值,第四个值为阴影颜色

2.字体属性

font-size:设置字体大小

font-weight:设置字体粗细 bold、bolder、normal 100/200/300…/900

font-family:设置字体,比如微软雅黑、黑体、楷体等

color:设置字体颜色

3.背景属性

background-color:设置背景颜色

background-image:设置背景图片,url(‘图片的路径’);

background-repeat:设置或检索对象的背景图像是否及如何铺排,常用取值:

repeat(默认值,重复排列)
repeat-x(横向重复排列,但纵向不重复)
repaet-y(纵向重复排列,但横向不重复)
no-repeat(不重复)

background-position:设置或检索对象的背景图像位置

background: 背景颜色 背景图片 背景图片是否重复 背景图片的位置

3.边框(border)

border:2px solid red; – 设置元素的边框(可以同时设置边框的宽度、样式、颜色)

border属性可以拆分为如下设置:

border-width: 2px; -- 设置元素边框的宽度
border-style: solid; -- 设置元素边框的样式
border-color: red; -- 设置元素边框的颜色

其中border-width、border-style、border-color也可以按照上右下左方向进行拆分,以border-width为例:

border-top-width: 2px; -- 设置上边框的宽度
border-left-width: 2px; -- 设置左边框的宽度
border-right-width: 2px; -- 设置右边框的宽度
border-bottom-width: 2px; -- 设置下边框的宽度

4.其他属性

width:设置元素的宽度

height:设置元素的高

5.颜色设置

颜色取值方式常见的方式有三种:

方式一:设置颜色名,例如:

red、green、blue、yellow、cyan、pink、white、black等

方式二:设置#加上六位的十六进制数值

#FF0000(red)、#00FF00(green)、#0000FF(blue)、#FFFF00(yellow)、#00FFFF(cyan)等

方式三:设置rgb颜色值

rgb(255,0,0) 、rgb(0,255,0) 、rgb(0,0,255) 、rgb(255,255,0) 、rgb(0,255,255) 等
(red) (green) (blue) (yellow) (cyan)

四、 网页开发实战

1.display属性

display用来设置元素的类型,常用取值:

block:块级元素的默认值
    默认情况下独占一行
    可以设置宽高
inline:行内元素的默认值
    默认情况下多个行内元素可以处在同一行
    一般不能设置宽高
inline-block:行内块元素
    多个元素既可以显示在同一行, 也可以设置宽和高
none:表示隐藏元素

2.元素类型

div/span/p 都是一个容器标签,用于包裹其他内容(这些元素本身不具备太多的样式!)

p: 块级元素,默认独占一行,用于包裹一段文本(写文章时用于p标签包裹每一段内容)

div: 块级元素,默认独占一行,用于包裹其他内容,将样式设置在div上,就可以作用在div的内容上。

span:行内元素,默认可以和其他元素显示在同一行。

① 块级元素(block)

默认情况下,块级元素独占一行

可以设置宽和高,如果设置了就是设置的宽和高

如果不设置宽和高,其中宽是默认填满父元素,而高是由内容决定(由内容支撑)

比如: div/p/h1~h6/form/table 等元素都是块级元素

② 行内元素(inline)

默认情况下,多个行内元素可以处在同一行

不能设置宽和高

比如: span/input/img/i/b 等元素都是行内元素

③ 行内块元素(inline-block)

既具备行内元素的特征(可以同行显示),还具备块级元素的特征(可以设置宽和高)

3.网页实战效果

4.京淘注册页面代码

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>京淘注册页面</title>
<!-- 引入JingTaoZhuCe.css文件 -->
<link rel="stylesheet" href="./JingTaoZhuCe.css">
</head>
<body>
	<!-- 头部(header) -->
	<div id="header">
		<!-- 第一行 -->
		<div id="line1">
			<img src="./imgs/logo_jt.jpg"/>
			<!-- span元素是行内元素,默认无法设置宽高 -->
			<span class="line"></span>
			<span class="wel-reg">欢迎注册</span>
		</div>
		<!-- 第二行 -->
		<div id="line2">
			<span>已有账号?</span>
			<!-- #做临时占位 -->
			<a href="#">请登录</a>
		</div>
	</div>
	
	<!-- 主体(middle) -->
	<div id="middle">
		<form action="#">
			<table>
				<tr><!-- 1.用户名 -->
					<td>
						<input type="text" name="username" placeholder="用户名"/>
					</td>
				</tr>
				<tr><!-- 2.用户名下方的提示消息 -->
					<td class="msg">
						支持中文、字母、数字、“-”、“_”的组合,4-20个字符
					</td>
				</tr>
				<tr><!-- 3.设置密码 -->
					<td>
						<input type="password" name="pwd" placeholder="设置密码"/>
					</td>
				</tr>
				<tr><!-- 4.设置密码下方的提示消息 -->
					<td class="msg">
						建议使用数字、字母和符号两种以上的组合,6-20个字符
					</td>
				</tr>
				<tr><!-- 5.确认密码 -->
					<td>
						<input type="password" name="repwd" placeholder="确认密码"/>
					</td>
				</tr>
				<tr><!-- 6.设置确认密码下方的提示消息 -->
					<td class="msg err-msg">
						两次密码输入不一致
					</td>
				</tr>
				<tr><!-- 7.验证手机 -->
					<td class="ck-phone">
						<input type="text" name="phone" placeholder="验证手机"/>
						&nbsp;<span></span>&nbsp;
						<a href="#">验证手机</a>
						
					</td>
				</tr>
				<tr><!-- 8.注册协议 -->
					<td class="protocol">
						已阅读并同意 
						<a href="#">《京淘用户注册协议》</a>
					</td>
				</tr>
				<tr><!-- 9.立即注册 -->
					<td>
						<input type="submit" value="立即注册"/>
					</td>
				</tr>
			</table>
		
		</form>
		<img src="./imgs/msg.jpg">
	</div>
	
	<!-- 尾部(footer) -->
	<div id="footer">
		<p>
			关于我们 | 联系我们 | 人才招聘 | 商家入驻 | 营销中心 | 手机京淘 | 友情连接 | 销售联盟 | 京淘社区 | 京淘公益 | English Site 
			<br/>
			Copyright © 2015 - 2019 京淘jingtao.com 版权所有
		</p>
	</div>
</body>
</html>

5.京淘注册页面CSS代码

@charset "UTF-8";
/*头部div(header) */
#header{
	/*background-color: pink; */ /* 333*/
	width: 1200px;
	height:114px;/* 333 */
	/* 设置div水平居中 */
	margin-left: auto;
	margin-right: auto;
	/* 设置向上的外边距 */
	margin-top: 41px;
}
/* 头部的第一行(#line1)*/
#line1{
	height: 72px;
}
#line1 img{/* logo图标 */
	margin-left: 50px;
	margin-right: 33px;
	/* 设置同行内的元素中线对齐 */
	vertical-align: middle;
}

#line1 .line{/* 竖线 */
	border: 2px solid #676767; 
	/* 将行内元素设置为行内块元素,就可以设置宽高*/
	display: inline-block;
	height: 68px;/* 68+2+2=72px*/
	/* 设置竖线的背景颜色 */
	background-color: #676767;
	/* 设置同行内的元素中线对齐 */
	vertical-align: middle;
}

#line1 .wel-reg{/* 欢迎注册 */
	/*letter-spacing:17px;*/ /* 字符间距 */
	/*text-shadow:12px 20px 30px #676767;*/  /* 阴影得到模糊程度*/
	font-size: 36px;
	color: #676767;
	/* 设置向左的边距 */
	margin-left: 32px;
	/* 设置同行内的元素中线对齐 */
	vertical-align: middle;
}

/* 头部的第二行(#line2)*/
#line2{
	height: 42px;
	/* 设置元素中的内容居右显示 */
	text-align: right;
}
#line2 span{/* 已有账号? */
	font-size: 24px;
	color: #999999;
}
#line2 a{/* 请登录 */
	font-size: 24px;
	color: #333333;
	/* 设置文本没有下划线(underline有下划线)*/
	text-decoration: none;
}
/*主体div(middle) */
#middle{
	/*background-color: yellow;*/ /* 333*/
	width: 1200px;
	/*height:600px;*/ /* 333 */
	/* 设置div水平居中 */
	margin-left: auto;
	margin-right: auto;
	/* 设置边框为实线 */
	border:7px solid #d0d0d0;
	border-top-width: 9px;/* 设置顶部宽度 */
	border-bottom-width: 9px;/* 设置底部宽度 */
	/* 设置上内边框(主体上边框和内容的距离)*/
	padding-top: 75px;
	
}
/* 主体中的表单*/
#middle form{
	width: 819px;
	min-height: 400px;/* 333 */
	/*background-color: pink;*/ /* 333 */
	margin-left: 109px;
	/* 将行内元素设置为行内块元素,
		就可以和其他元素显示在同一行 */
	display: inline-block;
}

/* 主体中的快速注册图片 */
#middle img{
	/* 让当前元素与行内最高元素顶端对齐 */
	vertical-align: top;
}

/* 匹配表单中所有input元素 (文本输入框/密码框/按钮等)*/
#middle form table input{
	width: 381px;
	height: 50px;
	font-size: 16px;
	color: #999999;
	border: 1px solid #d0d0d0;
	text-indent: 45px;
}

/* 用户输入框 */
#middle form table input[name='username']{
	/* 设置背景图片 repeat默认是重复的,
		repeat-x:只在X轴方向重复
		repeat-Y:只在y轴方向重复
		no-repeat:不重复
		前一个15px:向x轴位移15px
		后一个15px:向y轴位移15px
	*/
	background: url("./imgs/u_ico.jpg") no-repeat 15px 15px;
}

/* 选中用户名/密码/确认密码输入框下方的提示消息 */
#middle form table .msg{
	height: 32px;
	font-size: 12px;
	text-indent: 45px;
	color: #c3c3c3;
	background: url("./imgs/alert_1.jpg") no-repeat 17px 7px;
}

/* 密码输入框/确认密码输入框 */
#middle form table input[type='password']{
	background: url("./imgs/p_ico.jpg") no-repeat 15px 15px;
}

/* 错误提示消息 */
#middle form table .err-msg{
	color: #e64346;
	background: url("./imgs/alert_2.jpg") no-repeat 17px 7px;
}

/* 验证手机输入框 */
#middle form table input[name='phone']{
	background: url("./imgs/phone_ico.jpg") no-repeat 15px 15px;
}

#middle form table .ck-phone span{/*或*/
	font-size: 16px;
}
#middle form table .ck-phone a{/*验证手机*/
	font-size: 16px;
	color: #005ea7;
}

/* 同意注册协议 */
#middle form table .protocol{
	height: 83px;
	font-size: 16px;
	line-height: 52px;
	text-indent: 60px;
}
#middle form table a{
	color: #005ea7;
	/* 设置文本没有下划线 */
	text-decoration: none;
}
/*尾部(footer) */
#footer{
	/*background-color: cyan;*/ /* 333*/
	width: 1200px;
	height:100px;/* 333 */
	/* 设置div水平居中 */
	margin-left: auto;
	margin-right: auto;
	/* 设置向上的外边距 */
	margin-top: 109px;
}

/* 立即注册*/
#middle form table input[type='submit']{
	background-color: #e64346;
	color: #ffffff;/* 输入框内的字体颜色*/
	font-size: 20px;
	/* 覆盖上面设置的首行缩进 */
	text-indent: 0px;
}
#footer p{
	font-size: 14px;
	/* 设置元素中文本居中显示 */
	text-align: center;
	color: #999999;
	/* 设置行高 */
	line-height:34px; 
}

五、 学习目标

第五章 JavaScript

一、 JavaScript简介

1.什么是JavaScript

全称叫做JavaScript,简称叫做JS

由NetScape(网景)公司提供,是一门专门嵌入在浏览器中执行的脚本语言

LiveScript JavaScript

JS运行在浏览器中,负责实现网页中的动画效果

或者是实现表单校验等功能

2.JS特点和优势

① 特点

(1)JS是一门直译式的语言(边解释边执行,没有编译的过程)

java–> 编写时 xx.java —>编译成 xx.class --> 运行

js —> 编写时 html, 编写时 js文件, 直接运行, 没有编译过程

(2)JS是一门基于对象的语言(JS中没有类的概念,也没有编译的过程)

JS中是有对象的(内置对象、自定义对象)

(3)JS是一门弱类型的语言(Java:强类型)

在java中: 变量一旦声明, 就属于固定的数据类型, 不能被改变
String s = "abc";
int n = 100;JS: 变量是不区分类型的, 可以指向任意的数据类型
var s = 100;
s = "abc";
s = true;
s = [];
s = function(){}

② 优势

(1)JS具有良好的交互性

(2)JS具有一定的安全性(只能在浏览器内部运行,不能访问浏览器以外的资源)

(3)JS具有跨平台性(JS 浏览器)

( JS语言是跨平台的,是因为有浏览器,但浏览器不跨平台

Java语言是跨平台的,是因为有虚拟机,但虚拟主机不跨平台 )

3.在HTML书写JS的方式

① 在script标签内部可以书写JS代码

在head或者body标签内部可以添加一个script标签,在script标签内部可以直接书写JS代码!

<!-- 在script标签内部可以书写JS注释和JS代码 type="text/javascript"属性可以省略-->
<script type="text/javascript">
  //JS的单行注释
  /* JS的多行注释 */
  // 警告框
	alert( "在html中引入JS的第一种方式..." );
</script>

② 通过script标签引入外部的JS文件

在head或body标签内部,可以通过script标签引入外部的JS文件。例如:

<!-- 通过script标签可以引入外部的JS文件 -->
<script src="demo.js"></script>

<!-- 外部的JS文件(demo.js) -->
//在js文件中可以直接书写JS代码
alert("引入JS的第二种方式");

注意:(1)在引入js文件的script标签内部不要书写JS代码,例如:

<script src="demo.js">
	alert( 111 ); //这里的代码不会执行
</script>

(2)不要将引入JS文件的script标签自闭,因为可能会导致文件引入失败,如下:

<script src="demo.js" />

③ 也可以直接在元素上书写JS代码

<!-- 直接在元素上书写JS代码:
onclick属性用于给当前元素绑定点击事件:点击元素就会触发事件,执行相应函数
-->
<input type="button" value="点我~" onclick="alert('叫你点你还真敢点啊!!!')"/>
<input type="button" value="别点我"  onclick="console.log( new Date() )"/>

二、 JavaScript语法

1.注释格式

JS的注释符号和Java的注释符号相同,如下:

// 单行注释内容
/* 多行注释内容 */

2.数据类型

① 基本数据类型(数值、字符串、布尔、undefined、null)

(1)数值类型(number)

在JS中,所有的数值在底层都是浮点型,但是在处理和显示的过程中会自动的和整型进行转换。

例如:2.4+3.6=6
特殊值:Infinity(无穷大) / -Infinity(负无穷大) / NaN(非数字)

(2)字符串类型(string)

在JS中,字符串类型属于基本数据类型,字符串常量可以使用单引号或者使用双引号引起来。例如:

var s1 = "Hello JS";
var s2 = 'Hello JS';

另外,JS中字符串类型有对应的包装对象(String),在需要时会自动的和包装对象进行转换。

var s1 = "Hello JS"; //基本数据类型, string
console.log( typeof s1 ); //打印s1的类型,为string类型
var s2 = new String("Hello JS"); //复杂数据类型, object
console.log( typeof s2 ); //object
//不管是基本数据类型s1, 还是对象类型s2, 都可以当作对象来用
console.log( s1.valueOf() ); //s1是基本数据类型, 会转成对象, 调用valueOf函数
console.log( s2.valueOf() );

(3)布尔类型(boolean)

布尔类型的值有两个,分别为true和false。

(4)undefined类型

undefined类型的值只有一个,就是undefined,表示变量未定义(但不是指变量没有声明)。

是指声明了变量,但没有为变量赋值,该变量的值就是undefined。

/* 1.undefined类型 */
var x;
alert( x ); //undefined
alert( y ); //抛异常

(5)null类型

null类型的值也只有一个,就是null,表示空值。

可以作为函数的返回值,表示函数返回的是一个空的对象。

注意:null和undefined类型的变量是不能调用函数或属性的,会抛异常!

② 复杂数据类型

主要指对象(JS的内置对象、自定义的对象、函数、数组)

3.变量声明

JS中是通过var关键字声明变量,声明的变量是不区分类型,可以指向任意的数据。例如:

var x = 100;
x = "abc";
x = true;
x = [];
x = function(){}
x = new Object();
x = {};

另外,JS中多次声明同名的变量不会出现语法错误,会覆盖前面的。例如:

/* 2.变量的定义 */
var s = "Hello"; // var s; s="Hello";
var s = 123; // var s; s=123; 第二次覆盖第一次,第二次生效
alert( s ); //123

JS是严格区分大小写的!

4.JS运算符

JS和Java中的运算符大致相同,例如:

算术运算符: +,-,*,/,%,++,--
赋值运算符: =,+=,-=,*=,/=,%=
比较运算符: ==,!=,>,>=,<,<=
位运算符: & , |
逻辑运算符: && ,||   ( false && 表达式, true || 表达式 )
前置逻辑运算符: ! (not)
三元运算符: 表达式 ? 表达式 : 表达式
。。。

5.JS语句

JS中的语句和Java中的语句也大致相同

① if分支结构

if分支结构用于基于不同的条件来执行不同的动作。语法结构如下:

if (条件 1){
	当条件 1true 时执行的代码
}else if (条件 2){
	当条件 2true 时执行的代码
}else{
	当条件 1 和 条件 2 都不为 true 时执行的代码
}

② switch语句

使用 switch 语句来选择要执行的多个代码块之一。语法结构如下:

switch(n){
	case 1:
		执行代码块 1
		break;
	case 2:
    执行代码块 2
		break;
    ...
	default:case 1case 2 不同时执行的代码
}

执行原理:首先设置表达式 n(通常是一个变量)。随后表达式的值会与结构中的每个case 的值做比较。如果存在匹配,则与该 case 关联的代码块会被执行。请使用 break来阻止代码自动地向下一个 case 运行。

③ for循环语句

for 循环的语法结构如下:

for (语句 1; 语句 2; 语句 3){
	被执行的代码块
}

④ while循环

JS中while循环也分为while和do/while循环,下面为while循环语法结构:

while (条件){
	需要执行的代码
}

while 循环会在指定条件为真时循环执行代码块。

案例1:if分支案例: 接收用户输入的成绩,判断成绩所属的等级

80~100(包括80,也包括100) 优秀
60~80(包括60,但不包括80) 中等
0~60(包括0,但不包括60) 不及格
其他值 输入有误

代码实现如下:

propmt( 参数1, 参数2 )函数:在页面上弹出一个消息输入框,接收的参数1是在弹框中显示的提示内容,用户输入的内容,会作为函数的返回值直接返回。

参数2, 是输入框中的默认值, 可以修改

var score = prompt("请输入您的成绩: ");//相当于java中动态接收用户输入
if( score >= 80 && score <=100 ){
	alert("您的成绩属于: 优秀!");
}else if( score >= 60 && score < 80 ){
	alert("您的成绩属于: 中等!");
}else if( score >= 0 && score < 60 ){
	alert("您的成绩属于: 不及格!");
}else{
	alert("您的输入有误, 请重新输入!");
}

案例2:switch语句案例—实现一个简易的计算器:

可以接收用户输入的两个数值和一个操作符(+、-、*、/),根据操作符号的不同,对两个数值执行不同的运算。

代码实现如下:

//1.接收用户输入的数值和运算符, 中间用空格分隔

var str = prompt("请输入数值1、运算符、数值2,中间用逗号分隔:","10,5,+"); //"10 + 5"

//2.对用户输入的内容进行切割(以逗号作为分隔符切割)

var arr = str.split(" ,"); // ["10", "5", "+"]

var num1 = arr[0];//数值1
var num2 = arr[2];//数值2
num1=parseFloat(num1);//将num1转成浮点型的数值,再赋值给num1
num2=parseFloat(num2);//将num2转成浮点型的数值,再赋值给num2
var opt = arr[1];//运算符

//3.通过switch分支实现计算器
switch( opt ){
	case "+":
		alert( "两个数的和为: "+( num1+num2 ) );
		break;
	case "-":
		alert( "两个数的差为: "+( num1-num2 ) );
		break;
	case "*":
		alert( "两个数的乘积为: "+( num1*num2 ) );
		break;
	case "/":
		alert( "两个数的商为: "+( num1/num2 ) );
		break;
	default:
		alert( "您输入的运算符有误, 请重新输入!" );
}

案例3:for循环语句案例

遍历1~100之间的所有整数,求1~100之间所有整数的和,并输出到控制台;

代码实现如下:

//--------------------------------------
var i = 1;
var sum = 0;
while( i <= 100 ){
    sum += i;
    i++;
}
alert( "1~100之间所有整数的和为: "+sum );
//--------------------------------------
var sum = 0;
for( var i=0; i<=100; i++){
	sum += i;
}
alert( "1~100之间所有整数的和为: "+sum );
//--------------------------------------

案例4:while循环语句案例

遍历下面数组中的元素,将元素输出到控制台。

var arr = [123, "abc", false, new Object() ];

代码实现如下:

var arr = [123, "abc", false, new Object() ];
var index = 0;
while( index < arr.length ){
	console.log( arr[index] );
	index++;
}
for( var i=0; i<arr.length; i++ ){
	console.log( arr[i] );
}

6.JS数组

Array 对象用于在单个的变量中存储多个值。

① JS数组的声明方式一

//声明一个空数组,长度为零
var arr1 = [];
//声明一个数组,并为数组设置初始值
var arr2 = ["Hello", 111, false, new Object() ];

② JS数组的声明方式二

//声明一个空数组,长度为零
var arr3 = new Array();
//声明一个数组,并为数组设置初始值
var arr4 = new Array("Hello", 111, false, new Object());

③ 数组中的细节问题

(1)JS中的数组可以存储任意类型的数据

(2)JS中的数组长度是可以被任意改变的

var arr1 = [];
console.log("此处数组的长度为: "+arr1.length ); // 0
arr1.length = 5;
console.log("此处数组的长度为: "+arr1.length ); // 5
arr1[9] = "a";
console.log("此处数组的长度为: "+arr1.length ); // 10
arr1[99] = "b";
console.log("此处数组的长度为: "+arr1.length ); // 100

7. JS函数

函数就是一个具有功能的代码块, 可以反复调用

函数就是包裹在花括号中的代码块,前面使用了关键词 function。

① JS中声明函数的方式

function 函数名称([参数列表]){
	函数体
}

或者:

var 变量名/函数名 = function([参数列表]){
	函数体
}

调用函数: 函数名称([参数列表]);

8.综合练习

(自己完成)声明一个函数fn,在函数中实现:遍历指定的两个数值(例如1和100)之间的整数,将是3的倍数的数值存入到一个数组中,并将数组返回。

1、声明fn函数

function fn(x,y){
    var arr = [];
    for(var i=x,j=0;i<y;i++){
        if(i%3==0){
        	arr[j] = i;
        	j++;
    	}
    }
    return arr;
}

2、调用fn函数, 获取1~100之间是3的倍数的数值组成的数组

var arr = fn(1,100);

3、遍历数组中的元素, 输出在网页上(提示: document.write(“输出的内容”) )

for(var i=0;i<arr.length;i++){
	document.write(arr[i]+" ");
}

三、 DOM操作

DOM: Document Object Model,文档对象模型,其实就是JS专门为访问html元素提供的一套API。

1.案例:电灯开关

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>电灯开关案例</title>
<style>
body {
	padding: 20px;
	font-size: 20px;
}

#div1 {
	width: 30%;
	border: 1px solid red;
	padding: 5px;
	margin-bottom: 20px;
}
</style>
<!-- 引入jquery的js库
<script src="js/jquery-1.8.3.min.js"></script> -->
<script>
	/** 练习:点击按钮,开灯或关灯 */
	var flag = "off";//off表示默认状态为关灯
	function changeImg() {
		//1.获取表示img元素的JS对象(对象)
		var oImg = document.getElementById("img1");
		//2.通过判断切换图片(改变src属性值)(如果灯为关,点击则开;如果为开,点击则关)
		if (flag == "off") {
			oImg.src = "imgs/on.gif";//开灯!
			flag = "on";//on表示为开灯状态
		} else {//flag="on",灯为开,点击后则关
			oImg.src = "imgs/off.gif";
			flag = "off";
		}

	}
</script>
</head>
<body>
	<input type="button" value="开/关灯" onclick="changeImg()" />
	<br />
	<br />
	<img id="img1" src="./imgs/off.gif" />

</body>
</html>

2.案例:增删改元素

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title>元素的增删改查</title>
<!-- 加入样式表 -->
<style type="text/css">
	div { border:#0099FF 2px solid;height:60px;width:350px;margin:20px 10px;padding:5px 7px;line-height:60px;font-size:26px;text-align:center;}
	#div_1{ background-color:#d400ff; }
	#div_2{ background-color:#FF3399; }
	#div_3{ background-color:#00FF00; }
	#div_4{ background-color:#FFFF66; }
	#info{ width:250px;height:90px;font-size:22px;padding:5px;resize:none;}
	input{font-size:18px;}
</style>

<!-- 引入jquery的js库
<script src="js/jquery-1.8.3.min.js"></script> -->
<script type="text/javascript">
	/** 练习1、添加元素:添加一个div元素到body中 */
	function addNode(){
		//创建一个div元素
		var div = document.createElement("div");
		div.innerHTML = "我是新来的....";
		//获取父元素(body)
		var oBody = document.body;
		//将div元素挂载到body上
		oBody.appendChild(div);
	}

	/** 练习2、删除元素: 删除id为div_2的元素 */
	//parentNode
	function deleteNode(){
		//获取要删除的元素
		var div2 = document.getElementById("div_2");
		//获取父元素
		var body = div2.parentNode;
		//解除父子关系
		body.removeChild(div2);
	}

	/** 练习3、更新元素内容:将div_3的内容更新为当前时间 */
	function updateNode(){
		//1.获取要更新的元素
		var div3 = document.getElementById("div_3");
		//2.将元素内容替换为当前时间
		div3.innerHTML = new Date().toLocaleString();
	}
</script>
</head>
<body>
	<input type="button" onclick="addNode()"
			value="创建一个div添加到body中"/>
    <input type="button" onclick="deleteNode()"
    		value="删除id为div_2的元素"/>
    <input type="button" onclick="updateNode()"
    		value="将div_3的内容更新为当前时间"/><hr/>
    <div id="div_1">
		div_1
    </div>
    <div id="div_2">
    	div_2
    </div>
    <div id="div_3">
    	div_3
    </div>
    <div id="div_4">
    	div_4
    </div>
</body>
</html>

3.案例:网页换肤

<html>
<head>
<meta charset="utf-8"/>
<title>网页换肤</title>
<style type="text/css">
	body{font-size:18px;font-family:"微软雅黑";}
	hr{border:1px solid yellow;}
	a{font-size:17px;font-weight:500;}
	a:hover {color:#0099FF;}
	h2,#change-font{text-align:center;}
	#newstext{padding:10px;margin:0 auto;letter-spacing:2px;}	
	/* 预先定一些选择器 */
	.min{
		font-size:16px;
	}
	.middle{
		font-size:18px;
	}
	.max{
		font-size:20px;
	}
	.super-max{
		font-size:24px;
	}
</style>
<!-- 引入外部的CSS文件 -->
<link rel="stylesheet" href="css/none.css" id="link"/>

<!-- 引入jquery的js库
<script src="js/jquery-1.8.3.min.js"></script> --> 
<script type="text/javascript">
	/** 练习1:执行下面的函数,切换字体大小 */
	function resize(size){
		//获取div元素
		var oDiv = document.getElementById("newstext");
		//将参数(样式)赋值给div的class属性
		oDiv.className = size;
	}
	
	
	//定义数组,存放不同的皮肤(css文件的路径)
	var styleArr = ["css/red.css","css/green.css","css/blue.css"];
	var i = 0;
	/** 练习2:执行下面的函数,为页面切换不同的皮肤 */
	function changeStyle(){
		//获取link标签(获取js对象)
		var oLink = document.getElementById("link");
		oLink.href = styleArr[i];
		i++;
		if( i>=styleArr.length ){
			i=0;
		}
	}
</script>
</head>
<body>
	<!-- style="cursor: pointer;"这个属性,当鼠标移到字体上面,会出现小手 -->
    <h2 style="cursor: pointer;">达内时代科技集团简介</h2>
    <div id="change-font">
    <!-- 当a标签的href属性值为"javascript:void(0)",
    	就会阻止超链接跳转,即点击a标签后,不会跳转到任何页面!
    	 -->
	    <a href="javascript:void(0)" 
	    	onclick="resize('min')">小字体</a> 
	    <a href="javascript:void(0)" 
	    	onclick="resize('middle')">中字体</a> 
	    <a href="javascript:void(0)" 
	    	onclick="resize('max')">大字体</a> 
	    <a href="javascript:void(0)" 
	    	onclick="resize('super-max')">超大字体</a>
	    <a href="javascript:void(0)" 
	    	onclick="changeStyle()">换肤</a>
	</div>
    <hr/>
    
    <div id="newstext" class="middle">
		<p>
			达内时代科技集团有限公司,是中国高端IT培训的领先品牌,致力于培养面向互联网、电信和金融领域的Java、C++、C#、.Net、软件测试、嵌入式、PHP、android等方面的中高端软件人才。
		</p>
		<p>
			 达内创办于2002年,专注IT职业教育17年,2014年在美国纳斯达克上市公司。目前,已开设24大课程方向,在全国70多个城市建立了330家培训中心,真正实现“一地学习,全国就业”。高薪聘请总监级名师全职授课,术业有专攻,名师出高徒。实施“因材施教,分级培优”教学方案,让每一位学员都成才,让强者更强。采用“先学习,就业后付款”的模式,已帮助80万名学员成功就业。
		</p>
		<p>
			达内优秀的教学效果和行业领先的经营模式赢得了社会各界的广泛赞誉和好评,荣获了各界权威机构的颁奖:达内成为业界唯一的一家2006200720082009连续4年入选德勤评选的 “中国高科技高成长50强公司”、“亚太地区高科技高成长500强公司”,获得首届中国留学人才归国创业“腾飞”奖、中关村管理委员会指定的“软件人才培养示范基地”、被《计算机世界》评选的“就业服务杰出贡献奖”、被《中国计算机报》评选的“最具影响力培训机构奖”、被搜狐评为“中国十大教育集团”、被腾讯评为“中国大学生心目中最具影响力的IT品牌”。
			有实力、有信誉,要培训,就选上市公司!
		</p>
    </div>

    <hr/>
</body>
</html>

4.总结

① JS获取元素

document是一个js对象,用于表示当前html网页。当浏览器加载完整个html网页后,会用document对象表示整个html网页!

document.getElementById( id值 ) – 通过元素的id值,获取一个元素。返回的是表示该元素的js对象。

<div id="div1">xxxx</div>
//获取id为div1的元素
var oDiv1 = document.getElementById("div1");
//oDiv1是一个js对象,表示获取的div元素

document.getElementsByTagName( 元素名 ) – 通过元素名称获取当前文档中的所有指定名称的元素,返回的是一个数组,其中包含了所有指定名称的元素。

document.body – 获取当前文档中的body元素

ele.parentNode – 获取当前元素的父元素。ele表示当前元素

② JS增删改元素

document.createElement( 元素名称 ) – 根据元素名称创建指定名称的元素,返回的是表示新创建元素的js对象

parent.appendChild( child ) – 通过父元素添加子元素,其中parent表示父元素,child表示子元素

parent.removeChild( child )– 通过父元素删除子元素,其中parent表示父元素,child表示子元素

ele.innerHTML – 获取当前元素的html内容(从开始标签到结束标签之间的所有内容),还可以设置当前元素的html内容(如果元素内部有内容,将会覆盖原有内容)

<div id="div1">
	这是一个div元素...
	<span>这是一个span元素</span>
</div>
//获取div元素
var oDiv1 = document.getElementById("div1");
oDiv1.innerHTML;//获取div元素的内容

第六章 jQuery

一、 jQuery简介

1.什么是jQuery

jQuery: JavaScript Query JS查询

jQuery是一门轻量的、免费开源的JS函数库(就是JS的简单框架)

jQuery可以极大的简化JS代码

jQuery的核心思想:“写的更少,但做的更多”


轻量的:是指一个技术对代码或程序的侵入程度是比较低的。

或者说代码对该技术依赖程度越低,这个技术越轻。对该技术的依赖程度越高,这个技术越重。

jQuery本质就是一个包含了很多函数的JS文件,如果要在某一个HTML中使用这个JS文件中的函数,就必须得将JS文件引入到HTML中(想使用jQuery,就得引入jQuery的函数库文件,就是一个JS文件)

2.jQuery的优势

(1) 可以极大的简化JS代码

(2) 可以像CSS选择器一样获取html元素

css中获取所有的div,给div添加样式:

div{ css属性... }

jQuery中获取所有div,给div添加边框样式:

$("div").css("border", "2px solid red");
JS获取id为div1的元素: document.getElementById("div1")
jQuery获取id为div1的元素: $("#div1")

(3) 可以通过修改css属性控制页面的效果

(4) 可以兼容常用的浏览器

比如: JS中的innerText属性、removeNode()函数、replaceNode( )函数 这些函数在某些浏览器中是无法使用的。

jQuery中提供了相应的函数( text函数、remove函数、replaceWith函数 )

常用浏览器:谷歌浏览器、火狐浏览器、苹果浏览器、欧朋浏览器等

3.jQuery引入

jQuery的函数库文件就是一个普通的JS文件,引入jQuery和引入JS文件的方式一样。

<!-- 在使用jQuery之前,必须先引入jQuery的函数库文件 -->
<script src="js/jquery-1.8.3.js"></script>

在引入jQuery函数库文件时,如果文件引入路径错误,则会导致文件引入失败,如下图:

4.文档就绪事件函数

<head>
<meta charset="UTF-8">
<!-- 在使用jQuery之前,必须先引入jQuery的函数库文件 -->
<script src="js/jquery-1.8.3.js"></script>
<script>
	//1.获取id为demo的h1元素
	var h1 = document.getElementById( "demo" );
	//2.获取h1元素中的内容( innerHTML )
	alert( h1.innerHTML );
</script>
</head>
<body>
	<h1 id="demo">jQuery的引入示例...</h1>
</body>

问题描述:上面的代码在执行时,会报一个错误:

原因描述:在执行获取id为demo的元素时, h1元素还没有被浏览器加载到,所以获取不到h1元素。

① 解决方式一

将script标签移到body内部,也就是h1元素的后面

这样浏览器在加载时,会先加载h1元素,再执行获取h1元素的代码,由于在获取h1元素之前,h1元素已经被浏览器加载过了,所以后面再获取就能够获取到!

代码示例:

<body>
	<h1 id="demo">jQuery的引入示例...</h1>
	<script>
		//1.获取id为demo的h1元素
		var h1 = document.getElementById( "demo" );
		//2.获取h1元素中的内容( innerHTML )
		alert( h1.innerHTML );
	</script>
</body>

② 解决方式二

将获取元素的代码放在文档就绪事件函数中,文档就绪事件函数会在浏览器加载完所有的html元素后(也就是加载完最后一个html元素时)会立即执行。

由于整个html网页都被加载了,h1元素肯定也被加载了,此时再获取h1元素就一定能获取到。

<head>
<meta charset="UTF-8">
<!-- 在使用jQuery之前,必须先引入jQuery的函数库文件 -->
<script src="js/jquery-1.8.3.js"></script>
<script>
	$(function(){
		//1.获取id为demo的h1元素
		var h1 = document.getElementById( "demo" );
		//2.获取h1元素中的内容( innerHTML )
		alert( h1.innerHTML );
	});
</script>
</head>
<body>
	<h1 id="demo">jQuery的引入示例...</h1>
</body>

③ 解决方式三

将获取元素的代码放在一个自定义的函数中,并将该函数绑定在h1元素的点击事件上,当我们点击h1元素时会执行自定义的函数,函数执行时才获取h1元素,此时就能够获取到h1元素。

<head>
<meta charset="UTF-8">
<!-- 在使用jQuery之前,必须先引入jQuery的函数库文件 -->
<script src="js/jquery-1.8.3.js"></script>
<script>
	function fn1(){
		//1.获取id为demo的h1元素
		var h1 = document.getElementById( "demo" );
		//2.获取h1元素中的内容( innerHTML )
		alert( h1.innerHTML );
	}
</script>
</head>
<body>
	<h1 id="demo" onclick="fn1()">jQuery的引入示例...</h1>
</body>

④ 总结:什么时候该使用文档就绪事件函数?

如果在获取元素时,获取元素的代码执行的时机,比元素本身加载的时间还要早,如果元素还没有加载就获取,必然是获取不到的。

可以将获取元素的代码放在文档就绪事件函数中,等浏览器加载完整个网页后,文档就绪事件函数才会执行,此时所有的元素都被加载了,再获取任何元素都能获取到!

jQuery提供的文档就绪事件函数(简写形式):

<script>
	$(function(){
		//在浏览器加载完整个html网页后立即执行
  });
</script>

其完整写法为:

<script>
	$(document).ready(function(){
		//在浏览器加载完整个html网页后立即执行
	})
</script>

JS也为我们提供了文档就绪事件函数,其写法为:

<script>
	window.onload = function(){
		//在浏览器加载完整个html网页后立即执行
	}
</script>

二、 jQuery选择器(重点掌握)

1.基本选择器

(1)元素名选择器
$("div") -- 选中所有的div元素
$("span") -- 选中所有的span元素

(2)class/类选择器
$(".s1") -- 选中所有class值为s1的元素(class值为s1的元素可能是任何元素)
$("span.s1") -- 选中所有class值为s1的span元素

(3)id选择器
$("#one") -- 选中id为one的元素

(4)多元素选择器
$("div,span,.s1,#one") -- 选中所有的div元素,以及所有的span元素,以及所有class值为s1的元素,以及id为one的元素

2.层级选择器

$("div span") -- 选中所有div内部的所有span元素
$("#one span") -- 选中id为one的元素内部的所有span元素

$("#two+span") -- 选中id为two的元素后面紧邻的span兄弟元素
$("#two").next("span") -- 选中id为two的元素后面紧邻的span兄弟元素
$("#two").prev("span") -- 选中id为two的元素前面紧邻的span兄弟元素

$("#two~span") -- 选中id为two的元素后面所有的span兄弟元素
$("#two").nextAll("span") -- 选中id为two的元素后面所有的span兄弟元素
$("#two").prevAll("span") -- 选中id为two的元素前面所有的span兄弟元素

$("#two").siblings("span") -- 选中id为two的元素前、后所有的span兄弟元素

3.基本过滤选择器

(1) 选中第一个div元素
$("div:first")
$("div:eq(0)")
$("div").eq(0)

(2) 选中最后一个div元素
$("div:last")
$("div:eq(-1)")
$("div").eq(-1)

(3) 选中第n+1个div元素(n从零开始)
$("div:eq(n)")
$("div").eq(n)

4.选择器练习

<!DOCTYPE>
<html>
<head>
<meta charset="utf-8"/>
<title>选择器练习</title>
<style type="text/css">
	body{ font-family:"微软雅黑"; font-size:20px;padding-bottom:300px;}
	input{font-size:18px;margin-top:10px;}
	div,span{width:300px;border:1px solid #000;padding-left:10px;background:#bed4ef;;}
	span{display:block;}
	body>div,body>span{height:100px;margin:10px 0px 0px 20px;display:inline-block;vertical-align:middle;}
	#one{width:300px;height:185px;}
	div>span,div>div{width:250px;height:35px;margin:10px;}
</style>

<!-- 引入jquery函数库文件 -->
<script src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript">
	/* 文档就绪事件函数(即在浏览器加载完最后一个html元素后立即执行) */
	$(function() {
		/* -------一、基本选择器练习------- */
		/* 1、选中id为b1的按钮,为b1按钮绑定点击事件,点击b1按钮:改变所有 div 元素的背景色为 #FD5551 */
		$("#b1").click(function() {
			$("div").css("background-color","#FD5551");
			//	css()函数可以同时设置多个样式
			/*$("div").css({
				"background-color":"#FD5551",
				"border":"3px solid blue",
				"color":"green"
				"font-size":"20px"
			});*/
		});
		/* 2、选中id为b2的按钮,为b2按钮绑定点击事件,点击b2按钮:改变 id 为 one 的元素的背景色为 #91BF2F */
		$("#b2").click(function() {
			$("#one").css("background-color","#91BF2F");
		});
		/* 3、选中id为b3的按钮,为b3按钮绑定点击事件,点击b3按钮:
		改变 class 为 mini 的所有元素的背景色为 #EE82EE */
		$("#b3").click(function() {
			$(".mini").css("background-color","#EE82EE");
		});
		/* ---------二、层级选择器------- */
		/* 4、选中id为b4的按钮,为b4按钮绑定点击事件,点击b4按钮:改变 div 内所有 span 的背景色为 #DC21D2 */
		$("#b4").click(function() {
			$("div span").css("background-color","#DC21D2");
		});
		/* 5、选中id为b5的按钮,为b5按钮绑定点击事件,点击b5按钮:改变 id为two 元素的下一个相邻的 div元素 的背景色为 #2CADAA */
		$("#b5").click(function() {
			$("#two+div").css("background-color","#2CADAA");
		});
		/* 6、选中id为b6的按钮,为b6按钮绑定点击事件,点击b6按钮:改变 id为two 元素的后面所有的div兄弟元素 的背景色为 #ECD822 */
		$("#b6").click(function() {
			$("div~span").css("background-color","#ECD822");
		});
		/* 7、选中id为b7的按钮,为b7按钮绑定点击事件,点击b7按钮:改变 id为two 元素的前、后所有的div兄弟元素 的背景色为 #EE0077 */
		$("#b7").click(function() {
			$("#two").siblings("div").css("background-color","#EE0077");
		});
		/* ---------三、基本过滤选择器------- */
		/* 8、选中id为b8的按钮,为b8按钮绑定点击事件,点击b8按钮:改变第一个以及最后一个 div 元素的背景色为 #0074E8 */
		$("#b8").click(function() {
			$("div:first").css("background-color","#0074E8");
			$("div:last").css("background-color","#0074E8");
		});
		
		/* 9、选中id为b9的按钮,为b9按钮绑定点击事件,点击b9按钮:改变第4个 div 元素的背景色为 #D917C6 */
		$("#b9").click(function() {
			$("div").eq(3).css("background-color","#D917C6");
		});
	});
</script>
</head>

<body>
	基本选择器→:
	<!-- 按钮,id为b1 -->
	<input type="button" id="b1" value="b1,改变所有 div 元素的背景色为 #FA8072" />
	<!-- 按钮,id为b2 -->
	<input type="button" id="b2" value="b2,改变 id 为 one 的元素的背景色为 #9ACD32"/>
	<!-- 按钮,id为b3 -->
	<input type="button" id="b3" value="b3,改变 class 为 mini 的所有元素的背景色为 #EE82EE"/>
	<hr/>
	层级选择器→:
	<!-- 按钮,id为b4 -->
	<input type="button" id="b4" value="b4,改变 div 内所有 span 的背景色为 #7CFC00"/>
	<!-- 按钮,id为b5 -->
	<input type="button" id="b5" value="b5,改变 id为two 元素的下一个相邻的 div元素 的背景色为 #48D1CC"/>
	<!-- 按钮,id为b6 -->
	<input type="button" id="b6" value="b6,改变 id为two 元素的后面所有的div兄弟元素 的背景色为 #D2FA7E"/>
	<!-- 按钮,id为b7 -->
	<input type="button" id="b7" value="b7,改变 id为two 元素的前、后所有的div兄弟元素 的背景色为 #FF69B4"/>
	<hr/>
	基本过滤选择器→:
	<!-- 按钮,id为b8 -->
	<input type="button" id="b8" value="b8,改变第一个/最后一个 div 元素的背景色为 #1E90FF"/>
	<!-- 按钮,id为b9 -->
	<input type="button" id="b9" value="b9,改变第4个 div 元素的背景色为 #EA3AD8"/>

	<h3>点击按钮查看效果...</h3>

	<div id="one">
		这是一个div1
		<div class="one01">这是一个div11</div>
		<span class="mini">这是一个span,class为mini</span>
		<span class="mini">这是一个span,class为mini</span>
	</div>
	<div>这是一个div2
		<input type="button" value="按钮1"/>
		<input type="button" value="按钮2"/>
	</div>
	<div id="two">这是一个div3,id是two
		<span>这是一个span</span>
	</div>
	<div>这是一个div4</div>
	<div>这是一个div5</div>
	<span class="mini">这是一个span,class为mini</span>
	<div>这是一个div6</div>
	<span class="mini01">这是一个span,class为mini01</span>
	<span class="mini">这是一个span,class为mini</span>

</body>
</html>




三、 综合案例

1.创建(单行单列的表格、5行6列的表格、指定行和列的表格)

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title>创建表格</title>

<style type="text/css">
	/* 为表格定义CSS样式 */
	body{padding-left:15px;font-size:20px;}
	table{ border:1px solid #0099FF; width:70%; border-collapse:collapse; }
	table td{ border:#0099FF 1px solid; padding:10px 20px 3px 1px; }
	input[type='text']{width:150px;height:20px;vertical-align:text-bottom;text-indent:5px;font-size:20px;}
	input[type='button']{font-size:20px;}
</style>

<!-- 引入jquery函数库文件 -->
<script src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript">
	/** 练习1:创建单行单列的表格 */
	function createTable1(){
		//创建table元素
		var $table = $("<table></table>");
		//创建tr元素
		var $tr = $("<tr></tr>");
		//创建td元素并为td添加内容
		var $td = $("<td>Hello,我是td</td>");
		//将td添加到tr上
		$tr.append($td);	
		//将tr添加到table上
		$table.append($tr);
		//将创建的整个table添加到body内部
		$("body").append($table);
		$("body").append("<hr/>");
	}

	/** 练习2:创建5行6列的表格 */
	function createTable2(){
		//创建table元素
		var $table = $("<table></table>");
		for(var r=0;r<5;r++){//循环5次,table中添加5个tr
			//创建tr元素
			var $tr = $("<tr></tr>");
			for(var c=0;c<6;c++){//循环6次,tr中添加6个td
				//创建td元素并为td添加内容
				var $td = $("<td>Hello,我是td</td>");
				//将td添加到tr上
				$tr.append($td);	
			}
			//将tr添加到table上
			$table.append($tr);
		}
		//将创建的整个table添加到body内部
		$("body").append($table);
		$("body").append("<hr/>");
	}

	/** 练习3:创建指定行和列的表格 */
	function createTable3(){
		var rows = $("#rows").val();//获取指定的行数
		var cols = $("#cols").val();//获取指定的列数
		//创建table元素
		var $table = $("<table></table>");
		for(var r=0;r<rows;r++){//循环5次,table中添加5个tr
			//创建tr元素
			var $tr = $("<tr></tr>");
			for(var c=0;c<cols;c++){//循环6次,tr中添加6个td
				//创建td元素并为td添加内容
				var $td = $("<td>Hello,我是td</td>");
				//将td添加到tr上
				$tr.append($td);	
			}
			//将tr添加到table上
			$table.append($tr);
		}
		//将创建的整个table添加到body内部
		$("body").append($table);
		$("body").append("<hr/>");
	}
</script>
</head>
<body>
	<!-- 练习1:点击下列按钮创建单行单列表格 -->
	<input type="button" value="创建单行单列表格" onclick="createTable1()" /><br/><br/>

	<!-- 练习2:点击下列按钮创建56列表格 -->
	<input type="button" value="创建表格(5行6列)" onclick="createTable2()" /><br/><br/>

	<!-- 练习3:点击下列按钮创建指定行、指定列的表格 -->
	<input type="button" value="创建表格(输入行数和列数)" onclick="createTable3()" /><br/>
	行数:<input type="text" id="rows"/><br/>
	列数:<input type="text" id="cols"/><br/><br/>

	<!-- 将创建的表格添加到body内部(追加到最后) --><hr/>
</body>
</html>

2.仿QQ好友列表

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title>仿QQ好友分组</title>
<style type="text/css">
	table{border:#09f 1px solid;width:100px;border-collapse:collapse;margin:15px 10px;width:10%;margin:20px 20px;}
	table td{border:#06f 1px solid;background-color:#6f5f3e;text-align:center;padding:5px 0;}
	table td div{background-color:#ff9;text-align:left;line-height:28px;padding-left:14px;text-indent:padding-left:20px;}
	table td span{color:#fff;font-size:19px;width:100%;border:none;display:block;cursor:pointer;}
	table td a:hover{color:#0c0}
</style>

<!--引入jquery的js库-->
<script src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript">
	/** 通过jQuery实现仿QQ列表好友列表
		this是保留字,不能作为参数名或变量名
		thisobj.next()会报错,因为thisobj是js对象,next是jQuery对象
		thisobj(js对象)--->转成jQuery对象$(thisobj)
	*/
	function openDiv(thisobj){
		//=======方式一:=========================
			//在打开当前分组之前,先关闭其他三个分组(将其他三个div设置为隐藏)
			//获取其他三个div(选中所有span,用not排除当前点击的span就获得其他三个span)
			
			/*	next("div")获取span元素下一个div
				css("display","none")设置为隐藏,可以简写成hide()是将元素有显示设置为隐藏,里面可以传数字,有动画效果
				show()将元素由隐藏设置为显示,里面可以传数字,有动画效果
				*/
			$("span").not(thisobj).next("div").hide();
			
			//获取被点击sapn(分组名)相邻的div(好友列表)元素
			var $div=$(thisobj).next("div");
			//	切换div的显示状态(如果div是隐藏的,则切换为显示;如果div是显示的,则切换为隐藏)
			$div.toggle();//toggle()里面可以传数字,有动画效果
			
			
		//=======方式二:=========================	
			/*获取别点击sapn(分组名)相邻的div(好友列表)元素
			var $div=$(thisobj).next("div");
			//获取当前div状态(none,表示分组是关闭的,block表示分组是展开的)
			var display= $div.css("display");//获取display的值
			if(display=="none"){//表示分组是关闭的,此时需要展开
			$div.css("display","block");
			}else {//block ,表示分组是展开的,此时需要关闭
			$div.css("display","none");
			}
		*/
	}
	
</script>
</head>
<body>
<table>
	<tr>
		<td>
			<span onclick="openDiv(this)">君王好友</span>
			<div style="display:none"><!-- style="display:none"表示隐藏元素 -->
				秦始皇<br />
				刘邦<br />
				李世民<br />
				康熙<br />
			</div>
		</td>
	</tr>
	<tr>
		<td>
			<span onclick="openDiv(this)">三国好友</span>
			<div style="display:none">
				刘备<br />
				关羽<br />
				张飞<br />
				赵云<br />
			</div>
		</td>
	</tr>
	<tr>
		<td>
			<span onclick="openDiv(this)">美女好友</span>
			<div style="display:none">
				西施<br />
				貂蝉<br />
				杨贵妃<br />
				王昭君<br />
			</div>
		</td>
	</tr>
	<tr>
		<td>
			<span onclick="openDiv(this)">IT好友</span>
			<div style="display:none">
				孙悟空<br />
				马云<br />
				李开复<br />
				俞敏洪<br />
			</div>
		</td>
	</tr>
</table>
</body>
</html>

3.模拟员工信息管理系统

<!DOCTYPE HTML>
<html>
<head>
<title>模拟员工信息管理系统</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<style type="text/css">
	body{font-family: "微软雅黑"}
	h2, h4{ text-align: center; }
	div#box1, div#box2 {text-align:center;}
	hr{margin: 20px 0;}
	table{margin: 0 auto;width:70%;text-align: center;border-collapse:collapse;}
	td, th{padding: 7px; width:20%;}
	th{background-color: #DCDCDC;}
	input[type='text']{width:130px;}
</style>
<!--引入jquery的js库-->
<script src="js/jquery-1.8.3.js"></script>
<script type="text/javascript">
	/* --- 添加一个新员工 --- */
	function addEmp(){
		//1.获取要添加员工的id、name、email、salary
		//trim()是JQuery提供的函数,可以去除字符串两端的空格
		var id = $("#box1").find("input[name='id']").val().trim();
		var name = $("#box1").find("input[name='name']").val().trim();
		var email = $("#box1").find("input[name='email']").val().trim();
		var salary = $("#box1").find("input[name='salary']").val().trim();

		//2.检查员工信息是否符合格式(信息不能为空)
		if( id == "" || name == "" || email == "" || salary == ""){
			alert("员工信息不能为空!!!");
			return;//提前结束函数的执行!
		}
		//3.检查ID是否已存在
		/* 获取当前所有员工的id, 循环遍历(用each函数),
		this是表示当前的tr,因this是js对象,所有要转成jQuery对象$(this), 比较id是否存在
		*/
		//假设id是不存在的,默认值是false
		
		var idExists = false;
		$("table tr").each(function(){//$(this)是当前正在遍历的tr
			//获取当前行中的id值(也就是获取当前tr行中所有td中第2个td元素的内容)
			var _id=$(this).find("td:eq(1)").html();
		//拿着用户输入的id,和当前行中的id进行比较	
		if(id == _id){
				alert("id已存在,添加失败!!!");
				idExists = true;//true表示id已存在
				//return;注意:只能结束当前所在的function()函数
			}
		});
		if(idExists){//true表示id已存在
			return;
		}
		//4.将新员工信息添加到员工信息列表中(table)
		var $td1 = $("<td><input type='checkbox'/></td>");
		var $td2 = $("<td>"+id+"</td>");
		var $td3 = $("<td>"+name+"</td>");
		var $td4 = $("<td>"+email+"</td>");
		var $td5 = $("<td>"+salary+"</td>");
		var $tr = $("<tr></tr>");
		$tr.append($td1).append($td2).append($td3).append($td4).append($td5);

		//5.将tr挂载到table上
		$("table").append($tr);
	}
	/* --- 删除选中的员工 --- */
	function delEmp(){
		//1.获取被选中的员工
		//$("input[type='checkbox']")--匹配所有的复选框
		//$("input[type='checkbox']:checked")--匹配所有被选中的复选框
		$("input[type='checkbox']:checked").parent("td").parent("tr").each(function(){
			//如果当前行是表头, 不要删除
			if($(this).find("th").length == 0){
				$(this).remove();
			}
		});
	}
	/* --- 修改指定id的员工 --- */
	function updEmp(){
		//1.获取要修改的员工信息
		var id = $("#box2").find("input[name='id']").val().trim();
		var name = $("#box2").find("input[name='name']").val().trim();
		var email = $("#box2").find("input[name='email']").val().trim();
		var salary = $("#box2").find("input[name='salary']").val().trim();
		//2.检查员工信息是否符合格式(不能为空)
		if(id == "" || name == "" || email == "" || salary == ""){
			alert("修改员工信息不能为空!!!");
		}
		//3.检查ID是否存在(必须要存在)
		var flag = true;
		$("table tr").each(function(){
			if(id == $(this).find("td:eq(1)").text()){
				//4.进行修改
				//>>修改姓名
				$(this).find("td:eq(2)").text(name);
				//>>修改邮箱
				$(this).find("td:eq(3)").text(email);
				//>>修改工资
				$(this).find("td:eq(4)").text(salary);
				flag = false;
			}
		});
		if(flag){
			alert("您修改的员工id不存在!!!");
		}
	}
	/* 点击全选设置 员工所在的行全选或者全不选 */
	function checkAll(){
		//获取全选复选框的选中状态(true表示选中,false表示取消选中)
		var isCheck = $("#all").prop("checked");//prop("xxx")获取值
		//获取其他普通复选框(除全选以外),将全选框的状态设置给其他复选框
		$("input[type='checkbox'][id!='all']").prop("checked", isCheck);//prop("xxx","xxx")设置值
	}
</script>
</head>

<h2>添加新员工</h2>
<div id="box1">
	ID:<input type="text" name="id"/>
	姓名:<input type="text" name="name"/>
	邮箱:<input type="text" name="email"/>
	工资:<input type="text" name="salary"/>
	<input type="button" onclick="addEmp()" id="add" value="添加新员工"/>
</div>
<hr/>
<table border="1">
	<tr>
		<th>
			<!-- 全选复选框 -->
			<input type="checkbox" onclick="checkAll()" id="all"/>
		</th>
		<th>ID</th>
		<th>姓名</th>
		<th>邮箱</th>
		<th>工资</th>
	</tr>
	<tr>
		<td>
			<input type="checkbox"/>
		</td>
		<td>1</td>
		<td>宋江</td>
		<td>sj@163.com</td>
		<td>12000</td>
	</tr>
	<tr>
		<td>
			<input type="checkbox"/>
		</td>
		<td>2</td>
		<td>武松</td>
		<td>ws@163.com</td>
		<td>10500</td>
	</tr>
	<tr>
		<td>
			<input type="checkbox"/>
		</td>
		<td>3</td>
		<td>孙二娘</td>
		<td>sen@163.com</td>
		<td>11000</td>
	</tr>
</table>
<h4><a href="javascript:void(0)" onclick="delEmp()" id="del">删除选中员工</a></h2>
<hr/>

<div id="box2">
	ID:<input type="text" name="id"/>
	姓名:<input type="text" name="name"/>
	邮箱:<input type="text" name="email"/>
	工资:<input type="text" name="salary"/>
	<input type="button" onclick="updEmp()" id="upd" value="根据ID修改员工信息"/>
</div>
</body>
</html>

四、 jQuery总结

1.html元素操作

① 创建元素

$("<div></div>") -- 创建一个div元素,返回的是一个jQuery对象,表示创建的div元素
$("<div>xxxx</div>") -- 创建一个包含内容的div元素,返回的是一个jQuery对象,表示创建的div元素

② 添加子元素

$parent.append( $child ) -- 父元素调用方法添加子元素
$("body").append( "<div>我是新来的...</div>" ); -- 往body元素内部追加一个div子元素

③ 删除元素

$("div").remove() -- 删除所有的div元素

JS删除所有div元素:
//获取所有的div元素(返回的是所有div组成的数组)
var divArr = document.getElementsByTagName("div"); //div数组
//遍历div数组,依次删除每一个div元素
var len = divArr.length;
for(var i=0;i<len;i++){
    //通过当前元素的父元素删除当前元素(始终删除第一个)
    divArr[0].parentNode.removeChild( divArr[0] );
}

④ 替换元素

$("div").replaceWith("<p>我是来替换的…</p>")

2.html元素内容和值的操作

① html()函数 (类似于js中的innerHTML属性)

<div>
  	这是一个div11元素
    <span>这是一个span元素</span>
    这是一个div1111元素
</div>

用于获取或设置元素的内容,比如为div、span、p、h1~h6、table、tr、td、form等元素设置内容

$("div").html() -- 获取所有div中的第一个div的内容
$("div").html("xxxx") -- 为所有div设置内容

② text()函数 (类似于js中的innerText属性,innerText在部分浏览器中不兼容)

用于获取或设置元素的文本内容

$("div").text() -- 获取所有div中的所有文本内容
$("div").text("xxxx") -- 为所有div设置文本内容

③ val()函数 (类似于js中的value属性)

获取或设置表单项元素的value值(input/select/option/textarea)

$("input").val() -- 获取所有input元素中的第一个input元素的value值
$("input").val() -- 为所有的input元素设置value值

3.元素属性和css属性操作

<input type="text" name="username" id="inp"/>

① prop()函数 : 用于获取或设置元素的属性值

在jQuery1.6版本之后才有这个函数,1.6之前版本的jQuery可以使用attr()函数

$("input[type='checkbox']").prop("checked")
-- 获取input复选框的选中状态, 返回true表示复选框为选中状态,返回false表示复选框为取消选中状态
$("input[type='checkbox']").prop("checked", true)
-- 设置所匹配的复选框元素为选中状态

$("#inp").prop("name"); //获取id为inp元素的name属性值, 返回useranme
$("#inp").prop("name","user"); //为id为inp的元素设置name属性值, name属性值就会变成user

② css()函数: 用于获取或设置元素的css属性值

$("#div1").css("width") -- 获取id为div1元素的宽度
$("#div1").css("width","200px") -- 设置id为div1元素的宽度为200px
$("#div1").css({
	"width" : "200px",
	"height" : "150px",
	"border" : "5px solid red",
	"background" : "pink"
}); // 为id为div1的元素设置宽度为200px、高度为150px、边框以及背景颜色等样式

③ 其他函数

1、each() 函数

$(selector).each(function( index,element ){})
-- each()函数可以遍历$(selector)选择器选中的所有元素(即每次都选择器选中的元素中获取一个元素,并执行function 函数)
-- function中的index -- 表示遍历的元素的下标
-- function中的element -- 表示当前正在遍历的元素(也可以通过this获取)

2、show()/hide() 函数

show() – 设置元素由隐藏变为显示

hide() – 设置元素由显示变为隐藏

$("div").show() -- 设置所有的div元素为显示

等价于:

$("div").css("display", "block");
$("div").hide() -- 设置所有的div元素为隐藏

等价于:

$("div").css("display", "none")

2、toggle()/slideToggle()

toggle() – 切换元素的显示状态, 如果元素是显示的, 则切换为隐藏, 否则切换为显示

slidToggle() --切换元素的显示状态, 如果元素是显示的, 则切换为隐藏,否则切换为显示,切换为显示为下拉状态,隐藏为收缩状态。

五、扩展内容

1.为元素绑定点击事件

① 方式1(js版)

<script>
  function fn(){
    alert("input按钮被点击了...");
  }
</script>
<body>
	<input onclick="fn()" type="button" value="点我~!" />
</body>

② 方式2(js版)

<script>
	window.onload = function(){
		//获取id为btn的元素
		var btn = document.getElementById("btn");
		btn.onclick = function(){
			alert("input按钮被点击了...");
		}
	}
</script>
<body>
	<input id="btn" type="button" value="点我~!" />
</body>

③ 方式3(jQuery版):

<script>
$(function(){
    //当点击btn按钮时,触发点击事件执行其中的函数
    $("#btn").click( function(){
        alert("input按钮被点击了...");
    });
});
</script>
<body>
	<input id="btn" type="button" value="点我~!" />
</body>

2.js对象和jQuery对象的互相转换

通过JS的方式获取元素,返回的是JS对象,JS对象只能调用JS的属性或函数

通过jQuery选择器获取元素,返回的是jQuery对象(结构像一个集合),jQuery对象只能调用jQuery的属性或函数。

如果现有JS对象,但我们需要调用jQuery的属性或函数,可以将JS对象转成jQuery对象;

如果现有jQuery对象,但我们需要调用JS的属性或函数,可以将jQuery对象转成JS对象;

第七章 tomcat、HTTP

一、 服务器概述

1.什么是服务器?

服务器:分为服务器硬件 和 服务器软件。在硬件服务器(计算机)上安装了服务器软件,才可以对外提供服务。

比如:让其他的计算机来访问当前服务器,为其他的计算机提供服务。

① 服务器硬件:

是指在互联网上具有独立IP地址的计算机,比如我们自己用的计算机也可以作为服务器使用。

② 服务器软件:

就是一个计算机程序,比如MySQL服务器软件,tomcat服务器软件。服务器软件分为很多类型,比如:ftp服务器,数据库服务器,web服务器软件,邮件服务器等。

2.什么是Web服务器?

(1) web服务器是指驻留在互联网上的某种类型的计算机程序。当浏览器访问服务器,请求服务器上的文件时,服务器将会处理该请求,并将请求的文件响应给浏览器,并会附带一些信息告诉浏览器如何查看该文件(即文件的类型)

(2) web服务器是可以向 “发出请求的浏览器提供文档” 的程序,比如在访问百度时,其实就是在访问百度的服务器。

tomcat就是一个web服务器软件,是由apache组织提供的一款服务器软件,特点:小巧灵活,免费开源,简单易用。

二、 tomcat下载、安装、启动、配置

1.下载tomcat服务器

① 下载地址:

tomcat服务器下载

② tomcat版本:

有解压版 和 安装版,还分windows (还分为32位和64位版)和
linux版,根据自己的需求,选择对应的版本下载。

tomcat服务器运行需要jdk的支持,版本对应为:

tomcat5 需要jdk4以上支持
tomcat6 需要jdk5以上支持
tomcat7 需要jdk6以上支持
tomcat8 需要jdk7以上支持

2.安装、启动tomcat服务器

① 安装tomcat服务器

绿色版解压之后就可以使用(原则:安装的路径中不要包含中文和空格)

解压后还需要配置JAVA_HOME环境变量,该变量指向jdk的根目录,指定tomcat启动时使用哪一个位置的jdk。

② 启动tomcat服务器

如何配置JAVA_HOME环境变量:

变量名: JAVA_HOME

变量值: C:\Program Files\Java\jdk1.8.0_45

--------------------------------------------------------------------------------

(扩展内容)配置Path变量的两种方式:

方式一:

Path=**C:\Program Files\Java\jdk1.8.0_45\bin;**xxx;xxx;xxx;

方式二:

JAVA_HOME=C:\Program Files\Java\jdk1.8.0_45

Path=%JAVA_HOME%\bin;xxx;xxx;xxx;

--------------------------------------------------------------------------------

启动、关闭tomcat服务器:

通过 [tomcat根目录]\bin\startup.bat 可以启动tomcat服务器;

通过 [tomcat根目录]\bin\shutdown.bat 可以关闭tomcat服务器;

访问测试服务器:

在tomcat服务器启动后,服务器会默认监听8080端口,可以通过如下地址访问tomcat服务器的主页:

http://localhost:8080

③ 修改tomcat默认端口


tomcat服务器在启动时,默认监听的端口是8080,这意味着,我们在访问tomcat服务器时,就需要在主机名(localhost)或者IP地址(127.0.0.1)等后面加上端口。这样非常不方便。

可以将8080端口改为80端口,因为80端口非常特殊,可以省略不写(只有80端口可以省略,其他端口可在访问时必须得加上)

修改方法:找到 [tomcat安装目录]/conf/server.xml 文件并打开该文件,将文件中的 69 行的 <Connector> 标签上的 port 属性的值改为 80即可。

<Connector port="80" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

改完后,保存文件,重新启动服务器(只有在服务器启动时,才会重新加载server.xml文件)再次启动的服务器就会监听新的端口。

3.端口占用问题

在启动tomcat服务器时,可能会遇到端口占用问题,如下图:

原因分析:

① 情况一:

可能是之前的tomcat服务器没有完全关闭,仍然在占用80端口,导致服务器再次启动时,启动失败。

解决方式:运行shutdown.bat文件,将tomcat按照正常流程再关闭一次即可。如果再次启动服务器成功,说明问题已解决,否则看情况二。

② 情况二:

可能是其他程序占用了80端口,导致服务器启动失败。

解决方式:打开一个cmd窗口,通过 netstat -ano 命令查看当前系统中活动的进程,找到80端口对应的进程编号(PID),根据进程编号将进程结束即可!

netstat -ano
协议  本地地址         外部地址        状态           PID
TCP  0.0.0.0:80      0.0.0.0:0      LISTENING     6520
TCP  0.0.0.0:135     0.0.0.0:0      LISTENING     448
...
taskkill /f /pid 进程编号

三、 tomcat目录结构

1.tomcat目录结构介绍


tomcat服务器安装根目录下有很多子目录,这些目录的作用是:

bin:用于存放tomcat服务器中批处理文件的目录(xx.bat/xx.sh)
conf:用于存放tomcat服务器中的配置文件的目录(其中server.xml文件是tomcat服务器中非常重要的一个文件。)
lib:用于存放tomcat服务器运行时所依赖的jar包。
logs:用于存放tomcat服务器运行时产生的日志文件(启动tomcat服务器时会打印很多日志信息,这些日志信息还会以文件形式保存到logs目录下)
temp:用于存放tomcat服务器产生的临时文件,tomcat会自己清理,可以忽略该目录

webapps:是localhost【虚拟主机】默认管理的目录,将开发好的【web应用】程序放在webapps目录下,就可以通过浏览器访问localhost主机中的Web资源文件了。
可以简单的理解为:webapps目录就是web资源(html、css、js、图片、jsp等)的存放目录,将web资源文件放在该目录下,就可以通过浏览器来访问。

work:用于存放tomcat服务器产生的工作文件(JSP翻译后的Servlet文件会放在work目录下;session对象序列化后产生的文件也会放在work目录下;)

四、 虚拟主机


总结:

(1)虚拟主机就是tomcat服务器中配置的一个站点,在tomcat服务器中默认提供了一个localhost虚拟主机,这个主机的发布目录是webapps目录:

这样意味着,将Web应用放在webapps目录下,就表示发布到了localhost虚拟主机中。

(2)Web应用就是一个存放了很多Web资源(html、css、js、jsp、servlet、图片等)的目录,将Web应用发布到虚拟主机中,就可以通过浏览器来访问Web应用中的这些资源文件了。

五、 web应用

1.web应用的目录结构

news(目录,web应用)
  |-- 其他目录, 放在news根目录或者其他目录中的资源文件,浏览器可以直接访问
  |-- WEB-INF目录, 放在这个目录下的资源文件是受保护的,浏览器不能直接访问(不是不能访问,是不能直接访问) 
  		 |-- classes目录, 用于存放编译后的class文件
  		 |-- lib目录, 用于存放web应用所依赖的jar包
  		 |-- web.xml文件, 用于存放和web应用相关的一些配置(配置Servlet、配置主页、配置session的超时时间等)

其中news就是一个目录, 同时也是一个web应用程序, 其中可以包含很多的资源文件。

2.部署web应用到虚拟主机中

直接将Web应用的目录拷贝到虚拟主机所管理的目录下,就发布到了虚拟主机中

例如:将news目录拷贝webapps目录下,由于webapps目录是localhost主机默认管理的目录,所以就相当于将news应用发布到 了localhost主机中。

通过如下路径规则就可以访问localhost主机下的news应用下的资源文件:

http://localhost:端口/news/xxx

六、 扩展内容

1.配置WEB应用的主页

如果没有将 hello.html 配置为当前Web应用的主页,在访问 hello.html 时的路径为:

http://localhost/news/hello.html

如果将 hello.html 配置为当前Web应用的主页,再次访问 hello.html 时的路径为:

http://localhost/news

在上的路径中,/hello.html 这个路径可以加,也可以省略。

将 hello.html 配置为当前应用的主页,方式为:找到 [当前Web应用]/WEB-INF/web.xml文件并打开,在web.xml文件的根标签内部添加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp/xml/ns/javaee"
  xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp/xml/ns/javaee
                      http://xmlns.jcp/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">
  	<welcome-file-list>
			<welcome-file>/hello.html</welcome-file>
			<welcome-file>/hello1.html</welcome-file>
			<welcome-file>/hello2.html</welcome-file>
  	</welcome-file-list>

</web-app>

配置完后,需要重启服务器,配置才会生效。

2.配置缺省的(默认的)WEB应用

上面已经将news/hello.html配置为news应用的主页,访问hello.html时路径为:http://localhost/news/

如果不将 news 应用配置为默认的Web应用,在访问 news 下的 hello.html(主页)时的访问路径为:

http://localhost/news/

如果将 news 应用配置为缺省的(默认的)Web应用,在访问 hello.html(主页)时的路径就变成了:

http://localhost/

如何将 news 配置为缺省的(默认的)Web应用(缺省Web应用只能有一个)?:

将Web应用的名字改为一个大写的ROOT,当前Web应用就是一个默认的Web应用,再访问这个Web应用下的资源文件时,访问的路径中就可以不用写Web应用的名称了。

3.打war包

war包和jar包都是java程序中的一种压缩包格式,如果开发一个Java基础工程,可以将其中编译后的class文件和相关的配置文件打成一个jar包。

可以将一个Web应用打成一个war包,这样做的好处有:

(1) 将web应用打成一个war包,传输起来更加方便,而且文件体积会变小。
(2) 将war包发布到服务器中,服务器能识别war包格式,会自动将war包解压发布!

打war包的方法是:

进入到web应用的目录下,将Web应用目录下的所有子目录和文件全部选中,右键压缩成一个 xxx.zip 包,再把 xxx.zip 改为 xxx.war即可!!

需要注意的是,如果有以下问题,可能会导致war不会自动解压:

(1) 在将Web应用打成一个压缩包时,没有打成一个 xxx.zip,而是使用 rar格式或者其他格式,会导致自动解压失败!
(2) 打成的war包的名字 和 已发布的web应用的名字不能冲突,会导致自动解压失败!
(3) war包中包含的目录名和文件名是中文的,也会导致自动解压失败!

4.配置缺省Web应用、配置Web应用的主页、打war包

七、 HTTP协议概述

1.什么是HTTP协议?


HTTP协议是用于规定浏览器和服务器之间的通信方式/规则

主要规定了浏览器给服务器发送的请求信息的格式

以及规定了服务器给浏览器发送响应信息的格式

HTTP协议在工作时所遵循的基本原则:

(1)一次请求,只对应一次响应
(2)请求只能由浏览器发起,服务器只能被动的等待请求,根据请求作出回应。

八、 HTTP协议详解

IE浏览器的插件:HttpWatch,可以监听浏览器和服务器通信的内容。

1.HTTP请求

① 请求行

GET  /news/hello.html  HTTP/1.1

(1)GET:表示请求方式,在HTTP协议中一共定义了7种提交方式,但是我们只用GET和POST。

(2)/news/hello.html:请求资源路径,表示浏览器请求的是哪一个Web应用以及哪一个web资源文件。

(3)HTTP/1.1:请求所遵循的协议和版本。

② 若干请求头

请求头都是Key-Value结构,例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ANVW7dVD-1593771227261)(JAVAWEB-NOTE02.assets/image-20200219143453111.png)]

Host:localhost -- 通知服务器,浏览器要请求的是哪一台虚拟主机。
Accept:text/html, appliaction/xhtml+xml,...  -- 通知服务器,浏览器能接收的响应数据类型。
...

(空白行,用于分隔请求头和请求实体)

③ 请求实体内容

如果请求方式为 GET 提交,请求实体是没有内容的!

如果请求方式为 POST 提交,并且请求中携带了数据,请求实体中才会有内容!

2.HTTP响应

① 状态行

HTTP/1.1 200 OK

(1)HTTP/1.1:表示响应所遵循的协议和版本

(2)200:状态码,三位的数字,表示服务器对请求处理的结果。

	200: 表示请求处理成功
	302: 表示请求重定向(即需要再进一步请求才可以获取到相应的资源)
	304/307: 表示通知浏览器使用缓存
	404: 表示浏览器请求的资源不存在(浏览器的问题, 请求路径错误)
	500: 表示服务器在处理请求的过程中抛出了异常。

(3)OK:描述短语(可以忽略),也表示服务器对请求处理的结果,和状态码表示的结果一致。

	200:OK
	404:Not Found
	500: Internal Server Error
	...

② 若干响应头:也是key-value格式

Content-Type: 表示服务器响应的数据类型,text/html,  image/*...
Content-Length: 表示服务器响应数据的长度, 例如: 384 /字节
Set-Cookie: 和cookie技术相关的一个头, 后面会提到。
...

③ 响应实体内容

响应实体就是浏览器所请求文件的内容。例如:浏览器向服务器请求一个hello.html文件,服务器会找到hello.html文件,将文件的内容作为响应实体发送给浏览器。

3.内容补充

①问题1:请求方式什么时候是GET提交?什么时候是POST提交

只有当使用表单(form),并且在表单上明确的通过method指定提交方式为POST时,请求方式才是POST提交,其他方式都是GET提交(AJAX除外)

思考:判断以下请求方式是GET还是POST?

(1)<form action="#"></form>											-- GET提交
(2)<form action="#" method="GET"></form>				-- GET提交
(3)<form action="#" method="POST"></form>				-- POST提交
(4)点击超链接访问服务器,例如:
	<a href="http://www.baidu">百度一下</a>			-- GET提交
(5)直接在浏览器的地址栏中书写URL地址访问服务器					-- GET提交

② 问题2:GET提交和POST提交有什么区别?

主要区别体现在请求参数传输过程的不相同

GET提交:

  • 将数据通过问号拼接在地址栏URL地址的后面,相对非常不安全。
  • 将数据拼接在地址栏URL地址的后面,数据量是有限制的,通常不能超过1KB或者4KB。

POST提交:(form)

  • POST提交是通过请求实体将数据提交给服务器,不会显示在地址栏上,因此相对更加安全。
  • POST提交通过请求实体提交数据,数据量理论上是没有限制的。

③ 总结

  • 如果只是单纯做一个跳转,请求中没有数据,尽量使用GET提交。
  • 如果在请求中有数据,但数据量不大,并且数据没有隐私性,也尽量使用GET提交。
  • 如果在请求中有数据,数据量比较大或者数据较为隐私,此时推荐使用POST提交。

九、 将Tomcat整合到Eclipse中

将Tomcat服务器整合到Eclipse工具中,可以通过Eclipse启动、关闭tomcat服务器,更重要的是,可以非常方便的将在Eclipse中创建的Web项目发布到Tomcat服务器中运行。

整合tomcat到Eclipse

注意:使用tomcat必须创建动态Web项目

1.方式一:在window偏好设置中配置Tomcat

① 点击Window --> Preferences(偏好设置):

② 在偏好设置窗口中点击 Server --> Runtime Environments --> add:

③ 在弹出的窗口中选择 --> Apache --> Apache Tomcat v8.5,需要注意的是,这里得根据自己安装的tomcat版本进行选择,比如我安装是8.5版本的tomcat,所以这里选择Apache Tomcat v8.5

④ 在下面的窗口中配置tomcat服务器的安装根目录,可以直接把路径复制到第二个输入框中;也可以点击后面的 Browse按钮在文件管理器中选择tomcat服务器安装根目录

2.方式二:在创建Web项目时配置Tomcat

① 1、如果在创建Web项目时,Target runtime选项中没有配置可选的服务器,可以点击右面的选项进行配置,点击后进入下一步操作。

② 在弹出的窗口中选择 --> Apache --> Apache Tomcat v8.5,需要注意的是,这里得根据自己安装的tomcat版本进行选择,比如我安装是8.5版本的tomcat,所以这里选择Apache Tomcat v8.5

③ 在下面的窗口中配置tomcat服务器的安装根目录,可以直接把路径复制到第二个输入框中;也可以点击后面的 Browse按钮在文件管理器中选择tomcat服务器安装根目录


最后点击finish即可完成将Tomcat整合到Eclipse中的配置。

④ 上一步完成后,回到Web项目创建的视图窗口,再查看"Target runtime"选项,如下:

3.将整合到Eclipse中的tomcat从Eclipse中删除

如果要将整合到Eclipse中的tomcat从Eclipse删除:点击Windows --> Preferences --> Server --> Runtime Environments,选中要删除的服务器,点击右侧的删除按钮即可删除,最后点击Apply and Close保存设置即可!

4.在Eclipse中创建Server及移除Server

上面讲解了如何将Tomcat整合到Eclipse中,整合之后,需要在Eclipse中创建一个Server才可以进行启动tomcat、关闭tomcat等操作

① Eclipse中找到Servers窗口:

② 如果没有可以到Window --> Show View --> Other中搜索"servers",如下图:


③ 在Server窗口中点击“No servers are available…”链接:

④ 在弹出的窗口中,保持默认配置,直接点击完成

如果弹出的窗口中默认的服务器不是tomcat,则说明在此之前没有将Tomcat整合到Eclipse中。

⑤ 在上一步点完成后,Eclipses左侧会多出一个Servers项目,Servers窗口中会出现创建的Server,也就是tomcat服务器。

注意:①处的Servers项目不能关闭(close),更不能删除(delete)

⑥ 在创建完Server后,双击tomcat,可以修改Tomcat服务器配置

(1)将Server Locations中的选项切换为第二个选项

(2)将Deploy Path右侧的输入框中的内容改为webapps。ctrl+s保存配置即可

以上配置是为了保证在Eclipse中发布Web应用到tomcat服务器中时,可以将项目发布到tomcat服务器的webapps目录下。

如果不配置,会导致tomcat服务器中webapps下的Web应用无法访问。

⑦ 如果要移除添加的Server,需要同时删除①处的Servers项目(右键delete,要彻底从硬盘上删除),以及删除②处的tomcat服务器(右键delete)

5.tomcat右键选项介绍

① Start:

用于启动tomcat服务器,如果已启动,则显示 ReStart,作用是重启服务器

② Stop:

用于停止服务器

③ Add and Remove:

将Web应用部署到tomcat服务器中,或者移除服务器中部署的Web应用

④ Clean:

作用是将发布到Eclipse自己的webapps目录中的项目删除再重新部署

⑤ Clean Tomcat Work Directory:

作用是将在tomcat运行过程中存入work目录的文件删除

6.tomcat启动失败常见原因

① tomcat服务器启动失败原因一

如果在启动服务器时,服务器启动失败,并弹出窗口显示如下异常信息:

根据上面的描述信息,可以看出是8005、8080、8009端口被同时占用了,此时只有一种可能,就是之前已经启动了tomcat或者之前开启的tomcat没有完全关闭导致的。

解决方法:到tomcat安装目录找到bin目录中的shutdown.bat文件,双击运行将服务器关闭,再到Eclipse中启动服务器即可!

② tomcat服务器启动失败原因二

如果在启动服务器时,服务器启动失败,并弹出窗口显示如下异常信息:

解决方法

(1) 可以先将服务器中所有的Web应用移除(在服务器上右键,Add and Remove–>Remove All–>Finish

(2) 再分别执行服务器右键选项中的cleanClean Tomcat Work Directory

(3)再次启动服务器,如果启动没有报错,则说明tomcat服务器本身没有问题,再将要运行的项目发布到tomcat中,再次启动服务器,观察是否有错误。如果有则说明是项目本身的问题。

(4)如果移除了所有的Web应用,启动tomcat服务器报错,则说明tomcat本身就有问题,可以将tomcat服务器重新配置一次到Eclipse中(将tomcat和Server项目删除,再点击链接重新创建Server)

第八章 Servlet

一、 Servlet概述

1.什么是Servlet?

Servlet是由SUN公司提供的一门动态Web资源开发技术,所以创建项目要用动态Web创建

静态Web资源:不同的人,在不同的条件下访问后看到的是相同的效果,这样的资源叫做静态Web资源(html、css、js等)
动态Web资源:在不同的访问条件下看到的是不同的效果,这样的资源叫做动态Web资源
	(Servlet、jsp、.NET、PHP等)

Servlet本质上是一段Java程序,和之前的Java程序不同的是,Servlet程序无法独立运行,需要将Servlet程序放在服务器中(比如tomcat服务器),由服务器调用才可以执行。

Servlet: 服务器端的Java程序.

Servlet是运行在服务器端的Java程序,其作用是什么?

其作用是对服务器接收过来的请求进行处理(作用为处理请求)

二、 开发Servlet程序

1.开发Servlet程序的步骤

① 第一步:写一个类,实现一个Servlet接口或者继承Servlet接口的子类(GenericServlet/HttpServlet),并实现其中的方法

Servlet接口
	|-- GenericServlet类(抽象类)
				|-- HttpServlet类

② 第二步: 在web应用的web.xml文件中配置Servlet程序对外访问的路径。

Eclipse在创建一个Servlet时,会在web.xml文件中生成Servlet配置,所以不需要我们手动配置。

2.使用Eclipse创建Web项目


以上是Web项目在工程视图(Project)和包视图(package)下结构,推荐使用包视图!

注意:

(1) 3.0版本不会创建web.xml文件,
并且创建Servlet时也不会在web.xml文件中生成Servlet相关的配置信息, 记得改为2.5。

(2) Target runtime选项中如果没有可选的服务器,可点击右侧的"New
Runtime…"进行配置。

详细操作步骤在《5.2配置Target runtime(Web项目运行环境)》

3.使用Eclipse创建Servlet




通过Eclipse创建Servlet,默认继承HttpServlet。由于HttpServlet也是Servlet接口的子类,让HelloServlet继承HttpServlet,相当于间接实现了Servlet接口。

继承HttpServlet类,默认会覆盖doGet方法和doPost方法,两个方法的作用为:

doGet方法:当浏览器发送请求的方式为GET提交时, 将会调用doGet方法来处理请求

doPost方法:当浏览器发送请求的方式为POST提交时,
将会调用doPost方法来处理请求

提示:如果当GET提交和POST提交处理代码相同时,可以将代码写在其中一个方法里(例如写在doGet中),并在另外一个方法(例如doPost)中调这个方法。这样一来,不管是GET提交还是POST提交,最终doGet方法都会执行,都会对请求进行处理!!

4.Servlet在web.xml中的配置

在通过Eclipse创建Servlet时,会自动在web.xml文件中进行Servlet相关信息的配置

(注意:如果是复制Servlet类文件,但配置信息不会跟着复制,需要自己手动添加配置,否则复制的Servlet将无法访问!)

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.tedu.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>

关于上面的配置信息:

a) Eclipse每创建一个Servlet,就会在web.xml文件中添加两个标签:<servlet>和<servlet-mapping>标签(可以将这两个标签看成一个组的标签)

**b) **<servlet>和<servlet-mapping>标签内都会有一个<servlet-name>标签,标签的内容可以更改,但要求更改后的这两个<servlet-name>标签的内容也必须一致。

c) <servlet-class>标签用于配置Servlet类的全限定类名(即包名+类名)

需要注意:如果在创建Servlet后修改了Servlet类的名称,这个地方也要一起更改,否则将会出现"ClassNotFoundException" 即类找不到异常

**d) **<url-pattern>标签用于配置浏览器以什么路径访问当前Servlet(即Servlet对外访问的路径),默认的路径是:/类名

例如:上面为HelloServlet配置的<url-pattern>为**/HelloServlet**,因此我们在浏览器中的访问路径则为:

http://主机名/web项目访问路径(/HelloServlet)

5.运行Servlet程序、访问测试

① 访问Servlet方式一:

若是第一次运行,需要先创建tomcat服务器,即在Servers窗口中点击链接可创建一个tomcat服务器,且只需创建一次即可!

(1)发布项目到服务器:在服务器上右键 --> 点击 "add and remove" 将当前web项目发布到服务器中,并点击完成。

(2)启动tomcat服务器:在服务器上右键 Start 即可启动服务器

(3)通过浏览器访问Servlet:打开本地浏览器,通过路径访问,即可访问Servlet程序

② 访问Servlet方式二:



6.Eclipse默认发布Web应用的位置

Tomcat服务器中默认只有一台虚拟主机,叫做localhost主机

而localhost主机发布web应用的位置是webapps。

-------------------------------------------------------------------------------------------------

默认情况下,发布一个Web应用到localhost主机中,只需要将Web应用的目录拷贝到webapps目录下即可完成发布!

而将Eclipse和Tomcat整合之后,通2中,发布的路径默认被改成了:

[工作空间]\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps

如何修改Eclipse默认发布Web应用的目录位置:

(1)关闭服务器,将服务器中的所有应用移除

(2)在服务器上右键 --> clean

(3)双击tomcat服务器,在弹出窗口中找到 Server location, 选择第二个选项

并将下方的Deploy Path改为: webapps 改完后,Ctrl+s保存配置即可!!

7.Eclipse如何发布一个Web应用

三、 修改Servlet模版

通过Eclipse可以直接创建一个Servlet类,这相比通过记事本等文本编辑工具创建Servlet,可以节省配置Servlet的时间,提高了我们的开发效率。

但是通过Eclipse生成的Servlet类中包含了许多我们不需要的注释和默认实现代码,这些每次都删除也非常占用时间。

接下来可以通过添加模版代码的形式,来生成Servlet的内容,以便于提高我们的开发效率。

1.先创建一个Servlet,将其中的内容修改为自己期望的模版格式,并复制其中的内容,例如:

package ${enclosing_package};
//servlet(服务器端技术)是处理浏览器发过来的请求,响应(response)给浏览器
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * author: bjzhangsz@tedu
 * datetime: ${date} ${time}
 */
public class ${primary_type_name} extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

2.点击菜单栏中的window --> Preferences:

3.在出现的窗口左侧依次点击:Java --> Editor --> templates–>(在右边的窗口中) 点击New… :

4.在出现的新窗口中填写如下内容:

5.替换包路径和类名(作用是在新建Servlet生成的Servlet模版中使用当前类的包路径和类型)



6.点击OK保存,创建新的Servlet文件,测试:


四、 Servlet调用过程

通过浏览器访问服务器中的一个Servlet程序,这个Servlet程序是如何执行的?又是如何被调用的?

request和response介绍

request是代表HTTP请求信息的对象,response是代表HTTP响应信息的对象。

当浏览器发请求访问服务器中的某一个Servlet时,服务器将会调用Servlet中的service方法来处理请求。在调用service方法之前会创建出request和response对象。

其中request对象中封装了浏览器发送给服务器的请求信息(请求行、请求头、请求实体等),response对象中将会封装服务器要发送给浏览器的响应信息(状态行、响应头、响应实体),在service方法执行完后,服务器再将response中的数据取出,按照HTTP协议的格式发送给浏览器。

每次浏览器访问服务器,服务器在调用service方法处理请求之前都会创建request和response对象。(即,服务器每次处理请求都会创建request和response对象)

在请求处理完,响应结束时,服务器会销毁request和response对象。

五、 request对象

1.获取请求参数

① 问题1:什么是请求参数?

所谓的请求参数,就是浏览器发送给服务器的数据(不区分请求方式),例如:通过表单向服务器提交的用户名、密码等,或者在超链接后面通过问号提交的数据,都是请求参数。

http://localhost/day10/TestParam?user=zhangsan&pwd=123&like=篮球&like=足球

② 问题2:如何获取请求参数?

(1)request.getParameter(String paramName)
-- 根据请求参数的名字获取对应的参数值,返回值是一个字符串;
-- 如果一个参数有多个值,该方法只会返回第一个值。
-- 如果获取的是一个不存在的参数,返回值为null
(2)request.getParameterValues(String paramName)
-- 根据请求参数的名字获取该名字对应的所有参数值组成的数组,返回值是一个字符串数组,其中包含了这个参数名对应的所有参数值
-- 如果获取的是一个不存在的参数,返回值为null

代码示例:

//1.获取请求参数中的用户名(user)
String user = request.getParameter("user");
System.out.println( "user="+user );

//2.获取请求参数中的爱好(like)
String[] like = request.getParameterValues( "like" );
System.out.println( "like="+Arrays.toString( like ) );

③ 问题3:如何解决获取请求参数时的中文乱码问题?(三种情况)

在获取中文的请求参数时,可能会出现乱码问题(和请求方式、tomcat服务器版本有关),具体可以分为以下三种情况:

(1)如果请求是GET提交,并且tomcat是8.0及以后的版本,GET提交的中文参数,在获取时不会出现乱码问题!(8.0以后的tomcat包括8.0在获取GET提交的中文参数时,已经处理中文乱码问题。)

(2)如果请求是POST提交,不管是哪个版本的tomcat服务器,在获取中文参数时,都会出现乱码问题。因为tomcat底层在接收POST提交的参数时,默认会使用iso8859-1编码接收,而这个编码中没有中文字符,所以在接收中文参数时,一定会出现中文乱码问题!

解决方法是:通知服务器在接收POST提交的参数时,使用utf-8编码来接收!

request.setCharacterEncoding("utf-8");

注意:这行代码不会影响GET提交,只对POST提交有效!

这行代码要放在任何获取参数的代码之前执行!

(3)如果请求是GET提交,并且tomcat是7.0及以前的版本,GET提交的中文参数,在获取时会出现乱码问题!

解决方法:在[tomcat安装目录]/ conf/server.xml文件的(修改端口的)Connector标签上,添加一个 URIEncoding=“utf-8” 属性,如下:

<Connector port="80" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="utf-8" />

同时在[Eclipse]/Servers/[当前tomcat服务器对应的配置目录]/server.xml文件中,在Connector标签上,添加一个 URIEncoding=“utf-8” 属性,同上!

2.实现请求转发

请求转发是服务器内部资源的一种跳转方式,即当浏览器发送请求访问服务器中的某一个资源(A)时,该资源将请求转交给另外一个资源(B)进行处理并且由资源B做出响应的过程,就叫做请求转发。

请求转发和重定向都是资源的跳转方式,但是跳转的过程有所不同。

① 请求转发的特点:

1)转发是一次请求,一次响应
(2)请求转发前后,浏览器的地址栏地址不会发生变化。(浏览器--访问--> A --转发--> B,地址栏地址始终指向A的地址)
(3)请求转发前后的request对象是同一个(转发前在A中的request和转发到B后,B中的request对象和A中的request对象是同一个。基于这一点,可以通过request从A带数据到B)
(4)请求转发前后的两个资源必须属于同一个Web应用,否则将无法进行转发。(A--转发-->B,A和B必须属于同一个Web应用!)

② 请求转发实现:

request.getRequestDispatcher(url地址/转发到资源的地址).forward(request, response);

③ 代码示例:

//从当前Servlet转发到 index.jsp(http://localhost/day10/index.jsp)
//request.getRequestDispatcher("/index.jsp").forward(request, response);
request.getRequestDispatcher("index.jsp").forward(request, response);

3.作为域对象使用

request在实现转发时,通过request.setAttribute方法和request.getAttribute方法带数据到目的地时,就是通过request对象中的map集合带数据,这个request对象上的map集合以及request对象所在的范围即称之为是一个域对象。

如果一个对象具备可以被访问的范围,通过这个对象上的map集合可以在整个范围内实现数据的共享。这样的对象就叫做域对象。

在request对象上提供了往域对象(map)中存数据的方法以及取数据的方法:

request.setAttribute(String attrName, Object attrValue);
-- 往request域中存入一个域属性,属性名(key)只能是字符串,属性值(value)可以是任意类型。
request.getAttribute(String attrName);
-- 根据属性名(key)获取对应的属性值(value)。返回的是一个Object类型的对象。

① request域对象所具备的三大特征:

生命周期:在服务器调用Servlet程序的service方法之前,会创建代表请求的request对象,在请求处理完,响应结束时,会销毁request对象。

作用范围:在一次请求范围内,都可以获取到同一个request对象。

主要功能:和请求转发配合使用,从Servlet带数据到JSP(带数据到目的地)

② 扩展内容:request对象的getParameter和getAttribute方法有什么区别?

  • getParameter()方法是用于获取(从浏览器发送过来的)请求参数的,请求参数不能设置,只能是浏览器发送给服务器,在服务器端再通过getParameter方法获取请求中的参数
  • getAttribute()方法是用于从request域中获取域属性时用的,域属性得先存入到域中(即得先通过setAttribute方法将数据存入request域中),再通过getAttribute()方法从域中获取。

4.案例1:模拟查询所有门店功能

① 创建一个Servlet程序,用于处理查询所有门店信息请求

public class DoorListServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        //1.模拟查询数据库, 查询所有门店集合
        List<String> doorList = new ArrayList();
        doorList.add("01, 永和北三环西路店, 010-67676767");
        doorList.add("02, 永和西直门店, 010-68976347");
        doorList.add("03, 永和东直门店, 010-78397647");
        doorList.add("04, 永和北京西店, 010-78764397");
        doorList.add("05, 永和天安门店, 010-78769743");
        //2.将数据存入request域中
        request.setAttribute( "list", doorList );
        //3.将请求转发到 door_list.jsp 中, 取出所有门店显示在页面中
        request.getRequestDispatcher("door_list.jsp")
        .forward(request, response);
    }
}

② 创建一个JSP,用于显示所有门店信息

<body>
<h3>显示所有门店信息</h3>
    <%
        //获取request域中的门店信息集合
        List<String> list =(List<String>)request.getAttribute("list");
        //遍历门店集合, 将门店信息输出在网页上
        for( String door : list ){
        	out.write( door +"<br/>");
        }
    %>
</body>

5.案例2:servlet之间的转发(TestForward1转发到TestForward2)

TestForward1代码

package com.tedu;

//servlet模板
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestForward1 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		
		//目的是为了证明TestForward1执行了
		System.out.println("TestForward1.doGet()");//快捷键:syst+/
		/* 将请求转发到TestForward2
		 * TestForward1:localhost/day10/TestForward1
		 * TestForward2:localhost/day10/TestForward2
		 * 	从访问路径上来看,TestForward1和TestForward2都在day10应用的
		 * 根路径下,在同一级,直接通过TestForward2的类名就可以跳转过去
		 */
		request.getRequestDispatcher("./TestForward2").forward(request, response);
	}

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

		doGet(request, response);
	}

}

TestForward2代码

package com.tedu;

//servlet模板
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestForward2 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		
		目的是为了证明TestForward2执行了
		System.out.println("TestForward2.doGet()");//快捷键:syst+/
		
		//对请求进行处理
		out.write("牛逼克拉斯");
	}

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

		doGet(request, response);
	}

}

6.案例3: request域中存取数据

TestScope1中存数据转发到TestScope2(取数据)
TestScope1代码

package com.tedu;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestScope1 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		
		//目的证明了TestScope1执行了
		System.out.println("TestScope1.doGet()");
		
		//转发之前王request域(Map)中存入数据
		request.setAttribute("name", "安其拉");
		/* 将请求转发到TestScope2
		 *	 在转发时可以通过request对象中的Map集合带数据大目的地
		 */
		request.getRequestDispatcher("TestScope2").forward(request, response);
	}

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

		doGet(request, response);
	}

}

TestScope2代码

package com.tedu;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestScope2 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		
		//目的为了证明TestScope2执行了
		System.out.println("TestScope2.doGet()");
		
		//获取request域中存入的数据
		String name = (String) request.getAttribute("name");
		System.out.println("name"+name);
		out.write("TestScope2响应完毕");
	}

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

		doGet(request, response);
	}

}

六、 response对象

response是代表HTTP响应信息的对象。

1.向客户端发送数据

PrintWriter out = response.getWriter();

由于服务器在通过response获取的流发送数据时,默认使用iso8859-1编码,而这个编码中没有中文字符,所以在通过response获取的流发送中文数据时,会出现乱码问题。

解决方法是:在响应数据之前,通知服务器使用utf-8发送数据。

/*  通知服务器在响应数据时,使用utf-8编码
 * 也能通知浏览器使用utf-8接收服务器发送的数据 */
response.setContentType( "text/html;charset=utf-8" );
PrintWriter out = response.getWriter();
out.write( "你好" );

2.实现重定向

当浏览器向服务器发请求访问某一个资源A,资源A在响应时通知浏览器需要再进一步请求才能获取到对应的资源,浏览器再次发请求访问服务器中的资源B,最终由资源B响应浏览器要获取的资源,这个过程叫做重定向

3.重定向的特点:

1)重定向是两次请求、两次响应
(2)重定向前后,浏览器的地址栏地址会发生变化。(因为两次请求都是通过浏览器发起,浏览器知道这个跳转的过程,因此地址栏地址会变化)
(3)重定向前后的request对象不是同一个(因为重定向是两次请求,服务器会根据两次请求创建两个不同的request对象,request对象不是同一个,也就不能在重定向时通过request带数据到目的地。)
(4)重定向前后的两个资源可以是来自不同的web应用,甚至可以是来自不同的服务器。(进行跳转的两个资源之间没有限制)

4.实现代码:

response.sendRedirect(所重定向到资源的URL地址);

5.代码示例

//测试1: 从当前Servlet(day10/TestRedirect)重定向到day10/index.jsp
// http://localhost/day10/TestRedirect
// http://localhost/day10/index.jsp
response.sendRedirect( "http://localhost/day10/index.jsp" );
response.sendRedirect( "/day10/index.jsp" );
response.sendRedirect( "/index.jsp" ); //错误路径
response.sendRedirect( "index.jsp" ); //正确路径

//测试2: 从当前Servlet重定向到day09/index.jsp
response.sendRedirect( "http://localhost/day09/index.jsp" );

//测试3: 从当前Servlet重定向到百度首页
response.sendRedirect( "http://www.baidu" );

6.总结:什么时候用转发(forward)?什么时候用重定向(redirect)?

(1)如果希望跳转前后地址栏地址不会发生变化, 只能使用转发; 如果希望跳转前后地址栏地址会发生变化, 只能使用重定向
(2)如果希望在跳转前后, 能够通过request对象带数据到目的地, 只能使用转发
(3)如果仅仅是做一个跳转,没有其他要求,此时推荐使用转发(转发是一次请求,一次响应,可以减少访问服务器的次数,降低服务器的压力)

第九章 JSP、EL、JSTL

一、 JSP概述

1.什么是JSP

JSP和Servlet都是由SUN公司提供的动态Web资源开发技术。

JSP看起来像一个HTML,但和HTML不同的是,JSP中可以书写Java代码,可以通过Java代码展示动态的数据。

静态Web资源:任何人在任何条件下访问时,看到的都是相同的效果,这样的资源叫做静态Web资源。(html、css、js等)
动态Web资源:不同的人,在不同的条件下访问时,看到的都是不同的效果,这样的资源叫做动态Web资源。(Servlet、jsp、php、.NET等)

JSP本质上是一个Servlet程序

思考1:为什么要学习JSP?

  • Servlet是一段Java程序,适合处理业务逻辑,但是Servlet不适合向浏览器输出一个html网页。
  • html可以作为页面返回,但是html是一个静态Web资源,无法展示动态数据。
  • 而JSP也是页面的开发技术,也可以作为页面返回,并且JSP中可以书写Java代码,可以通过Java代码展示动态的数据。
  • 因此,JSP的出现即解决了Servlet不适合输出页面的问题,也解决了HTML无法展示动态数据的问题!

思考2:为什么说JSP本质是一个Servlet?

在JSP第一次被访问时,会翻译成一个Servlet程序。访问JSP后看到的html网页,其实是翻译后的Servlet执行的结果。(也就是说,访问JSP后看到的网页,是JSP翻译后的Servlet输出到浏览器的。)

2.JSP执行过程

访问服务器中的JSP文件,其执行过程为:

  1. 当浏览器请求服务器中的某一个JSP文件(例如:localhost/day11-jsp/index.jsp),服务器会根据请求资源的路径去寻找该文件:
  2. 如果找到了,JSP翻译引擎会将JSP翻译成一个Servlet程序(JSP----> xxx.java----> xxx.class),然后Servlet程序再执行,执行的结果是向浏览器输出一个HTML网页!
  3. 如果没有找到,服务器将会响应一个404页面,通知浏览器请求的资源不存在。

访问服务器中的HTML文件,其执行过程为:

  1. 当浏览器请求服务器中的某一个HTML文件时(例如:localhost/day11-jsp/index.html),服务器会根据请求资源的路径去寻找该文件:
  2. 如果找到了,服务器会将html文件的内容作为响应实体发送给浏览器,浏览器再解析html并显示在网页上。
  3. 如果没有找到,服务器将会响应一个404页面,通知浏览器请求的资源不存在。

3.修改JSP模版


点击edit编辑JSP模版,修改为如下:

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title></title>
</head>
<body>
${cursor}
</body>
</html>

二、 JSP语法

1.模版元素

模板元素是指写在JSP中的html内容

或者除了JSP特有内容以外的其他内容称之为模板元素

模板元素在翻译后的Servlet中,被out.write原封不动的发送给浏览器,由浏览器负责解析并显示。

2.JSP表达式

格式:<%= 常量、变量、表达式 %>

作用:计算表达式的结果,将结果输出到浏览器中,由浏览器负责解析并显示。

<%= "Hello JSP..." %>
<% String name = "林青霞"; %>
<%= name %>
<%= 100+123 %>
<%= Math.random() %>

3.JSP脚本片段

格式:<% 若干Java语句 %>

作用:在翻译后的Servlet中,将脚本片段中的Java语句复制粘贴到Servlet的对应的位置执行。例如:

在JSP中代码如下:

<!-- 在页面上输出5行"hello JSP" -->
<%
	for(int i=0; i<5; i++){
		out.write("Hello JSP...<br/>");
	}
%>

在翻译后的Servlet中代码如下:

...
//out是JSP内置对象,不用声明,也不用获取,可以直接使用
for(int i=0; i<5; i++){
	out.write("Hello JSP...<br/>");
}
...

另外,在某一个脚本片段中的Java代码可以是不完整的,但是在JSP中所有的脚本片段加在一起,必须是完整符合Java语法。例如,在JSP中代码如下:

<% for(int i=0;i<5;i++){ %>
		Hello JSP~~~<br/>
<% } %>

在翻译后的Servlet中:

for(int i=0;i<5;i++){ 
  out.write("\r\n");
  out.write("\t\t\tHello JSP~~~<br/>\r\n");
  out.write("\t");
} 

4.JSP注释

格式:<%-- JSP注释内容 --%>

作用:(1)为代码添加解释说明 (2)将一些暂时不需要执行的代码注释掉。

在JSP翻译时,注释内容不会参与翻译,而是直接被丢弃

面试题:考察JSP中的JSP注释、Java注释、html注释

<%-- 
<% out.write( "aaaaa<br/>" ); %>
 --%>
<% //out.write( "bbbbb<br/>" ); %>
<!-- 
<% out.write( "ccccc<br/>" ); %>
 -->

问题:(1)上面输出的三行内容,在访问时,会显示哪一行内容?

第一行被JSP注释给注释了,JSP注释的内容不会参与翻译,也不会执行,更不会发送给浏览器,也不会在浏览器上显示。

第二行被Java注释给注释了,放在脚本片段中的内容会参与翻译,会将其中的Java代码复制到翻译后的Servlet中,但由于代码被注释了,所以不会执行,也不会发送给浏览器,更不会在浏览器上显示。

第三行被html注释给注释了,html注释在JSP中是模板元素,注释本身会发送给浏览器,注释中的脚本片段会参与翻译,其中的java代码也会执行,也会将内容(ccccc)发送给浏览器,但由于发送到浏览器后的ccccc被html注释包裹,因此也不会显示在浏览器上。

(2)上面输出的三行内容,哪一行会发送到浏览器中?(不管是否显示)

其中第三行内容会发送到浏览器中,但不会显示,因为前后有html注释。

5.SP指令

指令的格式:<%@ 指令名称 若干属性声明... %>

指令的作用:用于指挥JSP解析引擎如何将一个JSP翻译成一个Servlet程序。

① page指令

用于声明JSP的基本属性信息(比如JSP使用的编码,JSP使用的开发语言等)

<%@ page language="java"%>
-- language属性用于指定当前JSP使用的开发语言,目前只有java语言支持

<%@ page import="java.util.Date"%>
-- import属性用于导入包,如果不导入包,在使用其他包下的类时,就需要在类名前面加上包路径,例如: 
	java.util.Date date = new java.util.Date();

<%@ page pageEncoding="UTF-8"%>
-- pageEncoding属性是用于指定当前JSP使用的编码,Eclipse工具会根据这个编码保存JSP文件。
保证pageEncoding属性指定的编码和JSP文件保存时使用的编码相同,可以避免JSP文件出现乱码!

② taglib指令

用于引入JSTL标签库或者其他的自定义标签库

三、 JSP标签技术

在JSP页面中写入大量的java代码会导致JSP页面中html代码和java代码混杂在一起,会造成jsp页面结构的混乱,导致后期难于维护,并且代码难以复用。

于是在JSP的2.0版本中,sun提出了JSP标签技术,推荐使用标签来代替JSP页面中java代码,并且推荐,JSP2.0以后不要在JSP页面中出现任何一行java代码。

1.EL表达式

格式:${ 常量/表达式/变量 } (放在EL中的变量得先存入域中,才可以获取变量的值)

作用:(1)计算放在其中的表达式的结果,将结果输出在当前位置。(类似于JSP表达式)

(2)主要作用:用于从域对象中获取数据,将获取到的数据输出在当前位置。

域对象:pageContext、request、session、application

① 获取常量、表达式、变量的值(变量得先存入域中)

${ "hello el" } 
hello el	<br/>
${ 100+123 }
${ 12*12 > 143 ? "yes" : "no" } <br/>

<% 
	String name = "马云";
	request.setAttribute( "name123" , name );
%>
${ name123 }
<%= request.getAttribute("name123") %>
<%-- 在EL表达式中书写变量,底层会根据变量名到四个作用域中寻找该名称的属性值
	如果找到对应的属性值, 就直接返回, 输出到当前位置; 如果找不到就接着寻找
	直到找完四个作用域, 最后还找不到就什么都不输出!
	到四个作用域中寻找的顺序为: pageContext->request->session->application
 --%>

在EL表达式中书写变量,底层会根据变量名到四个作用域中寻找该名称的属性值
如果找到对应的属性值, 就直接返回, 输出到当前位置; 如果找不到就接着寻找
直到找完四个作用域, 最后还找不到就什么都不输出!
到四个作用域中寻找的顺序为: pageContext->request->session->application

② 获取作用域中数组或集合中的元素

Servlet中的代码:

//声明一个数组, 为数组添加元素, 并将数组存入到域中
String[] names = {"刘德华", "郭富城", "张学友", "黎明" };
request.setAttribute( "names", names );
//将请求转发到jsp, 在JSP中获取域中的数组中的元素
request.getRequestDispatcher( "/02-el.jsp" ).forward(request, response);

JSP中的代码:

<%-- 获取从Servlet转发带过来的域中的数组中的元素 --%>
${ names[0] } <%-- 刘德华 --%>
${ names[1] } <%-- 郭富城  --%>
${ names[2] } <%-- 张学友 --%>
${ names[3] } <%-- 黎明 --%>

③ 获取作用域中map集合中的元素

Servlet中的代码:

//声明一个map集合,为集合添加元素, 并将map集合存入到域中
Map map = new HashMap();
map.put( "name" , "尼古拉斯赵四" );
map.put( "age" ,  28 );
map.put( "addr", "中国" );
request.setAttribute( "map1", map );
//将请求转发到jsp, 在JSP中获取域中的数组中的元素
request.getRequestDispatcher( "/02-el.jsp" ).forward(request, response);

JSP中的代码:

${ map1.name } <%-- 尼古拉斯赵四 --%>
${ map1.age } <%-- 28 --%>
${ map1.addr } <%-- 中国 --%>

④ 获取作用域中JavaBean对象的属性值

Bean:指可重用的组件
JavaBean:指Java中可重用的组件
业务Bean:专门用于处理业务逻辑(例如:处理注册请求时,在将用户的注册信息保存到数据库之前,需要对注册薪资进行校验)
实体Bean:是专门用于封装数据的(例如:User user = new User() ...)

User类(封装数据)

package com.tedu.pojo;
/**
 * POJO:Plain Ordinary Java Object 简称java对象,用于封装信息
 * 	比如:我们为了封装用户信息信息所创建的User就是一个POJO
 * 
 * Bean:计算机语言中,表示可重用组件
 * JavaBean:使用Java语言编写的可重用组件(Service、Dao、POJO)
 *	Service、Dao、POJO:业务Bean,专门用于处理业务逻辑
 *	POJO:实体Bean,专门用于封装数据
 */
//封装User类
public class User {
	private String name;
	private int age;
	private String addr;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getAddr() {
		return addr;
	}
	public void setAddr(String addr) {
		this.addr = addr;
	}
	
}

Servlet中的代码:

//声明一个User对象, 为对象的属性赋值, 并将User对象存入到域中
User u1 = new User();
u1.setName( "刘德华" );
u1.setAge( 18 );
u1.setAddr( "中国香港" );
request.setAttribute( "user" , u1 );

//将请求转发到jsp, 在JSP中获取域中的数组中的元素
request.getRequestDispatcher( "/02-el.jsp" ).forward(request, response);

JSP中的代码:

<%-- 
${ user.getName() }
${ user.getAge() }
${ user.getAddr() } --%>
<hr/>
<!-- 在EL中通过 对象.getXxx()方法,可以简写为 对象.xxx
		但底层调用的仍然是getXxx()方法,只是简写化了!!
	 -->
<%-- user.name 底层调用的仍然是 getName()方法--%>
${ user.name }
<%-- user.age 底层调用的仍然是 getAge()方法--%>
${ user.age }
<%-- user.addr 底层调用的仍然是 getAddr()方法--%>
${ user.addr }

2.request转发+域对象实现从Servlet带数据到JSP

3.JSTL标签库

JSTL标签库是为JavaWeb开发人员提供的一套标准通用的标签库;

JSTL标签库和EL配合使用可以取代JSP中大部分的Java代码;

在使用JSTL标签库之前需要完成:

  • 导入JSTL的开发包
  • 在使用JSTL标签库的JSP中引入JSTL(taglib指令)

① <c:set></c:set>标签

用于往域中添加属性,或者修改域中已有的属性值(可以写成自闭<c:set/>)

c:set 标签属性总结:

(1)var -- 指定存入作用域中的属性名称
(2)value -- 指定存入作用域中属性的值
(3)scope -- 指定将属性存入哪一个作用域中,默认值是page,表示pageContext域
可取值: a)page表示pageContext域 b)request表示request域
	c)session表示session域 d)application表示ServletContext域

代码示例:

<%-- request.setAttribute("name", "张三"); --%>
<c:set var="name" value="张三" scope="request"/>
${ name }

<% String job = "java开发工程师"; %>
<c:set var="job" value="<%= job %>" scope="request"/>
${ job }

<c:set var="name" value="张三丰" scope="request"/>
${ name }

	<% 
		//声明一个User对象,通过c:set标签存入域中
		User user=new User();
	%>
	<c:set var="user" value="<%= user %>"/>
	${ user }

② <c:if></c:if>标签

能够实现简单的 if…else…分支结构
c:if 标签属性总结:

test属性 -- 指定一个布尔表达式,当表达式的结果为true时,将会执行(输出)c:if标签中的内容,如果表达式结果为false,将不会输出c:if标签中的内容

代码示例:往域中存入一个成绩, 根据成绩判断成绩所属的等级

<c:if test="${ 3>5 }">yes</c:if>
<c:if test="${ 3<=5 }">no</c:if>
<hr>
<!-- 根据成绩判断成绩所属的等级 -->
<c:set var="score" value="-35"/>
<c:if test="${ score>=80 && score<=100 }">您的成绩属于: 优秀!</c:if>
<c:if test="${ score>=60 && score<80 }">您的成绩属于: 中等!</c:if>
<c:if test="${ score>=0 && score<60 }">您的成绩属于: 不及格!</c:if>
<c:if test="${ score<0 || score>100 }">您的成绩有误!</c:if>

③ <c:forEach></c:forEach>标签

对集合或数组等中元素进行循环遍历或者是执行指定次数的循环.

c:forEach 标签属性总结:

(1)items: 指定需要遍历的集合或数组
(2)var: 指定用于接收遍历过程中的每一个元素
(3)begin: 指定循环从哪儿开始
(4)end: 指定循环到哪儿结束
(5)step: 指定循环时的步长, 默认值是1,每次遍历的值都会增长1
(6)varStatus: 用于表示循环遍历状态信息的对象, 这个对象上有如下属性:
	first属性: 表示当前遍历是否是第一次, 若是, 则返回true;
	last属性: 表示当前遍历是否是最后一次, 若是, 则返回true;
	count属性: 记录当前遍历是第几次

代码示例

<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="com.tedu.pojo.User"%>
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title></title>
</head>
<body>
	<h3>1、c:set标签--负责往作用域中添加域属性</h3>
	<%-- 往request域中添加一个属性,名称name,值是张三
		request.setAttribute("name","张三");
	--%>
	<c:set var="name" value="张三" scope="request"/>
	${name }
	
	<hr>
	
	<% 
		//声明一个User对象,通过c:set标签存入域中
		User user=new User();
	%>
	<c:set var="user" value="<%= user %>"/>
	${ user }
	
	<hr>
	
	<h3>2、c:if标签--能够实现简单的 ifelse…分支结构</h3>
	<c:set var="score" value="85" scope="request"/>
	<!-- 
		test属性 -- 指定一个布尔表达式,当表达式的结果为true时,将会执行(输出)c:if标签中的内容,如果表达式结果为false,
		将不会输出c:if标签中的内容
	 -->
	<c:if test="${score >=80 && score<=100 }">优秀</c:if>
	<c:if test="${score >=60 && score<80 }">中等</c:if>
	<c:if test="${score >=0 && score<60 }">不及格</c:if>
	<c:if test="${score <0 || score>100 }">您的成绩不合法</c:if>
	
	<h3>3、c:forEache标签--对集合或数组等中元素进行循环遍历或者是执行指定次数的循环.</h3>
	<h4>(1) 遍历域中数组或集合中的元素</h4>
	<%
		String[] names={"孙悟空","猪八戒","沙僧","唐僧"};
		request.setAttribute("name", names);
		//for(String str:names){}
	%>
	<!-- 
		items: 指定需要遍历的集合或数组
		var: 指定用于接收遍历过程中的每一个元素
		varStatus: 用于表示循环遍历状态信息的对象, 这个对象上有如下属性:
			first属性: 表示当前遍历是否是第一次, 若是, 则返回true;
			last属性: 表示当前遍历是否是最后一次, 若是, 则返回true;
			count属性: 记录当前遍历是第几次
	 	begin: 指定循环从哪儿开始
	 	end: 指定循环到哪儿结束
	 	step: 指定循环时的步长, 默认值是1,每次遍历的值都会增长1
	 
	 -->
	<c:forEach items="${ name }" var="str" varStatus="VS">
		${ VS.count },${ str }<br>
	</c:forEach>
	
	<hr>
	
	<h4>(2) 遍历域中map集合中的元素</h4>
	<% 
		//声明一个Map集合,并将Map存入到request域中
		Map map=new HashMap();//Alt+/可以快速导入工具包
		map.put("name","尼古拉赵四");
		map.put("age",30);
		map.put("addr","中国");
		request.setAttribute("map1", map);
		//Entry<k,V> key=value
	%>
	<c:forEach items="${ map1 }" var="entry">
		${ entry }<br>
	</c:forEach>
	
	<hr>
	
	<c:forEach items="${ map1 }" var="entry">
		<%-- ${ entry.getKey() } : ${ entry.getValue()}<br> --%>
		${ entry.key } : ${ entry.value }<br>
	</c:forEach>
	<hr>
	
	<h4>(3) 遍历1~100之间的整数,将是3的倍数的数值输出到浏览器中</h4>
	<!-- for(int i=1;i<=100;i++){} -->
	<c:forEach begin="1" end="100" var="i" step="1">
		${ i % 3 ==0 ? i : "" }
	</c:forEach>
	
	<hr>
	
	<c:forEach begin="1" end="100" var="i" step="1">
		<c:if test="${ i % 3 ==0 }">${ i }</c:if>
	</c:forEach>
</body>
</html>

第十章 Maven

一、 Maven介绍

1.Maven是什么?


Maven: 翻译为"专家"、“内行”,是Apache下的一个纯Java开发的一个开源项目。

Maven是一个项目管理工具,使用Maven可以来管理企业级的Java项目开发及依赖的管理。

使用Maven开发,可以简化项目配置,统一项目结构。总之,Maven可以让开发者的工作变得更简单。


什么是依赖管理?要明白依赖管理,首先要知道什么是依赖?

一个Java项目中往往会依赖一些第三方的jar包。比如JDBC程序中要依赖数据库驱动包,或者在使用c3p0连接池时,要依赖c3p0的jar包等。这时我们称这些Java项目依赖第三方jar包。

而所谓的依赖管理,其实就是对项目中所有依赖的jar包进行规范化管理。

2.为什么要使用Maven?

传统的项目(工程)中管理项目所依赖的jar包完全靠人工进行管理,而人工管理jar包可能会产生诸多问题。

① 不使用Maven,采用传统方式管理jar包的弊端

(1)在一些大型项目中会使用一些框架,比如SSM或者SSH框架,而框架中所包含的jar包非常多(甚至还依赖其他第三方的jar包),如果这些jar包我们手动去网上寻找,有些jar包不容易找到,比较麻烦。

(2)传统方式会将jar包添加到工程中,比如Java工程中将jar包放在工程根目录或者放在自建的lib目录下;JavaWeb工程会将jar包放在:/WEB-INF/lib目录下,这样会导致项目文件的体积暴增(例如,有些项目代码本身体积可能仅仅几兆,而加入jar包后,工程的体积可能会达到几十兆甚至百兆)。

(3)在传统的Java项目中是将所有的jar包统一拷贝的同一目录中,可能会存在jar包文件名称冲突的问题!

(4)在进行项目整合时,可能会出现jar包版本冲突的问题。

(5)在传统java项目中通过编译(手动编译或者在eclipse保存自动编译)、测试(手动在main函数中测试、junit单元测试)、打包部署(手动打war包/手动发布)、运行(手动启动tomcat运行),最终访问程序。

② 使用Maven来管理jar包的优势

(1)Maven团队维护了一个非常全的Maven仓库(中央仓库),其中几乎包含了所有的jar包,使用Maven创建的工程可以自动到Maven仓库中下载jar包,方便且不易出错。

另外,在Maven构建的项目中,如果要使用到一些框架,我们只需要引入框架的核心jar包,框架所依赖的其他第三方jar包,Maven也会一并去下载。

(2)在Maven构建的项目中,不会将项目所依赖的jar包拷贝到每一个项目中,而是将jar包统一放在仓库中管理,在项目中只需要引入jar包的位置(坐标)即可。这样实现了jar包的复用。

(3)Maven采用坐标来管理仓库中的jar包,其中的目录结构为【公司名称+项目/产品名称+版本号】,可以根据坐标定位到具体的jar包。即使使用不同公司中同名的jar包,坐标不同(目录结构不同),文件名也不会冲突。

(4)Maven构建的项目中,通过pom文件对项目中所依赖的jar包及版本进行统一管理,可避免版本冲突。

(5)在Maven项目中,通过一个命令或者一键就可以实现项目的编译(mvn complie)、测试(mvn test)、打包部署(mvn deploy)、运行(mvn install)等。

还有发布到tomcat服务器中运行: mvn tomcat7:run。如果想实现上面的所有过程,只需要记住一个命令:mvn install

总之,使用Maven遵循规范开发有利于提高大型团队的开发效率,降低项目的维护成本,大公司都会优先使用Maven来构建项目.

二、 Maven安装

1.下载、安装Maven

① 官方下载地址Maven下载地址

② 下载绿色版,解压之后就可以使用。


原则: 安装的路径中不要有中文和空格!!

③ 若要下载旧版本Maven,可以访问

Maven旧版本下载地址

三、 Maven的相关配置

在开发中更多是通过Eclipse+Maven来构建Maven项目,所以这里我们需要将Maven配置到Eclipse开发工具中。

在将安装好的Maven工具配置的Eclipse开发工具中之前,需要做一些相关的配置。

1.配置本地仓库位置

本地仓库:其实就是本地硬盘上的某一目录,该目录中会包含maven项目中所需要的所有jar包及插件。当所需jar包在本地仓库没有时,从网络上下载下来的jar包也会存放在本地仓库中。

因此本地仓库其实就是一个存放jar包的目录,我们可以指定Maven仓库的位置。

如果不指定,maven本地仓库的默认位置是在c盘,在:
C:/Users/{当前用户}/.m2/repository,例如:

可以保持默认,当然也可以修改本地仓库的位置到别的盘符路径。

修改方法:找到[MAVEN_HOME]/conf/目录中的配置文件settings.xml,修改maven仓库的路径。

配置该目录后,以后通过maven下载的jar包将会保存在配置的目录下。

2.配置镜像仓库(私服)

当maven项目中需要依赖jar包时,如果本地仓库中没有,就会到远程仓库去下载jar包。

如果不配置镜像仓库,默认连接的是中央仓库,由于中央仓库面向的是全球用户,所以在下载jar包时,速度可能会比较慢,效率会比较低。

可以在settings.xml文件中配置连接达内镜像仓库(前提是在达内教室,连接的是达内内网)或者连接阿里云镜像仓库(需要有外网)。

1、如果连接的是达内内网,可以连接达内镜像仓库(如果不配置,默认连接中央仓库,没有外网,连接不了中央仓库,会导致jar包无法下载)。

需要做的是,在settings.xml文件中的<settings>标签下的<mirrors>标签内部添加如下配置,配置达内镜像仓库:

<mirror>
    <id>nexus-tedu</id>
    <name>Nexus tedu</name>
    <mirrorOf>central</mirrorOf>
    <url>http://maven.tedu/nexus/content/groups/public/</url>
</mirror>

2、如果在家里、在公司连接的是外网,是无法连接达内的镜像仓库,可以选择什么都不配置,默认连接中央仓库,或者可以配置连接阿里云镜像仓库(不要使用手机热点网络连接),配置如下:

配置阿里云镜像仓库:

<mirror>
	<id>nexus-aliyun</id>
	<name>Nexus aliyun</name>
	<mirrorOf>central</mirrorOf>
	<url>http://maven.aliyun/nexus/content/groups/public/</url>
</mirror>

3.镜像仓库配置总结

  1. 当所需jar包在本地仓库没有时,会从网络上下载。配置镜像仓库其实就是配置,去网络中哪个位置下载jar包到本地。

  2. 如果在公司,并且公司有搭建镜像服务器,推荐使用公司的镜像服务器下载jar包,速度会更快。(如果在达内,使用的是内网,只能配置达内的镜像仓库。否则,没有外网也连接不了中央仓库,下载jar包会失败!)

  3. 如果在家里,使用的是外网,可以不配置镜像仓库,默认连接中央仓库下载jar包,或者配置阿里云的镜像仓库。连接阿里云服务器下载jar包。(注意,如果配置阿里云镜像服务器,不可使用手机热点网络!

4.配置JDK版本

通过 Maven创建的工程,JDK版本默认是JDK1.5,每次都需要手动改为更高的版本。

这里可以通过修改maven的settings.xml文件, 达到一劳永逸的效果。

配置方式为:打开 {maven根目录}/conf/settings.xml 文件并编辑,在 settings.xml文件内部的 <profiles> 标签内部添加如下配置:

<profile>
    <id>development</id>
    <activation>
    	<jdk>1.8</jdk>
    	<activeByDefault>true</activeByDefault>
    </activation>
    <properties>
    	<mavenpiler.source>1.8</mavenpiler.source>
    	<mavenpiler.target>1.8</mavenpiler.target>
  <mavenpilerpilerVersion>1.8</mavenpilerpilerVersion>
    </properties>
</profile>

5.将Maven配置到Eclipse中

将Maven工具配置到Eclipse中,就可以通过Eclipse和自己安装的Maven创建Maven项目了。



一定要注意:自己安装的Maven不要放在桌面上(容易丢失,并且路径中往往有空格),maven的安装路径中也不要包含中文和空格!!


四、 Maven的项目构建

通过Maven构建Java项目分为两种方式:

(1)使用简单方式创建:创建Maven的简单Java工程以及创建Maven的简单Web工程

(2)使用模板方式创建:创建使用模板的Java工程以及创建使用模板的Web工程

在利用Maven构建项目时分两种,第一种是:创建简单工程(Create a simple
project),即在创建时勾选前面的框。


(不勾选前面的框,即创建使用骨架(其实就是模版)创建Maven工程)

另,在创建简单工程时,还分为创建Java工程JavaWeb工程。下面分别进行演示。

1.创建简单工程—Java工程




在上述内容中,必填的内容有四项:

(1)Group Id – 组的名称,通常填写公司名称(比如com.tedu)或者组织名称(org.apache…)

(2)Artifact Id – 项目名称或者模块名称

(3)Version – 项目的版本,创建的项目默认是0.0.1-SNAPSHOT快照,也叫非正式版,正式版是RELEASE)

(4)Package – 项目的类型:jar表示创建的是Java工程,war表示创建的是web工程,pom表示创建的是父工程(当然相对的还有子工程)或者聚合工程,pom目前我们不讨论。

填写完毕后,点击完成即可完成创建简单Java工程

2.创建简单工程—Web工程






方式二解决找不到Web.xml文件(推荐)


<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

添加后保存pom文件。若还报错,在项目上右键选择 “Maven” —> “UpdateProject…” 更新工程即可!

6、实现Servlet程序

3.Maven的目录结构


下面以Maven的Web项目为例,介绍Maven项目中的目录结构:

Maven项目名称(Web项目)
  |-- src/main/java(源码目录):用于存放程序/项目所需要的java源码文件
  |-- src/main/resources(源码目录):用于存放程序/项目所需要的配置文件
  |-- src/test/java(源码目录):用于存放测试程序的java源文件
  |-- src/test/resources(源码目录):用于存放测试程序所需要配置文件
  |-- src/main/webapp:(Web应用的根目录,作用类似于WebContent)
  				 |-- WEB-INF:(受保护的目录)
  				 		|-- web.xml:(Web应用的核心配置文件)
  |-- target/classes(类目录):源码目录中的资源经过编译后,会输出到类目录下。
  |-- pom.xml:Maven项目中非常重要的文件,将来项目需要任何jar包或插件,都可以通过pom文件来导入这些jar包或插件。

五、 导入已有的Maven项目

现将后面通过SSM框架实现的<<永和大王门店管理系统>>(Maven)项目导入到我们的Eclipse开发环境中。

在导入项目时我们通常会通过 “File” --> “Import…” 来导入项目,但是这样能会产生环境问题=,例如:如果项目本身自带的环境和我们当前使用的开发环境不一致,就会产生问题。可以按照下面的方式来进行导入。

下面是导入的步骤:




六、 Maven的依赖管理

1.依赖(jar包)管理

依赖管理即jar包的管理,那么通过Maven创建的工程是如何管理jar包的?

① 1、在Maven项目中如何引入jar包?

在Maven创建的项目中,如果需要引用jar包,只需要在项目的pom.xml文件中添加jar包的坐标(GroupID + ArtifactID + Version)即可将jar包引进项目中,之后就可以在项目中使用所引入的jar包了。

例如,现在我们在pom.xml文件中,在<project></project>里面的<dependencies></dependencies>标签内部添加servlet的jar包的坐标如下:

	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>servlet-api</artifactId>
		<version>2.5</version>
	</dependency>

在pom.xml文件中,在<project></project>里面的<dependencies></dependencies>标签内部添加mysql驱动包的坐标如下:

	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.32</version>
	</dependency>

注意:如果pom.xml文件中没有<dependencies></dependencies>标签,就需要手写这个标签

② 项目中引入的jar包存放在哪里?

③ 如果引入的jar包在本地仓库中没有呢?

  • 如果是刚配置的Maven环境,本地仓库中还没有太多jar包,此时在pom文件中通过坐标引入jar包,而本地仓库中没有这个jar包,这时会怎么样呢?

  • 若本地仓库没有所需要的jar包,则会到镜像仓库(也叫私服)或者到中央仓库(也叫公服)中下载。下面我们就来介绍Maven的这三种仓库。

2.Maven三种仓库

在上面所提到的本地仓库、镜像仓库、中央仓库是用来Maven用来更好的管理jar包的所采用的一种方式。下面来了解Maven的三种仓库,以及三种仓库之间的潜在联系。

① 本地仓库

默认的本地仓库位置在:c:/${user.dir}/.m2/repository,其中${user.dir}表示windows下的用户目录。本地仓库的作用是,用于保存(存储)从私服或者从中央仓库下载下来的jar包(或插件)。当项目中需要使用jar包和插件时,优先从本地仓库查找。

如果本地仓库中没有所需的jar包,可以到私服或者到中央仓库中下载后再保存到本地仓库。

② 镜像仓库

镜像仓库也叫做私服(Nexus),私服一般由公司搭建并维护(也可以自己搭建)。比如达内有搭建自己的私服服务器(http://maven.tedu/nexus/content/groups/public/),以及阿里云私服服务器(http://maven.aliyun/nexus/content/groups/public/)。

如果项目中使用到的jar包或者插件本地仓库中没有,则可以到私服中下载,如果私服中有就直接将jar包保存到本地仓库中;而如果私服中也没有所需的jar包,就到中央仓库(公服)上下载所需要的jar包,下载之后先在私服上保存一份,最后再保存到本地仓库。

③ 中央仓库

中央仓库也叫做公服,在maven软件中内置了一个仓库地址(http://repo1.maven/maven2)它就是中央仓库,服务于整个互联网,由Maven团队自己搭建并维护,里面存储了非常全的jar包,它包含了世界上大部分流行的开源项目的jar包。
那么我们在使用Maven构建的Java项目,项目中所使用的jar包会来自哪里呢?例如,通过Maven先后构建项目A和项目B,在项目中都需要依赖第三方jar包:

  • 如果项目A中需要依赖第三方jar包,只需要在项目下的pom文件中引入jar包在本地仓库中的坐标即可使用。如果本地仓库没有所需要的jar包,则会连接私服(需要提前配置)下载所需jar包到本地仓库供项目使用。

  • 如果私服上也没有所需的jar包,则会连接中央仓库下载所需要的jar包保存到私服,再将jar包从私服下载至本地仓库,供项目使用。

  • 如果没有配置私服,则默认连接中央仓库下载所需要的jar包到本地仓库中供项目使用

  • 当项目B也需要依赖第三方jar包时,先到本地仓库中查找所需jar包,如果有则直接引用而无需再次下载,如果仍有部分jar包本地仓库中没有,则同上,即连接私服下载所需jar包到本地仓库。若私服中也没有所需jar包,则连接中央仓库下载jar包到私服,再从私服下载jar包到本地仓库中,供项目使用。

3.添加依赖:方式一

使用maven插件的索引功能快速添加jar包

这种方式需要本地仓库中已经包含了该jar包,否则搜索不到!!!

1、如果本地仓库中有我们需要的jar包,可以在项目中的pom.xml文件中添加这个标签,然后空白处右键–> Maven --> Add Dependency在弹出的窗口中添加所需要的依赖(jar包),如图:




4.添加依赖:方式二

1、直接在pom.xml文件中的标签内部添加。例如:在pom.xml文件中添加如下配置,就可以将junit单元测试的jar包引入到项目中来了。

添加依赖:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
        <scope>test</scope>
    <dependency>
<dependencies>

2、手动添加依赖需要指定所依赖jar包的坐标,但是在大部分情况下,我们是不知道jar包的坐标的。可以通过访问如下网址,在互联网上搜索查询:

http://mvnrepository

或者在公司镜像仓库中搜索查询:

http://maven.tedu/nexus

3、示例:添加c3p0的jar包的坐标到项目中

  • 访问上面其中的一个网址,在搜索框中搜索 “c3p0”

  • 在搜索出来的内容中,选择所需要的版本并点击版本,查看该版本的c3p0
    jar包所对应的坐标:

  • 将坐标直接拷贝到项目的pom.xml文件中即可:



七、 FAQ:Maven常见问题

1.常见Maven环境问题

问题描述1:

  • 创建Maven项目时报如下错误:

  • 或者创建Maven项目目录结构不全(比如只有src目录),如下图:

  • 导入已有的Maven项目,项目运行不了(jar没有下载完全)

此时是因为maven的环境被破坏了,导致Maven基础运行环境不全,无法创建Maven项目,或者无法下载所需要的jar包。解决方法:

(1)在项目的pom文件中敲一个空白行,再保存文件,目的是让maven检测到pom文件发生变化,再根据pom文件中的配置到本地仓库中寻找对应的jar包,如果没有相应的jar包,maven会重新下载。
(2)如果上面的方式不行,可以尝试在项目上,右键---> Maven ---> Update Project...,强制更新项目,此时maven也会检查pom文件,在本地仓库中有没有相应的jar包。
(3)如果上面的方式仍然没有解决问题,检查当前网络环境是否能连接上所配置的镜像仓库。(比如在家里使用外网,无法连接达内的镜像仓库,或者使用手机热点网络无法连接阿里云的镜像仓库等)
	a) 在达内教室,连接的是达内内网:在settings.xml文件中配置连接达内的镜像仓库
	b) 在家里,用的是外网(不是手机热点):可以不配置,默认连接中央仓库,或者 在settings.xml文件中配置连接阿里云仓库
	c) 用的是手机热点网络:不可以配置连接达内镜像仓库或者阿里云仓库,连不上!!
(4)如果网络能够连接上所配置的镜像仓库,到本地仓库的目录下,将本地仓库中所有的目录都删除,删除时,eclipse正在使用本地仓库中的资源文件,所以会阻止删除,此时将eclipse关闭,再将本地仓库中的所有目录删除,重启eclipse。
(5)启动eclipse后,再将上面的第(1)步和第(2)步再做一遍!

问题描述2:

每天第一次打开Eclipse发现之前创建的Maven工程报错(比如项目上有叉号或者叹号,但项目之前是OK的),解决方法:在菜单栏中找到 Project —> Clean…

2.找不到jar包问题

在项目中通过坐标引入了jar包(或者插件),并且本地仓库中也存在对应的jar包,但是项目还是报错,提示内容说找不到。

解决方法:如果引入的jar包,在本地仓库中存在,但是还是提示找不到,可以将本地仓库中jar包或插件的所在目录整个删除(如果删除时提示文件正在被占用,关闭eclipse再删除即可),重新保存pom.xml文件,并更新工程,让maven再次下载上面的jar包即可!

3.拷贝Maven仓库

如果因为网络环境的问题,导致jar包无法下载,也可以将别人下载好的(完整的)Maven的本地仓库拷贝过来,放在自己配置的本地仓库中。因为Maven可以支持拷贝别人的仓库。

第十一章 Cookie、Session

一、 什么是会话

什么是会话:当浏览器发请求访问服务器开始,一直到访问服务器结束,浏览器关闭为止,这期间浏览器和服务器之间产生的所有请求和响应加在一起,就称之为浏览器和服务器之间的一次会话。

在一次会话中往往会产生一些数据,而这些数据往往是需要我们保存起来的,如何保存会话中产生的这些数据呢?

  • 比如在购物过程中,将商品加入购物车,其实就是将商品信息保存到数据库中。(不讨论)
  • 如果在没有登录时,将商品加入购物车,其实就是将商品信息保存到了cookie或session中。

可以使用cookie或者session保存会话中产生的数据。

如何将会话中产生的数据保存到cookie或者是session中?

二、 cookie原理及应用

1.cookie的工作原理

  1. Cookie是将会话中产生的数据保存在客户端,是客户端技术。
  2. Cookie是基于两个头进行工作的:分别是Set-Cookie响应头和Cookie请求头
  3. 通过Set-Cookie响应头将cookie从服务器端发送给浏览器,让浏览器保存到内部;而浏览器一旦保存了cookie,以后浏览器每次访问服务器时,都会通过cookie请求头,将cookie信息再带回服务器中。在需要时,在服务器端可以获取请求中的cookie中的数据,从而实现某些功能。
  4. 浏览器清楚缓存(cookie)快捷键:Ctrl+Shift+Delete

2.cookie的API及应用

① 创建Cookie对象

Cookie c = new Cookie(String name, String value);
// 创建cookie的同时需要指定cookie的名字和cookie要保存的值

② 将Cookie添加到response响应中

response.addCookie( Cookie c );
// 将cookie添加到响应中,由服务器负责将cookie信息发送给浏览器,再由浏览器保存到内部(可以多次调用该方法,添加一个以上的cookie)

③ 获取请求中的所有cookie对象组成的数组

Cookie[] cs = request.getCookies();
// 获取请求中携带的所有cookie组成的cookie对象数组,如果请求中没有携带任何cookie,调用该方法会返回null。

④ 删除浏览器中的Cookie

// cookie的API中没有提供直接删除cookie的方法,可以通过别的方式间接删除cookie
// 删除名称为cart的cookie:可以向浏览器再发送一个同名的cookie(即名称也叫做cart),并设置cookie的最大生存时间为零,由于浏览器是根据cookie的名字来区分cookie,如果前后两次向浏览器发送同名的cookie,后发送的cookie会覆盖之前发送的cookie。而后发送的cookie设置了生存时间为零,因此浏览器收到后也会立即删除!

代码示例:

//创建一个名称为cart的cookie
Cookie c = new Cookie("cart", "");
//设置cookie的最大生存时间为零
c.setMaxAge( 0 );
//将cookie添加到响应中,发送给浏览器
response.addCookie( c );
out.write( "成功删除了名称为cart的cookie..." );
package com.tedu;


import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DeleteCookie extends HttpServlet {
	private static final long serialVersionUID = 1L;
	//删除名称为cart的cookie
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		//1.创建一个名叫cart的cookie
		Cookie c=new Cookie("cart","");
		//2.指定cookie的生存时间为0
		c.setMaxAge(0);
		//3.将cookie添加到响应中
		response.addCookie(c);
		out.write("成功删除了名叫cart的cookie");
	}

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

		doGet(request, response);
	}

}

⑤ Cookie的常用方法

cookie.getName(); // 获取cookie的名字
cookie.getValue(); // 获取cookie中保存的值
cookie.setValue(); // 设置/修改cookie中保存的值(没有setName方法,因为cookie的名字无法修改)
cookie.setMaxAge(); //设置cookie的最大生存时间(如果不设置,cookie默认在一次会话结束时销毁!)

⑥ setMaxAge方法:设置cookie的最大生存时间

如果不设置该方法,cookie默认是会话级别的cookie,即生存时间是一次会话。当浏览器关闭,会话结束时,cookie也会被销毁(cookie默认存在浏览器的内存中,当浏览器关闭,内存释放,cookie也会随着内存的释放而销毁。)
如果设置了该方法,cookie将不会保存到浏览器的内存中,而是以文件形式保存到浏览器的临时文件夹中(也就是硬盘上),这样再关闭浏览器,内存释放,保存到硬盘上的cookie文件不会销毁,再次打开浏览器,还可以获取硬盘上的cookie信息。

代码示例:

//创建一个Cookie对象,将商品信息保存到cookie中
Cookie cookie = new Cookie( "cart", prod  );
//设置cookie的最大生存时间, 单位:秒
cookie.setMaxAge( 60*60*24 );
//将cookie对象添加到response响应中
response.addCookie( cookie );

3.案例:使用cookie模拟购物车

① index.html(HTML代码)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>点击下面的商品连链接,可以将商品加入购物车</h3>
	<!-- 
		http://localhost/day13-cookie/CartServlet
		http://localhost/day13-cookie/index.html
		相对路径可以写成 <a href="CartServlet">
	 -->
	<p><a href="http://localhost/day13-cookie/CartServlet?prod=iphone11">iphone11</a></p>
	<p><a href="http://localhost/day13-cookie/CartServlet?prod=HUAWEIp40">华为p40</a></p>
	<p><a href="http://localhost/day13-cookie/CartServlet?prod=xiaomi8">小米8</a></p>
	<p><a href="http://localhost/day13-cookie/CartServlet?prod=meizu">魅族</a></p>
	
	<h3>点击下面的支付连链接,可以对商品进行结算</h3>
	<!-- 
		http://localhost/day13-cookie/PayServlet
	 	http://localhost/day13-cookie/index.html
	 	相对路径可以写成 <a href="PayServlet">
	 -->
	<p><a href="http://localhost/day13-cookie/PayServlet">支付</a></p>
</body>
</html>

② CartServlet(负责将商品加入购物车)

package com.tedu;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CartServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// 负责将商品加入购物车
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		// 1.从请求中获取要添加的商品信息
		String value = request.getParameter("prod");// getParameter()方法是获取浏览器提交时,自带的参数
		// 2.创建一个cookie对象,并将商品信息保存到cookie中
		Cookie c = new Cookie("cart", value);
		//设置cookie的存活时间(30天)
		c.setMaxAge(60*60*24*30);//单位时:秒
		// 3.将cookie添加到response中,响应给浏览器
		response.addCookie(c);
		out.write("成功将[" + value + "]加入到购物车");
	}

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

		doGet(request, response);
	}

}

③ PayServlet(负责对购物车商品进行结算)

package com.tedu;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PayServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// 负责对购物车商品进行结算
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		// 1.获取请求中的所有cookie信息
		Cookie[] cs = request.getCookies();
		// 2.遍历所有cookie组成的数组(用foreach来遍历)
		String prod = null;
		if(cs != null) {//如果cs不为null(也就是说添加了商品信息),我们才会遍历
			for (Cookie c : cs) {
				// 2.1判断当前遍历的cookies的名字是不是"cart"
				if ("cart".equals(c.getName())) {
					// 2.2说明找到了存放商品的cookie
					prod = c.getValue();
				}
			}
		}
		// 为商品进行结算
		if (prod != null) {
			out.write("成功为" + prod + "支付了1000.00元");
		} else {
			out.write("您还没有将任何商品加入到购物车");
		}

	}

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

		doGet(request, response);
	}

}


二、 session原理及应用

1.session的工作原理

  1. Session是将会话中产生的数据保存在服务器端,是服务器端技术
  2. Session是一个域对象,session中也保存了一个map集合,往session中存数据,其实就是将数据保存到session的map集合中。
  3. 通过session.setAttribute()方法可以将数据保存到session中,通过session.getAttribute()方法可以将数据从session中取出来。

2.session是一个域对象

① 获取session对象

request.getSession() 
// 获取一个session对象;如果在服务器内部有当前浏览器对应的session,则直接返回该session对象;如果没有对应session,则会创建一个新的session对象再返回;

② Session是一个域对象,因此session中也提供了存取数据的方法

session.setAttribute(String attrName, Object attrValue); 
// 往session域中添加一个域属性,属性名只能是字符串类型,属性值可以是任意类型。
session.getAttribute(String attrName);
// 根据属性名获取域中的属性值,返回值是一个Object类型

③ Session域对象的三大特征

(1)生命周期:

创建session:

第一次调用request.getSession()方法时,会创建一个session对象。(当浏览器在服务器端没有对应的session时,调用request.getSession()方法服务器会创建一个session对象。)

销毁session:

  1. 超时销毁:默认情况下,当超过30分钟没有访问session,session就会超时销毁。(30分钟是默认时间,可以修改,但不推荐修改)
//设置session的销毁时间为30秒,超过30秒就会销毁
session.setMaxInactiveInterval(30);//单位:秒
  1. 自杀:调用session的invalidate方法时,会立即销毁session。

  2. 意外身亡:当服务器非正常关闭时(硬件损坏,断电,内存溢出等导致服务器非正常关闭),session会随着服务器的关闭而销毁;

    当服务器正常关闭,在关闭之前,服务器会将内部的session对象序列化保存到服务器的work目录下,变为一个文件。这个过程叫做session的钝化(序列化);再次将服务器启动起来,钝化着的session会再次回到服务器,变为服务器中的对象,这个过程叫做session的活化(反序列化)。

(2)作用范围:

在一次会话范围内(获取到的都是同一个session对象)

(3)主要功能:

在整个会话范围内实现数据的共享

3.案例:使用session模拟购物车

① index.html(HTML代码)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>点击下面的商品连链接,可以将商品加入购物车</h3>
	<!-- 
		http://localhost/day13-Session/CartServlet
		http://localhost/day13-Session/index.html
		相对路径可以写成<a href="CartServlet">
	 -->
	<p><a href="http://localhost/day13-Session/CartServlet?prod=iphone11">iphone11</a></p>
	<p><a href="http://localhost/day13-Session/CartServlet?prod=HUAWEIp40">华为p40</a></p>
	<p><a href="http://localhost/day13-Session/CartServlet?prod=xiaomi8">小米8</a></p>
	<p><a href="http://localhost/day13-Session/CartServlet?prod=meizu">魅族</a></p>
	
	<h3>点击下面的支付连链接,可以对商品进行结算</h3>
	<!-- 
		http://localhost/day13-cookie/PayServlet
	 	http://localhost/day13-cookie/index.html
	 	相对路径可以写成<a href="PayServlet">
	 -->
	<p><a href="http://localhost/day13-Session/PayServlet">支付</a></p>
</body>
</html>

② CartServlet(负责将商品加入购物车)

package com.tedu;

//servlet模板
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class CartServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	//负责将商品加入购物车
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		//1.获取要加入购物车的商品信息
		String value = request.getParameter("prod");
		//2.获取一个session对象,并将商品保存到session中
		HttpSession session = request.getSession();//sessionA
		session.setAttribute("cart", value);
		//3.向浏览器做出响应
		out.write("成功将["+value+"]加入购物车");
		
	}

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

		doGet(request, response);
	}

}

③ PayServlet(负责对购物车商品进行结算)

package com.tedu;


import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class PayServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	//负责对商品进行结算
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");// 预防请求中文乱码
		response.setContentType("text/html;charset=utf-8");// 预防响应中文乱码
		PrintWriter out = response.getWriter();// 获取写出流
		//1.获取之前的session对象(sessionA)
		HttpSession session = request.getSession();
		//2.从session中获取商品信息
		String prod = (String) session.getAttribute("cart");
		//3.为商品结算
		if(prod!=null) {
			out.write("成功为["+prod+"]支付了2000.00元");
		}else {
			out.write("您还没有添加任何商品到购物车");
		}
	
	
	}

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

		doGet(request, response);
	}

}

三、 总结:cookie和session的区别

Cookie和session都属于会话技术,都可以保存会话中产生的数据,但由于cookie和session的工作原理和特点不同,因此两者的应用场景也不一样。

1.Cookie的特点

  1. cookie是将会话中产生的数据保存在浏览器客户端, 是客户端技术(JS可以访问cookie)

  2. cookie是将数据保存在客户端浏览器,容易随着用户的操作导致cookie丢失或者被窃取,因此cookie中保存的数据不太稳定,也不太安全。

  3. 但cookie将数据保存在客户端,对服务器端没有太多影响,可以将数据保存很长时间。

  4. 总结:因此cookie中适合存储需要长时间保存、但对安全性要求不高的数据。

  5. 浏览器对cookie的大小和个数都有限制,一般推荐每一个站点给浏览器发送的cookie数量不超过20个,每一个cookie的大小不超过1kb。

  6. Cookie的应用:实现购物车、记住用户名、30天内自动登录等。

2.Session的特点

  1. session是将会话中产生的数据保存在服务器端,是服务器端技术
  2. session将数据存在服务器端的session对象中,相对更加的安全,而且更加稳定。不容易随着用户的操作而导致session中的数据丢失或者是被窃取。
  3. 但session是服务器端的对象,在并发量较高时每一个浏览器客户端在服务器端都要对应一个session对象,占用服务器的内存空间,影响效率。
  4. 总结:因此session中适合存储对安全性要求较高,但不需要长时间保存的数据。
  5. Session的应用:保存登录状态、保存验证码

四、 扩展内容

1.cookie中保存中文数据的问题

以下问题是针对Tomcat8.0及8.0以下的版本,在Tomcat8.5及8.5以后的版本中已经解决了该问题!

HTTP协议中规定了请求信息和响应信息中不能包含中文数据!

因此通过浏览器向服务器发送中文数据时,浏览器会将中文数据进行URL编码,编码为下面这种格式:

解释: ? 是间隔符,前面是url地址,后面是参数

http://localhost/day13-cookie/index.html?user=%E5%BC%A0%E9%A3%9E%E9%A3%9E

将中文数据转成下面这种格式,叫做URL编码:

张飞飞 ---> URL编码 ---> %E5%BC%A0%E9%A3%9E%E9%A3%9E

将下面这种格式再次转回中文数据,叫做URL解码:

%E5%BC%A0%E9%A3%9E%E9%A3%9E ---> URL解码---> 张飞飞

① 问题:当cookie中保存中文数据,将cookie添加到响应中时,会报一个500异常,如下:

② 解决方法

2.获取不到之前的session的问题

将商品保存到session中后,关闭浏览器再打开浏览器,访问服务器,此时获取不到之前的session。因为session是基于Cookie工作的。

在服务器创建一个session后,会为session分配一个独一无二的编号,称之为session的id,在此次响应时,服务器会将session的id以一个名称为JSESSIONID的cookie发送给浏览器保存到浏览器内部。

由于保存sessionid的cookie默认是会话级别的cookie,在浏览器关闭后,cookie会跟着销毁,sessionid也丢失了。因此下次访问服务器,没有session的id就获取不到之前的session。也获取不到session中的商品信息

解决方法

我们可以创建一个名称为JSESSIONID的cookie,其中保存session的ID,并设置cookie的最大存活时间,让cookie保存到硬盘上(即使浏览器关闭,cookie也不会销毁),这样下次访问服务器时,还可以将sessionid带给服务器,服务器可以通过sessionid获取到之前的session。 从session中获取到商品信息

更多推荐

MySQL、JDBC、HTML、CSS、JavaScript、jQuery、tomcat、Http、Servlet、JSP、EL、JSTL、Maven、Cook