javaweb的学习知识点(SSH之前的学习基础)

Oracle JDBC HTML/CSS Servlet JSP


oracle
数据库,关系型数据库管理系统RDBMS
一、相关概念
数据库:DataBase 用于存储数据、管理数据的。
表:table 用于存储数据,由行和列构成。
行:row 一行代表一条数据,也称实体entity
主键: primary key 用来唯一的标识表中的一行数据
列:column 代表数据的属性,也称字段field
外键:foreign key 用来定义表和表之间的关系
二、服务器端/客户端模式 client/server c/s
必须启动的服务:OracleServiceXE OracleXETNSListener

1、sqlPlus
oracle公司提供的客户端工具,用于操作管理数据。
开始->运行->输入cmd->输入sqlplus,根据提示输入用户名/密码
->在SQL命令提示符下,输入正确的命令(sqlPlus命令和sql命令)
sqlPlus命令:   desc 表名; --显示表结构
如,desc employees;

exit; 退出sqlplus
sql命令: select table_name from user_tables;  --显示和当前用户相关的表
2、iSqlPlus
oracle公司提供的基于浏览器的客户端工具,用于操作管理数据。
http://192.168.0.8:8080/apex
http://localhost:8080/apex
http://127.0.0.1:8080/apex
sql:Structure Query Language 结构化查询语言
用于在DBMS中进行数据的操作(增删改查)
PLSQL: oracle公司在SQL基础上进行了功能扩展

isqlplus、sql、PLSQL有什么区别?

三、查询[重点]
select 字段名1,字段名2 from 表名
1、查询部分列
--查询员工的工号、姓、名、收入
select EMPLOYEE_ID,first_name,last_name,salary from employees;
2、查询所有列
--查询员工的所有信息
select * from employees;

注意:*的可读性较差/会把没有用的列也查出来效率相对较低,所以开发时不建议使用。只作练习使用。
3、对查询出的列进行算术运算
--查询员工的工号、姓、名、月薪、年薪
select EMPLOYEE_ID,first_name,last_name,salary,salary*12 from employees;
4、给查询列起别名
select EMPLOYEE_ID,first_name,salary,salary*12 as 年薪 from employees;

注意:as可以省略;如果别名中包含空格使用""括起来。
如,select EMPLOYEE_ID,first_name,salary "月 薪",salary*12 as 年薪 from employees;
5、字符串拼接 ||
--查询员工的工号、姓、名、姓名
select EMPLOYEE_ID,first_name,last_name,last_name || ' ' ||first_name  from employees;
注意,字符串常量使用''界定。
四、排序[重点]
select 列1,列2 from 表名 order by 列名 asc(升序,默认)|desc(降序),列名2 asc(升序,默认)|desc(降序)
1、单列排序
--查询员工的工号、姓、名、收入,并按收入降序显示
select employee_id,first_name,last_name,salary from employees order by salary desc;
2、多列排序
--查询员工的工号、收入,并按收入降序显示,如果工资相同则按工号从大到小显示

select employee_id,first_name,last_name,salary from employees
order by salary desc,employee_id desc

小结:
select 列名 as 别名,表达式 as 别名
from 表名
order by 列名 规则, 列2 规则2
-----------------------------------------------------------
一、条件查询【重点】
select 列名 from 表名 where 过滤条件 order by 列名 规则
1、等值查询
--查询工资是24000的员工工号、姓、名、收入
select employee_id,last_name,first_name,salary
from employees
where salary=24000

--查询姓King的员工工号、姓、名、收入
select employee_id,last_name,first_name,salary
from employees
where last_name = 'King'
注意,字符串常量用''界定,并且严格区分大小写。
2、> >= < <= != and or not
--查询工资是24000的姓King的员工工号、姓、名、收入
select employee_id,last_name,first_name,salary
from employees
where salary=24000 and last_name = 'King'

--查询工资高于20000的员工工号、姓、名、收入
select employee_id,last_name,first_name,salary
from employees
where salary > 20000

--查询部门90/100中的员工
select employee_id,last_name,first_name,salary,department_id
from employees
where department_id=90 or department_id=100
3、is null | is not null 为空|不为空 【重点】
--查询没有奖金的员工信息
select employee_id,last_name,first_name,salary,commission_pct
from employees
where commission_pct is null

--查询有奖金的员工信息
select employee_id,last_name,first_name,salary,commission_pct
from employees
where commission_pct is not null

4、between 小值 and 大值 | not between 小值 and 大值
--查询工资介于10000-20000之间的员工
select employee_id,last_name,first_name,salary
from employees
where salary >= 10000 and salary <= 20000

select employee_id,last_name,first_name,salary
from employees
where salary between 10000 and 20000
注意:小值在前大值在后;包含边界值。

5、in (值1,值2,值3,...) | not in (值1,值2,值3,...)
--查询部门90/100/60中的员工
select employee_id,last_name,first_name,salary,department_id
from employees
where department_id=90 or department_id=100 or department_id=60

select employee_id,last_name,first_name,salary,department_id
from employees
where department_id in (90,100,60)
6、模糊查询  列名 like '格式字符串' 【重点】
格式字符串:可以包含字符串常量、%、_
%:通配符,代表0-N个字符
_:通配符,代表有且只有1个字符
--查询姓中有字母A或a的员工
select employee_id,last_name,first_name,salary,department_id
from employees
where last_name like '%A%' or last_name like '%a%'

常见错误:格式字符串必须使用''界定; 通配符必须和like运算符一起使用。

--查询姓以A开头的员工
where last_name like 'A%'

--查询姓中第3个字母是a的员工
where last_name like '__a%'

--查询姓中倒数第2个字母是a的员工
where last_name like '%a_'

--查询姓的长度是5并且第3个字母是a的员工
where last_name like '__a__'
二、去除结果中的重复数据
select distinct 列名 from 表 where 过滤条件 order by 。。。。

--查询员工中的经理id
select distinct manager_id 
from employees
三、case ... when ... then ... else... end 等价于java中的switch case语句
--查询员工的工号、姓、收入、收入等级、部门
select employee_id,last_name,salary, case
when (salary < 5000) then '1级'
when (salary between 5000 and 10000) then '2级'
else '3级'
end as 等级, department_id 
from employees 

注意,一个case ..when..then..else..end 代表查询中的1列,如果查询还有其他列,使用,间隔开


*****************************
别名,使用“”界定
字符串常量,使用''界定
字符串常量,严格区分大小写
SQL语句,不区分大小写
*****************************


oracle中日期的默认表示形式是'dd-m月-yy'


--查询员工的工号、姓、月收入、年收入(+提成)
select employee_id,last_name,salary,commission_pct,salary*12+commission_pct
from employees  --error,原因:commission_pct 是null的时候,年薪计算错误

select employee_id,last_name,salary,commission_pct,case
when (commission_pct is null) then salary*12+0
else salary*12+commission_pct
end as 年薪
from employees
四、单行函数:作用于单行数据,有1条数据函数就被执行1次。
1、字符串相关
length(str) --长度
--显示员工工号、姓、姓的长度
select employee_id,last_name,length(last_name)
from employees
--查询员工姓是4位的信息
select employee_id,last_name,length(last_name)
from employees
where length(last_name)=4
substr(str,begin,length)  --截取字符串,从begin(从1开始)开始长度为length
--显示员工工号、姓、姓的前3个字母
select employee_id,last_name,substr(last_name,1,3)
from employees
lower(str)|upper(str)  --转小写|大写
--查询姓中包含A或a的员工
select employee_id,last_name
from employees
where last_name like '%A%' or last_name like '%a%'

select employee_id,last_name,lower(last_name),upper(last_name)
from employees
where lower(last_name) like '%a%'
concat(s1,s2)  --字符串拼接,等价于 ||
--查询工号、姓、名、姓名
select employee_id,last_name,first_name,concat(concat(last_name,' '), first_name)
from employees

2、数学相关
mod(n1, n2)  --取模,n1 % n2
--查询工号是奇数的员工
select employee_id,last_name,mod(employee_id, 2)
from employees 
where mod(employee_id, 2)!=0

round(n1,length)  --四舍五入,对n1四舍五入到小数点后length
--查询员工的工号、姓、收入、日平均工资(保留2位小数)
select employee_id,last_name,salary,round(salary/22,2)
from employees 

3、日期相关
sysdate   --获取系统的当前日期时间
--显示当前日期
select sysdate,employee_id
from employees

--dual,哑表,没有实际意义,只是为了select语句的完整性
select sysdate from dual

to_char(date, '日期格式字符串')  --根据日期格式字符串,把date转成字符串显示【重点】
日期格式字符串: yyyy --4位年
mm --2位月
dd --2位日期
hh12
hh24 --时
mi --分钟
ss --秒
d --星期(1-7)
day --星期(星期天/sunday)
--使用年月日时分秒的格式显示当前日期时间
select to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss day') from dual

--显示当前月份
select to_char(sysdate, 'mm') from dual

--查询员工工号、姓、入职日期、入职月份
select employee_id,last_name,hire_date,to_char(hire_date, 'mm')
from employees

--查询本月份入职的员工
select employee_id,last_name,hire_date,to_char(hire_date, 'mm')
from employees
where to_char(hire_date,'mm')=to_char(sysdate,'mm')

--查询96年2月入职
select employee_id,last_name,hire_date,to_char(hire_date, 'mm')
from employees
where hire_date like '%-2月 -96'

add_months(date, num)  --对月份进行加减(num<0)
--查询上月入职的员工
select employee_id,last_name,hire_date,to_char(hire_date, 'mm')
from employees
where to_char(hire_date,'mm')=to_char(add_months(sysdate,-1),'mm')

对日期的加减,直接使用算术运算符,默认以天为单位,如
--显示昨天的日期
select sysdate-1 from dual

last_day(date) --获取日期的最后一天
select last_day(sysdate) from dual

to_date(str, '日期格式字符串')  --根据日期格式,把字符串转成日期类型
--显示2000-5-1和2000-5-3相差的天数
select to_date('2000-5-1','yyyy-mm-dd') - to_date('2000-5-3','yyyy-mm-dd') from dual

4、处理null
nvl(n1,n2) --判断n1是否为空,为空则返回n2,否则返回n1
--等价于java中的 n1==null?n2:n1

--查询员工的工号、姓、月收入、年薪(+提成)
select employee_id,last_name,salary,commission_pct,salary*12+nvl(commission_pct,0) as 年薪
from employees

----------------------------------------------------------
一、组函数:作用于(分好的)一组数据,有一组数据就产生一个结果。[重点]
max(列名) --求最大值
min(列名) --求最小值
sum(列名) --求和
avg(列名) --求均值
注意:对null忽略处理;所有的组函数都不能使用在where子句中。

count(列名) --对非空列计数
count(*) --对查询结果计数
--统计员工人数
select count(*) from employees;
--统计有部门的员工人数
select count(department_id) from employees;
二、分组[重难点]
--统计有奖金的员工人数
select count(commission_pct) from employees;
select count(*) from employees where commission_pct is not null;

--统计职位的数量
select distinct job_id from employees;
select count(distinct job_id) from employees;

--统计90号部门的员工人数
select count(*) from employees where department_id = 90;

1、语法
select ... from ... where ... group by ... order by ...
--查询各部分的员工人数
步骤1:根据部门把部门相同的人放到一起,形成一组数据
步骤2:对每组数据统计数量
select count(*)
from employees
group by department_id

--查询各岗位的平均工资
select job_id,avg(salary)
from employees
group by job_id

--查询各部门的最高工资
select max(salary)
from employees
group by department_id


--统计90 100各部门的员工人数
select count(*)
from employees
where department_id in (90,100)
group by department_id

--统计各部门的平均工资
select department_id,avg(salary)
from employees
group by department_id


2、使用规则
1)只有在group by子句出现的列,才可以出现在select子句中
2)如果在group by没出现的列,那么配合组函数一起可以出现在select子句中
3)如果在group by中出现了某些函数,那么在select子句中必须使用完全相同的函数(order by中也必须相同)


--统计各部门的平均工资和员工人数
select department_id,avg(salary),count(employee_id)
from employees
group by department_id

select avg(salary),count(*)
from employees
group by department_id

--查询1997年各月份入职的员工人数
步骤1:先找到1997年入职的员工
步骤2:根据月份,把月份相同的分到一起,形成一组数据
步骤3:计数
select to_char(hire_date,'yyyy-mm') as month,count(*)
from employees
where to_char(hire_date,'yyyy')=1997
group by to_char(hire_date,'yyyy-mm')
order by month
3、多列分组
--查询各部门各岗位的员工人数
步骤1:根据部门,把部门相同的人放到一起,形成1组数据
步骤2:在每个组中,根据岗位,把岗位相同的人再放到一起,形成1组数据
步骤3:计数
select department_id, job_id,count(*)
from employees
group by department_id, job_id




--查询平均工资高于10000的各部门平均工资
select department_id,avg(salary)
from employees
where avg(salary) > 10000
group by department_id     --错误,原因:组函数不能使用在where子句中

上午回顾:
count(*)
count(列名)  count(distinct 列名)
思考:select count(1) from dual   --1
select count(null) from dual   --0

select count(commission_pct) from employees; --35
运行过程:分别把查询结果中(107数据)的commission_pct的值做为实参代入count函数中计数  假设第1数据的commission_pct值是 null, count(null) 返回0
  2 0.4, count(0.4) 返回1
select count(1) from employees; --107
select count(null) from employees; --0

select ... from ... where ... group by 列1,列2 order by ...

三、having 对分组后的结果进行过滤,满足条件则保留
语法:select .. from .. where .. group by .. having 过滤条件 order by ...
--查询平均工资高于10000的各部门平均工资
select department_id,avg(salary)
from employees
group by department_id 
having avg(salary) > 10000

having 和 where 的区别:
1) where 对分组前的数据过滤; having对分组后的数据过滤。
2) 在where 和 having 都可以使用的前提下,where的效率高于having,因此where优先考虑。
3) 如果使用分组后的结果进行过滤时,则只能使用having(不能用where)

--统计1997年各月份入职人数超过3人的部门及员工数
select to_char(hire_date,'yyyy-mm') as month,count(*)
from employees
where to_char(hire_date,'yyyy')=1997
group by to_char(hire_date,'yyyy-mm')
having count(*) > 3

完整的select语句语法:
select 列名,表达式,函数 
from 表名
where 过滤条件
group by 列1,列2
having 过滤条件
order by 列 排序规则

select语句的执行顺序:
from 确定要查询的表
where 对表中的原始数据进行过滤
group by 根据分组字段,进行分组
having 对分组后的数据进行过滤
select 查询
order by 对查询结果排序

select 列,表达式 as 别名
from 表
where 别名 --不能用别名,原因where先执行,select后执行
order by --能用别名

-------------------------------
四、伪列
1、rowid 唯一标识表中的一行数据,借助于数据在硬盘中物理存放地址计算得到的。
select employee_id,last_name,salary,rowid from employees

select *,rowid from employees --错误,原因*代表所有列,在其后又出现其它内容,oracle就不知道*的具体含义

解决方案:
select employees.*,rowid from employees

为了简化SQL语句,可以给表起个别名 : from 表名 别名 
select  e.*,rowid from employees e
注意:给表起别名,没有as关键字。
2、rownum 给查询结果编号,第1个符合条件的数,编号1;第2个符合的编号2.....
select employee_id,last_name,salary,rownum from employees

--查询前5个员工信息
select employee_id,last_name,salary,rownum 
from employees
where rownum<6

--查询第6-10个员工
select employee_id,last_name,salary,rownum 
from employees
where rownum between 6 and 10 --错误,原因rownum在判断过程中不符合条件的不被编号

注意:rownum在使用时,只能进行< <= =1 >=1 运算,不能进行其他的> >=等运算。

五、子查询[难点]
在查询语句中嵌套了select语句。
1、子查询的结果是单个值(1行1列) 把子查询当成单个值,直接代入主查询。
--查询具有最高工资的员工
步骤1:查询最高工资 -->maxSalary
select max(salary) as maxSalary from employees
步骤2:查询有最高工资的员工
select employee_id,last_name,salary 
from employees 
where salary = maxSalary --伪代码
步骤3:合成(把子查询直接代入)
select employee_id,last_name,salary 
from employees 
where salary = (select max(salary) from employees)

--查询工资比平均工资高的员工
步骤1:查询平均工资
select avg(salary) as avgSalary from employees
步骤2:查工资比平均工资高的员工
select employee_id,last_name,salary 
from employees 
where salary > avgSalary --伪代码
步骤3:合成(把子查询直接代入)
select employee_id,last_name,salary 
from employees 
where salary > (select avg(salary) as avgSalary from employees)

--错误示例:employee_id和avg(salary)没有可比性
select employee_id,last_name,salary 
from employees 
where employee_id = (select avg(salary) as avgSalary from employees)

----------------------------------------------------------------------
回顾:
--查询员工的最高工资
select max(salary) from employees

--查询各部门员工的最高工资
select department_id,max(salary)
from employees
group by department_id

--查询具有最高工资的员工
select * from employees where salary = (select max(salary) from employees)

--查询各部门具有最高工资的员工
假设,查90号部门具有最高工资的员工
步骤1:查90号部门具有最高工资  -->maxSalary
select max(salary) from employees where department_id=90
步骤2:查90号部门具有最高工资的员工
select employee_id,last_name,salary
from employees
where salary=maxSalary and department_id=90  --伪代码
步骤3:合成
select employee_id,last_name,salary
from employees
where salary=(select max(salary) from employees where department_id=90) and department_id=90
假设,查100号部门具有最高工资的员工
步骤1:查100号部门具有最高工资  -->maxSalary
select max(salary) from employees where department_id=100
步骤2:查100号部门具有最高工资的员工
select employee_id,last_name,salary
from employees
where salary=maxSalary and department_id=100  --伪代码
步骤3:合成
select employee_id,last_name,salary
from employees
where salary=(select max(salary) from employees where department_id=100) and department_id=100
写最终SQL语句:
步骤1:查某个部门的最高工资
select max(salary) from employees where department_id=?
步骤2:查当前部门具有本部门最高工资的员工
select employee_id,last_name,salary
from employees
where salary=maxSalary and department_id=?  --伪代码
步骤3:合成
select employee_id,last_name,salary
from employees
where salary=(select max(salary) from employees where department_id=?) and department_id=?
解决?的问题:
select employee_id,last_name,salary,department_id
from employees e1
where salary=(select max(salary) from employees where department_id=e1.department_id)    ---最终版

SQL语句对比:
--查具有最高工资的人
select * from employees    where salary = (select max(salary) from employees)
--查各部门具有最高工资的人
select * from employees e1 where salary = (select max(salary) from employees where department_id=e1.department_id)

2、子查询的结果是多个值(N行1列),把子查询的结果当作枚举值,直接代入主查询
--查询和King(last_name)同部门的员工
步骤1:查King在的部门  -->80,90
select department_id from employees where last_name = 'King'
步骤2:查询和King(last_name)同部门的员工
select employee_id,last_name,salary,department_id 
from employees 
where department_id in (80, 90)
步骤3:合成(直接代入)
select employee_id,last_name,salary,department_id 
from employees 
where department_id in (select department_id from employees where last_name = 'King')
3、子查询的结果是N行N列, 把子查询的结果(虚表)当作临时表使用。
--查询工资最高的前5个员工
select employee_id,last_name,salary  
from employees 
where rownum <= 5
order by salary desc   --错误,原因:order by最后执行

步骤1:按工资降序排  -->tb1
select employee_id,last_name,salary from employees order by salary desc,employee_id desc 
步骤2:查询工资最高的前5个员工
select employee_id,last_name,salary
from tb1
where rownum <= 5
步骤3:合成(把查询结果当成临时表)
select employee_id,last_name,salary
from (select employee_id,last_name,salary from employees order by salary desc,employee_id desc) tb1
where rownum <= 5

--按工资排前6-10的员工
select employee_id,last_name,salary
from (select employee_id,last_name,salary from employees order by salary desc,employee_id desc) tb1
where rownum between 6 and 10  --错误,原因:rownum只能参加< <= =1 >=1的运算。

步骤1:查工资排前10的员工  -->tb2
select employee_id,last_name,salary,rownum rn
from (select * from employees order by salary desc,employee_id desc) tb1
where rownum<=10
步骤2:查按工资排前6-10的员工
select employee_id,last_name,salary
from tb2
where rn>5
步骤3:合成
select employee_id,last_name,salary
from (select employee_id,last_name,salary,rownum rn
from (select * from employees order by salary desc,employee_id desc) tb1
where rownum<=10) tb2
where rn>5

因为rownum只能参加< <= =1 >=1的运算,如果想使用>运算时会出错,解决方案:给rownum起个别名,使用别名进行>操作。

--查询第3-5名入职的员工
select * 
from (select tb1.*,rownum rn 
from (select * from employees order by hire_date) tb1
where rownum <= 5) tb2
where rn >= 3
一、集合运算符
union 合并2个查询结果,去除重复数据行
union all 合并2个查询结果,不去除重复数据行
minus 差集,在第1个结果中去除第2个结果中存在的数据
intersect 交集,两个查询结果中相同的数据

select employee_id,last_name,salary from employees where department_id=60 --5
select employee_id,last_name,salary from employees where department_id=70 --1
select employee_id,last_name,salary from employees where department_id=80 --34

select employee_id,last_name,salary,department_id from employees where department_id in (60,70) --6
union all
select employee_id,last_name,salary,department_id from employees where department_id in (70,80) --35

二、表连接[难点]
查询的过程中,数据来源于多个表,这种查询称为表连接查询。
分类:内连接、外连接、自连接、交叉连接
语法:
select ...
from 表名1
[] join 表名2
on 连接条件
where .. group by ... having .. order by...

1、内连接 [inner] join
--查询员工的工号、姓名、收入、部门名称
select employee_id,last_name,salary,department_name
from employees
inner join departments
on employees.department_id = departments.department_id 

特点:1)必须要有连接条件
  2)内连接的结果:符合(连接)条件的数据
  3)2个表的顺序没有要求

--查工号是100的员工姓名、收入、部门编号和部门名称
select last_name,salary,e.department_id,department_name
from employees e
inner join departments d
on e.department_id = d.department_id 
where e.employee_id = 100

--查询各部门具有最高工资的员工(思考题)*****************************************

2、外连接 
左外连接 left [outer] join 
右外连接 right [outer] join
全部外连接 full [outer] join

--查询所有员工的工号、姓名、收入、部门名称
select last_name,salary,e.department_id,department_name
from employees e
left outer join departments d
on e.department_id = d.department_id 

左外连接的特点:1)必须要有连接条件
  2)左外连接的结果:符合(连接)条件的数据 + 左表中没有匹配上的数据
  3)2个表有顺序要求,以左表为主,右表为辅
  4)左表中没有匹配上的数据,如果显示右表中的字段值填充null
 
右外连接的特点:1)必须要有连接条件
  2)右外连接的结果:符合(连接)条件的数据 + 右表中没有匹配上的数据
  3)2个表有顺序要求,以右表为主,左表为辅
  4)右表中没有匹配上的数据,如果显示左表中的字段值填充null
--查所有部门的员工信息
select last_name,salary,e.department_id,department_name
from departments d
left outer join employees e
on e.department_id = d.department_id 

select last_name,salary,e.department_id,department_name
from employees e
right outer join departments d
on e.department_id = d.department_id 

--查没有员工的部门(思考题)***************************************************

完全外连接的特点:1)必须要有连接条件
  2)完全外连接的结果:符合(连接)条件的数据 + 左表中没有匹配上的数据 + 右表中没有匹配上的数据
  3)2个表没有顺序要求
  4)没匹配上的数据填充null
预习内容:
1、自连接
--查询员工的工号、姓、收入、经理姓名
2、交叉连接
3、多表连接
4、建表
5、增删改数据
6、修改删除表


------------------------------------------------------------
--查询各部门具有最高工资的员工(思考题)*****************************************
1) 查各部门最高工资  -->tb1
select max(salary) as maxSalary , department_id from employees group by department_id
2) 和 employees 连接查询(条件: 部门id相同)
select employee_id,last_name,salary,department_id
from employees e
join tb1
on e.department_id = tb1.department_id
where e.salary = tb1.maxSalary
3)合成
select employee_id,last_name,salary,e.department_id
from employees e
join (select max(salary) as maxSalary , department_id from employees group by department_id) tb1
on e.department_id = tb1.department_id
where e.salary = tb1.maxSalary

--查没有员工的部门(思考题)***************************************************
select employee_id,last_name,salary,e.department_id,d.department_name
from employees e
right join departments d
on e.department_id = d.department_id
where employee_id is null

--查工资前6-10的员工
1) 排序  -->tb1
select * from employees order by salary desc
2) 查前10 -->tb2
select employee_id,last_name,salary, rownum rn
from tb1
where rownum <= 10
3) 查6-10
select *
from tb2
where rn >= 6
4) 合成
select employee_id,last_name,salary
from (select employee_id,last_name,salary, rownum rn
from (select * from employees order by salary desc) tb1
where rownum <= 10) tb2
where rn >= 6

--显示今天是星期几
select to_char(sysdate, 'day') from dual

--显示2008-8-8是星期几
select to_char(to_date('2008-8-8','yyyy-mm-dd'), 'day') from dual

3、自连接 特殊的内连接,参与连接的2个表相同。
--查询员工的工号、姓、收入、经理姓名
select e1.employee_id,e1.last_name,e1.salary,e2.last_name,e2.first_name 
from employees e1
join employees e2
on e1.manager_id = e2.employee_id

4、交叉连接[了解] 笛卡尔积
表1  5
表2  8
交叉连接后的结果:5*8=40
关键字: cross join
特点: 没有连接条件

select 
from employees, departments
where .... --相当于先交叉连接,再进行过滤,效率相对较低,不建议使用

5、多表连接
select ...
from 表1
[inner/left/right/full] join 表2
on 连接条件
[inner/left/right/full] join 表3
on 连接条件
where ... group by .. having.. order by ..
--查询所有员工的工号、姓、部门名称、所在地名称
select e.employee_id,e.last_name,e.department_id,d.department_name,d.location_id,l.street_address
from employees e
left join departments d
on e.department_id = d.department_id
left join locations l
on d.location_id = l.location_id
SQL语言的分类:
DQL:Data Query Language 数据查询语言   select  [重点]
DDL: Data Define Language 数据定义语言 create alter drop
DML: Data Manipulate Language 数据操纵语言 insert update delete [重点]
DCL: Data Control Language 数据控制语言 grant revoke 
TCL: Transaction Control Language 事务控制语言 commit rollback

一、建表
create table 表名(
列名 数据类型 [default 默认值] [[constraint 约束名] 约束类型] [[constraint 约束名] 约束类型],
列2 数据类型 [default 默认值] [[constraint 约束名] 约束类型]
)

1、数据类型
number(v1,v2) 数字类型,长度是v1(不含小数点),其中小数位长v2
number(5) 1/123456(error)/123.456-->123/123456.123(error)
number(5,2)  123.12/123.12345-->123.12
number 默认小数位占7位,总长38
varchar2(v1) (可变)字符串类型,长度是v1,存放1-4000Byte
char(v1) 固定字符串类型,长度是v1,存放1-2000Byte
varchar2(5)  存'abc',实际存储为'abc'
char(5) 存'abc',实际存储为'abc  '
date 日期,存储年月日时分秒
注意:oracle没有布尔类型,如果想存储此类数据,可以使用char(1)/number(1)
大数据类型:clob 字符型的大数据类型,如文本文件等
blob 二制型的大数据类型,如音频等文件
2、约束[重点]
1)主键 primary key
唯一标识表中的一行数据
特点:非空 唯一
2)非空 not null
当前列的内容必须要有值
3)唯一 unique
当前列的值不允许重复
4) 检查约束(自定义约束)  check(规则)
如,邮箱必须有@符号 check(email like '%@%')
性别只能是'男''女' check(sex in ('男','女'))
5)外键 references 主表(主键)
用来定义2个表之间的关系,当前列的值只能引用主表中主键字段中有的值。
cid(pk) cname
clazz:  1 java18班
2 java20班
3 java22班

stuid stuname clzid
studends: 1 zs 1
 2 ls 1
 3 ww 2
 5 aa 1
 4 zl 4 --错误,原因没有编号为4的班级
3、默认值 default 值
当前列的内容在不输入的情况,系统会默认分配的值。
4、建表
班级表(编号、名称)
学生表(学号、姓名-必填、性别-男/女、婚姻状况、邮箱@、年龄、手机号11、身份证号码)

create table clazz(
clzid number(10) primary key,
clzname varchar2(20) 
)

插入:
insert into 表名 values(值1,值2,....) --往表中插入一条数据,同时指定所有字段的值

insert into clazz values(1,'java18班')
insert into clazz values(12345678901,'java20班') --错误,原因编号长度太大
insert into clazz values(1234567890.6789,'java20班') --正确,编号值1234567891

create table students(
stuid number(10) constraint stu_stuid_PK primary key,
name varchar2(20) not null,
sex char(3) check(sex in ('男','女')),
married number(1) default 0 check(married in (1,0)),
email varchar2(50) check(email like '%@%'),
birthday date,
phononum char(11),
cardid char(18) unique
)

删除表:drop table 表名
drop table students


--添加外键字段
create table students(
stuid number(10) constraint stu_stuid_PK primary key,--定义主键,同时指定约束名stu_stuid_PK
name varchar2(20) not null,
sex char(3) check(sex in ('男','女')),
married number(1) default 0 check(married in (1,0)),
email varchar2(50) check(email like '%@%'),
birthday date,
phononum char(11),
cardid char(18) unique,
clz_id number(10) references clazz(clzid)
)

外键约束补充点:特点-允许为空,允许重复
外键字段的值,必须引用主表中主键/唯一字段存在的值。
5、联合约束
一个字段不能定义出完整的约束条件,需要使用多个字段时,这种约束称联合约束
语句:约束类型 (列名1,列名2,...)
约束类型,通常只有主键、唯一约束。
primary key(field1,filed2)
unique(field1, field2)

create table sc22(
stuid number(10) references students22(stuid),
cid number(10) references course22(courseid),
score number(5,2),
primary key (stuid, cid) --联合主键
)

create table students22(
stuid number(10) primary key,
name varchar2(20)
)

create table course22(
courseid number(10) primary key,
coursename varchar2(20)
)

根据约束定义的位置不同,分为2类:
列级约束,在列的定义后面;
表级约束,在所有列的后面,如联合约束。

DDL小结:约束的分类、含义、特点[重点]。
------------------------------------------------------------------
一、DML 语句[重点]
1、insert
插入所有列
insert into 表名 values(值1,值2,....) --插入1条数据,同时为所有列指定值

--插入1个学生信息
insert into students values(4,'zl','男',1,'zl@aa.aa','9-9月-99','11111111111','111111111111111118',1)

注意:值列表,必须和表结构中的字段列表完全一致(类型/个数/顺序)

插入部分列
insert into 表名(列名1,列2,....) values(值1,值2,....) ----插入1条数据,同时为指定列赋值
注意:值列表,必须和表名后的字段列表完全一致(类型/个数/顺序);
 在insert中未指定列,必须是允许为空或有默认值。

--插入学生信息(5 王五 男 )
insert into students (stuid,sex,name) values(5,'男','王五')

--插入学生信息(5 王五 男 email-null married-0)
insert into students(stuid,sex,name,email) values(6,'男','王6')--错误,没有足够值
insert into students(stuid,sex,name,email) values(6,'男','王6',null)
insert into students(stuid,sex,name,email,married) values(6,'男','王6',null,0)
insert into students(stuid,name,sex,married,email) values(8,'王8','男',0,null)

2、update
update 表名 set 列名1=值,列2=值2 [where 过滤条件]

--所有男生的婚姻状态改成未婚
update students set married=0 

--把学号是5改成女生
update students set sex='女' where stuid=5

3、delete
delete from 表名 [where 过滤条件]

--删除学号>6的学生
delete from students where stuid>6

--删除所有学生
delete from students --删除所有学生信息,表结构存在,同时记录日志文件,可以恢复

删除表中的所有数据
truncate table 表名 --截断表,删除表中所有数据,不记录日志文件,不能恢复

--删除编号是2的班级
delete from clazz where CLZID=2 --正确,原因2班没有学生

--删除编号是1的班级
delete from clazz where CLZID=1 --错误,原因1班有学生(有外键引用关系的数据)

解决方案:
一:  把1班的学生转到2班
update students set clz_id=2 where clz_id=1
删除1班
delete from clazz where CLZID=1

二: 删除1班的学生
delete from students where clz_id=1
删除1班
delete from clazz where CLZID=1

三: 去除外键约束
删除1班

注意: 删除主表中数据时,必须保证,在从表中没有外键引用的数据。


二、事务
事务:数据库中的最小执行单元(由1条或N条SQL语句构成), 
当所有SQL都执行成功时才代表事务成功;
有1个SQL语句执行失败,事务失败,所有SQL都失败。


事务成功/提交: commit
事务失败/回滚: rollback

Oracle server会为每个连接client开辟一块内存空间(回滚段),在事务结束前,所有SQL语句的执行结果
存放在回滚段中,事务成功则把回滚段中的数据持久化到数据库中,所有的client都可以看到结果;
事务失败则清空回滚段中的结果,对数据库没有任何影响。

事务开始:写完SQL语句,向server发请求
事务结束:1) 多个DML语句构成,事务commit/rollback代表结束;
  2) 1个DDL语句, DDL执行结束即代表事务结束,相当于自动提交commit
  3)多个DML语句,退出时,正常退出时自动提交事务commit
非正常退出时自动回滚rollback。
例:insert1/insert2/update1/create/insert3/insert4,结果是什么?
insert1/insert2/update1/create
insert1/insert2/update1/create/insert3/insert4/exit,结果是什么?
insert1/insert2/update1/create/isert3/insert4

多用户并发操作,数据库中的数据可能出现的问题:
假设帐户表accounts中有2数据:1 zs 1000
  2 ls 2000
1)脏读
B事务读到了A事务没有提交的数据。
update accounts
set balance=500
where accountid=1;
select balance
from accounts
where accountid=1;   --500
update accouts ....
commit;

解决方案: 把事务的隔离级别设成 read_commited 可提交读
  oracle中默认就是此级别。
2)不可重复读
A事务,在B事务的2个查询中间,修改了数据并且成功提交,导致B事务两次读到的数据不一致。

select balance
from accounts
where accountid=1;  --1000
update accounts
set balance=500
where accountid=1;
....
commit;
select balance
from accounts
where accountid=1;   --500
解决方案: 把事务的隔离级别设成 repeatable_read 可重复读
oracle中不支持此级别。但是,提供解决方案:行级锁
select balance from accounts where accountid=1 for update;--对查询结果添加行级锁
select ... from ... for update wait n;  --对查询结果加行级锁,n秒后仍未获取则提示“繁忙”
select .. from ..for update nowait; --对查询结果加行级锁,不等待直接提示“繁忙”

3)幻读
A事务,在B事务的2个查询中间,插入了数据并且成功提交,导致B事务两次获取的数据条目不一致.
select count(*) from accounts; --2
insert into accounts
values(3,'ss',222);
commit;
select count(*) from accounts; --3
解决方案:隔离级别设成 serializable 可序列化
oracle中支持此级别,但是,设成此级别后相当于1个client在操作,并发性丢失。

标准SQL中数据库的隔离级别:
1)read_uncommited(没有级别) : 看到脏读数据
2)read_commited: 解决了脏读的问题
3)reapeatable_read: 解决了脏读、不可重复读的问题
4)serializable: 解决了脏读、不可重复读、幻读的问题

事务的特点ACID:
1)原子性 Atomic 同一事务中的多个SQL语句是不可分割的整体,成功都成功,1个失败则失败。
2)一致性 Consistency 事务执行前后数据的状态是一致的。
3)隔离性 Isolation 多用户并发操作时,事务的隔离级别。
4)永久性 Durability 事务执行前后,对数据库的影响是永久的。


三、序列[重点]
1、语法
create sequence 序列名 --从1开始,1次+1
create sequence 序列名 start with 2  --从2开始,1次+1
                      increment by 2  --1次递增2
  maxvalue  5000 --最大值
2、使用
序列名.nextval  --获取序列中的下一个值
序列名.currval --获取序列中的当前值

注意:序列创建后,被所有的表共用。
序列中,被使用过的值,不能重新获取。

例子:
create sequence seq_students start with 2; --创建序列
insert into students(stuid,sex,name,email) values(seq_students.nextval,'男','王4',null) --使用序列
select seq_students.nextval from dual     --使用序列

3、删除序列
drop sequence 序列名;
四、视图
1、语法
create view 视图名 as select ....
2、使用
select * from 视图名
3、补充说明:
1)视图是起了名字的查询语句,可以重复使用
2)视图中的数据占存储空间?  不单独占存储空间
3)视图能不能提高执行效率?  视图只是简化SQL语句,不能提交效率
4)通过view可以修改表中的数据,不建议使用,因为限制较多。

drop view 视图名  --删除视图

五、索引
语法:create index 索引名 on 表名(字段)
create index idx_students on students(name)
使用:oracle会自动应用。
注意:1)索引占存储空间
 2)创建的原则:在经常使用查询的列上;大数据表中查询小部分数据
 3)主键、唯一键,会自动创建索引
 4)索引能提高查询效率。

drop index 索引名 --删除索引


六、修改表
1、添加列
alter table 表名 add ( 列名 数据类型 [默认值][约束],
列名2 数据类型 [默认值][约束]
)
2、删除列
alter table 表名 drop (列名1, 列2)
3、修改列名
alter table 表名 rename column 旧列名 to 新列名
4、修改列的其它属性
alter table 表名 modify (列名 数据类型 [默认值][约束],
列名2 数据类型 [默认值][约束]
)
5、添加约束
alter table 表名 add 约束类型(列名1, 列2)
6、删除约束
alter table 表名 drop constraint 约束名
七、删除表
drop table 表名
---------------------------------------------------
一、数据库设计
数据库设计优秀的方案:1)节省存储空间(减少数据冗余)
2)保证数据的准确/正确性(数据完整性)
3)方便企业应用调用(结构清晰/关系明了)
软件开发过程:
需求分析-对用户实际的业务数据、数据处理过程进行调研
概要设计-E-R图
详细设计-表结构/建表
编码-使用JDBC编程
测试-....
1、E-R图 实体关系图 Entity-Relationship
实体(实体属性)  : 矩形 
关系:直线,并添加特殊的符号用来描述具体关系。
1对1:
1对N:
N对N:
2、建表
1)1对1(示例:学生sid/sname、电脑cid/ctype)
s1 zs
s2 ls

c01 DELL
c02 HP
解决方案1:
students: sid/sname/com_id(FK+Unique)
s1 zs c01
s2 ls c02
s3 ww -
完整的SQL语句:
create table students(
sid number primary key,
name varchar2(20),
com_id number references computer(cid) unique --外键约束+唯一约束
)
create table computer(
cid number primary key,
ctype varchar2(20)
)
解决方案2:
computer: cid/ctype/stu_id(FK+Unique)
c01 DELL s1
c02 HP s2
完整的SQL语句:
create table students(
sid number primary key,
name varchar2(20)
)
create table computer(
cid number primary key,
ctype varchar2(20),
stu_id number references students(sid) unique  --外键约束+唯一约束
)

如果两个实体是一对一的关系,在建表时,外键+唯一键 在任表中定义都可以。
通常情况,定义在相对更重要的一方。

2)1对N(示例:学生sid/sname、班级cid/cname)
s1 zs
s2 ls
s3 ww

c01 java20班
c02 java22班

clazz: cid/cname/stu_id(FK)
c01 20班 s1
c01 20班 s2 --错误,违反了PK
studnets: sid/sname/clz_id(FK)
s1 zs c01
s2 ls c01
s3 ww c02
完整的SQL语句:
create table clazz( --"一"的一方
cid number(10) primary key,
cname varchar2(20)
)
create table students( --"多"的一方
sid number(10) primary key,
sname varchar2(20),
clz_id number(10) references clazz(cid)
)
如果两个实体是一对多的关系,在建表时,外键必须在 “多”的一方创建。

3)N对N(示例:学生sid/sname、课程courseid/cname)
s1 zs
s2 ls
s3 ww

c01 java
c02 oracle

students:sid/sname/courseid(FK)
s1 zs c01
s1 zs c02 --错误,违反了PK
course:courseid/cname/stuid(FK)
c01 java s1
c02 oracle s1
c01 java s2 --错误,违反了PK
sc:sid(FK)/cid(FK)/score
s1 c01 80
s1 c02 70
s2 c02 90
s1 c01 --s1已经选过c01

完整的SQL语句:
create table students( --"多"的一方
sid number(10) primary key,
sname varchar2(20)
)
create table course( --"多"的一方
courseid number(10) primary key,
cname varchar2(20)
)
create table sc( --关系表
sid number(10) references students(sid),--学号
cid number(10) references course(courseid),--课程号
score number(5,2), --成绩
primary key(sid, cid)
)


如果如果两个实体是多对多的关系,在RDBMS中,必须引入第3个表(关系表),
把原本的1个N对N,转换成2个 1对N 关系。
3、范式
在数据库设计过程中存在的一组规范,实际可以遵循,如果遵循会给设计过程提供一些指导性参考。

1)一范式 1NF :  列的原子性(列的不可再分)
sid  name slike(兴趣爱好)
1 zs 游戏/球/棋/书/画
2 ls 棋/书/画
3 ww 游戏/球
4 zl 游戏/棋

解决方案:把非原子的列分出来,构成一个新表

sid sname

id slike
1 游戏
2

2)二范式 2NF: 属性 不允许 部分依赖于 主键属性
sid sname cid cname score
1 zs 1 java 90
2 ls 2 oracle 80
1 zs 2 oracle 90

sid + cid --> PK
sname 依赖 sid
cname 依赖 cid

解决方案:把存在部门依赖的字段分出来,构成新表。

sid sname

cid cname

sid cid score

3)三范式 3NF: 属性 不允许 传递依赖于 主键属性
sid(PK) sname clzid clzname
1 zs 1 java18班
2 ls 1 java18班
3 ww 2 java20班

clzname --> clzid --> sid

解决方案:把存在传递依赖的字段分出来,构成新表。

clzid clzname

sid sname clzid


注意:1)使用范式在一定程度上减少了数据冗余,节省了存储空间。
 2)使用范式在一定程度上增加了表的数量,多表查询时,效率降低。
 3)时间、空间,靠经验,找平衡点。
-----------------------------------------
一对一:  外键(添加唯一约束)可以在任何一方,通常放在更重要一方。 (学生、电脑)
create table computer(
cid number primary key,
ctype varchar2(20)
)
create table students(
sid number primary key,
sname varchar2(20),
com_id number references computer(cid) unique --学生使用的电脑编号
)
一对多:外键必须定义在“多”的一方。  (学生、班级)
create table clazz(
cid number primary key,
cname varchar2(20)
)
create table students(
sid number primary key,
sname varchar2(20), 
clz_id number references clazz(cid) --学生所在班级编号
)
多对多:在RDBMS,必须借助第3个表(关系表),把1个多对多的关系,转换成2个1对多的关系。
(学生、课程,学生选课)
create table students(
sid number primary key,
sname varchar2(20)
)
create table course(
courseid number primary key,
coursename varchar2(20)
)
create table sc(
sid number references students(sid),
cid number references course(courseid),
primary key(sid, cid)
)




JDBC: Java DataBase Connector
Java操作数据库的驱动。
一、JDBC的发展
1、java -->  ODBC(c语言) -->  DB    桥连
2、java --> 本地clientAPI --> DB
3、java --> DB 直连[重点]
class OracleDriver{
//
void lianjie(int m){
//.....
}

//
String guan(String s1, String s2){
//...
}
}

class DB2Driver{
//
int conn(int m,int n){
//.....
}

//
void close(){
//...
}
}

class JavaApp{
public static void main(String[] args){
OracleDriver driver = new OracleDriver();
driver.lianjie(2);
driver.guan("","");


DB2Driver driver = new DB2Driver();
driver.conn(2,5);
driver.close();
}
}



interface Driver{
void getConn(String s1, String s2, String s3);
void close(int m, int n);
}
class OracleDriver implements Driver{
void getConn(String s1, String s2, String s3){
}
void close(int m, int n){
}
}
class DB2Driver implements Driver{
void getConn(String s1, String s2, String s3){
}
void close(int m, int n){
}
}
class JavaApp{
static private Driver driver;  //成员变量
JavaApp(Driver driver){ //带参构造
this.driver = driver; //给成员变量赋值
}
public static void main(String[] args){
driver.getConn("","","");
driver.close(1,3);
}
}
4、java --> webServer配置 --> DB       连接池


二、JDBC包含的内容
1、接口 SUN公司提供 JDK中,java.sql和javax.sql包中
2、实现类 数据库厂商提供,到主页下载。 ojdbc5.jar。

jar包的使用:
1、UE记事本。 修改环境变量classpath的值,.;E:\javaweb\jdbc\ojdbc5.jar
2、IDE。 Java项目右键菜单-->Build Path-->Configure Build Path
-->点击“Add External Jars...”按钮-->选择ojdbc5.jar-->OK,OK。


三、JDBC的操作步骤[重点]
1个准备,6个步骤
准备工作:jar导入java项目中。

6个步骤:
1、注册驱动类  Class.forName("oracle.jdbc.OracleDriver");
2、创建连接 String url="jdbc:oracle:thin:@localhost:1521:xe";
Connection conn = DriverManger.getConnection(url,"hr","hr");
3、创建 Statement Statement stm = conn.createStatement();
4、执行SQL语句 String sql = "insert...";//在sqlplus里,确认SQL语句能正确运行
int row = stm.executeUpdate(sql);
5、处理执行结果(针对select)
6、释放资源(后打开的先关闭)
if (stm != null)
stm.close();
if (conn != null)
conn.close();

获取本机数据库SID的方法:使用system使用sqlplus,然后输入:select instance from v$thread;


四、常见的问题
1、 java.lang.ClassNotFoundException: oracle.jdbc.OracleDrfgdiver
解决方案:1)检查驱动类是否正确,包含包名。
2)检查ojdbc5.jar是否导入到java项目的build path中。
2、 java.sql.SQLException: No suitable driver found for jdbc:ordfacle:thin:@localhdfedsefost:15241:xe22
解决方案:检查数据库连接字符串url
3、 java.sql.SQLException: IO 错误: The Network Adapter could not establish the connection
解决方案:检查URL中的IP地址/port;确认oracle服务已正确启动。
4、 java.sql.SQLException: Listener refused the connection with the following error:
ORA-12505, TNS:listener does not currently know of SID given in connect descriptor
解决方案:检查URL中的SID
5、java.sql.SQLException: ORA-01017: invalid username/password
解决方案:检查用户名/密码
6、java.sql.SQLException: ORA-01756: 引号内的字符串没有正确结束/值过大/违反了...约束/标识符无效
解决方案:检查SQL语句
----------------------------------
1、executeUpdate(sql)[重点] 执行insert/update/delete语句,返回int类型,代表影响的行数。
2、executeQuery(sql)[重点] 执行select语句,返回 ResultSet 类型,代表查询到的虚表。
在JDBC中,ResultSet借助于指针来具体操作数据。默认指针在第1行数据的上方。
boolean rs.next()  //指针下移一行,返回布尔类型,代表是否有数据。
Xxx rs.getXxx("字段名") //获取指针指向的数据行中某个字段的值。
//其中Xxx代表取出的数值类型。
3、execute(sql)[了解] 执行DDL/DML语句,返回 boolean 类型,代表 第1个结果是否是ResultSet。
true -- 执行了select语句;
false -- 执行的不是select 语句。
stm.getResultSet() //获取结果集
课堂练习:1、查询帐户表中的所有帐户的编号、姓名、密码
 2、查询帐号是3的帐户信息,姓名、余额
 3、创建表test_22,字段id number、name varchar2 长度30。
 


一、应用程序体系结构
1、单机版
必须安装软件
维护麻烦(view/service/data)
2、C/S 结构(客户端/服务器)
必须安装client软件
维护比较麻烦(view/service)
3、C/S/S 结构(客户端/业务服务器/数据服务器)
必须安装client软件
维护(view改变--client需要维护;service改变--server需要维护)
4、Browser/Server/Server  结构
client只要有browser即可,不需要培训;
view改--server需要维护;
需要使用http协议访问,只要有internat即可访问。
二、Tomcat
免费开源的 web应用服务器,遵循JavaEE规范。

1、tomcat的配置:1) 必须要有环境变量 java_home : Jdk的安装目录
 2)默认端口8080,但是被oracle占用,修改端口8989
tomcat安装根目录/conf/server.xml
修改 <Connector port="8080" protocol="HTTP/1.1" 
把8080-->8989,save。
3)启动。  安装根目录/bin/startup.bat 双击。
在控制台界面,找“信息: Starting Coyote HTTP/1.1 on http-8989”
"信息: Server startup in 387 ms" 
同时,没有异常信息,服务器启动成功。
4)测试。   http://ip:port
http://192.168.0.3:8989
http://localhost:8989
http://127.0.0.1:8989
服务器的停止:1)安装根目录/bin/shutdown.bat 双击.
2)关闭控制台窗口。
注意:tomcat服务只能启动一次。
三、手工完成第1个Servlet
Ser[ver]Let: 在server上运行的java写的小的程序,用于接收client的请求,处理,响应结果给client。

1、写代码
implements Servlet  必须重写所有的方法
extends GenericServelt 重写service方法,与http协议无关
extends HttpServlet 重写service方法,与http协议相关
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletExcetpin, IOException{
//设置响应类型
response.setContentType("text/html");

//获取输出流
PrintWriter out = response.getWriter();

//使用输出流
out.println("<html>");
out.println("<head><title>这是第一个Servlet</title></head>");
out.println("<body>");
java.util.Date now = new java.util.Date();
out.println(now);
out.println("</body>");
out.println("</html>");
out.flush();
}
注意:导入 tomcat安装根目录/lib/servlet-api.jar。
2、编译
3、部署(目录结构/copy)[重点]
1)根据JavaEE规范,创建目录结构
tomcat安装根目录/webapps/myweb(web应用名称)
|+ WEB-INF 必须
|-web.xml 必须,有且只能有1个
(copy ROOT目录中的,修改,保留XML的第1行和web-app成对的根标签)
|+classes 存放class文件
|+lib 存放jar包
|+css
|+images
|+user
|- .html .css .gif
2)copy 编译后的类文件(连同包结构)到 tomcat根目录/webapps/myweb/WEB-INF/classes。
4、配置[重点]
把不能直接运行.class 转换成 可直接访问的 web资源。

必须在web.xml中配置:
<servlet> <!-- 配置servlet的相关信息 -->
<servlet-name></servlet-name><!-- 别名,唯一 -->
<servlet-class></servlet-class><!-- servlet全类名 -->
</servlet>
<servlet-mapping> <!-- 把class映射成url可访问的资源 -->
<servlet-name></servlet-name><!-- 别名,唯一 -->
<url-pattern></url-pattern><!-- 请求servlet时url的地址格式,必须以/开头 -->
</servlet-mapping>

5、运行
http://ip:port/web应用名称/url-pattern
http://localhost:8989/myweb/hello

四、常见问题
1、404 请求资源无效
检查URL地址栏中的字符
2、405
public void service(HttpServletRequest req,HttpServletResponse req) throws ServletException,IOException{}
检查service方法的方法签名。
3、500
java源码运行出异常;
web.xml中<servlet-class>全类名写错。
五、请求servlet的三种方式
1、URL请求
http://ip:port/web应用名称/url-pattern
2、超链接请求
<a href='/web应用名称/url-pattern'>test</a>
3、表单请求
<form action='/web应用名称/url-pattern'>.....</form>

servlet中,获取client的数据:String str = request.getParameter("元素名称");
==========================================================
一、使用MyEclipse简化开发
1)配置tomcat
window菜单-->首选项-->左侧展开MyEclipse-->展开servers-->展开tomcat6.X
-->点击JDK,确认JDK已配置-->点击tomcat6.X-->右侧界面,选择Enable,选择tomcat安装根目录-->OK.

停止MyEclipse自带的tomcat:window菜单-->首选项-->左侧展开MyEclipse-->展开IntegatedSendBox-->点击MyEclipseTomcat-->右侧界面,选择Disable。
二、Servlet的生命周期[理解,难点]
1、从请求发出到响应回来的完整过程
1)输入URL后,回车,向server发请求
2)解析URL地址,获取/web应用名称,定位web.xml
3)解析URL地下,获取/url-pattern,定位<url-pattern>
4)匹配到对应的<servlet-mapping><servlet-name>
5)匹配到对应的<servlet><servlet-name>
6)定位到<servlet-class>
7)server检测当前内存中是否存在当前类的对象
不存在-使用反射,创建当前类的对象
存在-直接使用获取的对象
8)进入生命周期方法service,处理客户端请求,最后把结果响应client。

2、生命周期
调用方法 调用时间 调用次数
实例化 构造方法 第1请求 1
初始化 init 构造之后 1
服务 service 有请求即调用 N
销毁 destroy 停服务器 1

特殊情况:<servlet>
<servlet-name></servlet-name>
<servlet-class></servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>

3、线程安全
在Servlet中尽量避免定义成员变量,以保证多线程访问时,临界资源的安全。
如果避免不了,在对临界资源做修改的代码上,添加线程安全锁sychronized。

4、ServletContext 和 ServletConfig
1)ServletContext是web.xml文件在内存中的表现。
一个web应用对应一个ServletContext对象。

应用:获取web.xml中的全局初始化参数。
参数的配置:<servlet>标签外面.
<context-param>
<param-name>count</param-name>
<param-value>1000</param-value>
</context-param>
获取全局参数:// 获取ServletContext对象
ServletContext context = super.getServletContext();
// 获取全局参数
String val = context.getInitParameter("参数名");

2)ServletConfig 代表web.xml中Servlet的配置信息。
一个servlet对应着一个ServletConfig对象。

应用:获取web.xml中的局部初始化参数。
参数的配置:<servlet>标签内部。
<init-param>
<param-name>age</param-name>
<param-value>10</param-value>
</init-param>
获取局部参数://获取ServletConfig对象
ServletConfig conf = super.getServletConfig();
//获取局部参数
String val = conf.getInitParameter("参数名");
三、Servlet通讯[重点]
1、include包含: 
1个Servlet中包含另个1个Servlet的执行结果,2个Servlet的结果之和一起响应到client。

/includeA
/includeB

// 获取请求分发对象
RequestDispatcher rd = request.getRequestDispatcher("目标Servlet的url-pattern");
// 包含
rd.include(request,response);

servlet中的图片路径:<img src='/web应用名称/路径/文件名'/>
2、forward 请求转发[重点]
(功能)逻辑和表示(界面)分离。

// 获取请求分发对象
RequestDispatcher rd = request.getRequestDispatcher("目标Servlet的url-pattern");
// 请求转发
rd.forward(request, response);



// 请求转发时,向目标servlet传数据
request.setAttribute(String, Object); //往request中设置命名属性

// 获取命名属性
Object obj = request.getAttribute(String);

forward小结: 1)逻辑Servlet,步骤:
[获取client的数据]
调用业务层的方法,实现业务功能;
[往request中设置命名属性]
跳转View
2)界面Servlet,步骤:
设置响应类型
获取输出流
[获取request中的命名属性]
使用输出流,实现动态显示

forward特点:1)服务器内部的跳转
2)共享同一个request对象
3)地址栏不变
4)永久转向,看到最后1个Servlet的结果
=================================
一、重定向 
response.sendRedirect("/web应用名称/url-pattern");//转servlet
response.sendRedirect("/web应用名称/路径/文件名");//转页面

sendRedirect特点:1)client的跳转
2)不共享request对象
3)地址栏改变
4)永久转向,看到最后1个Servlet的结果

使用forward的场景:ActionServlet要向ViewServlet传递数据。
使用sendRedirect的场景:地址栏要改变。

JavaWeb中路径的写法:
1、客户端的路径(/web应用名/url-pattern)
sendRedirect
url地址栏
所有的HTML标签中的路径
<form action=""
<a href=""
<img src=""

2、服务器端的路径(/url-pattern)
forward
include

req.getRequestDispatcher("http://www.sina.com.cn").forward(req,res);//error,原因只能做站内资源的跳转
res.sendRedirect("http://www.sina.com.cn");//OK
==============================================
一、Cookie 
是 server 往 client 写的一小段文本信息,由2部分构成:name 和 vlaue。

1、创建cookie
Cookie c = new Cookie("name", "zs");
Cookie c2 = new Cookie("pwd", "111");
2、写cookie
response.addCookie(c);
3、读cookie
Cookie[] cookies = request.getCookies();

c.setMaxAge(n);//设置cookie的有效时间,单位秒
n>0 //n秒后,cookie失效
n<0 //IE关闭,cookie失效
n=0 //删除cookie
二、session [重点]
会话,同一client的多次请求过程。

1、创建session
HttpSession session = request.getSesseion(true);
// true -- 判断当前session是否存在,不存在则创建;存在则使用。
// false -- 不存在则null;存在则使用。
2、作用域(对象)
开始:第1个调用getSession(true)
结束:超时/invalidate()
session.setMaxInactiveInterval(n);//设置session的有效时间为n秒
web.xml:   <session-config>
<session-timeout>10</session-timeout><!--单位分钟-->
 </session-config>
使用:session.setAttribute(String, Object);
Object obj = session.getAttribute(String);
session.removeAttribute(String);
3、特点
1)session 和 client(浏览器软件) 一一对应。
2)session 的作用周期比 request长。
4、场景
同一client的多次请求要共享数据。


5、session 和 cookie的关系
client第1次请求server(执行getSession(true)),server会为当前client创建session对象
(同时分配唯一的sessionId),server响应client时把sid封装到http响应头中写入
本地cookie(maxage=-1,IE关cookie即失效);
client再次请server时,client会读取本地cookie中的sid一起发送请求到server,server根据
sid确认到对应的session对象,从而实现会话跟踪。

如果cookie被禁用:String strPath = response.encodeURL(path);
如果本地cookie被禁用,则自动在原path后添加jsessionid,
格式 url;jsessionid=xxxxx
三、request、session、ServletContext
1、request 请求
1) String name = request.getParameter("username");
2)作用域[对象]
开始:写URL,回车
结束:server处理结束,响应回到client(看到响应结果)
使用:request.setAttribute(String, Object);
request.getAttribute(String);
3)场景
功能逻辑Servlet 请求转发到 界面Servlet,同时界面Servlet要使用
功能逻辑Servlet中的值。
同一请求中的多个Servlet要共享数据。
2、ServletContext
1) 是 web.xml文件中内存中的表现。ServletContext和web应用 一一对应。
2)作用域[对象]
开始:server启动
结束:server停止
使用:context.setAttribute(String, Object);
context.getAttribute(String);
context.removeAttribute(String);
3)场景
不同的client共享数据。

思考:下面的数据,最合适的作用域应该是什么?
用户的登录信息(session)   登录时的错误提示消息(request)   在线用户列表(context)
当前servlet的访问次数(servlet的成员变量/context)     购物车(session)        webQQ群聊内容(context)
   用户权限(session)


应用:登录/强制用户登录/安全退出。[重点]
================================================
1、client往server发送数据
<form action="" method="get|post"
<a href="url?id=3&name=zs"
url?id=3
response.sendRedirect("/day4/url-pattern?id=3");
2、server接收client的数据
//获取client的数据(get|post)
str = request.getParameter("表单元素名|get方式传的参数名");
3、2个servlet是否可以传递数据?   可以
怎么传?   
request.setAttribute(String, Object);
forward
4、servlet获取另外一个servlet传的值?
obj = request.getAttribute(String);
5、serlvet如果解释乱码?
网页中的乱码:  response.setCharacterEncoding("GBK");//字符集和java源码一致
表单输入了中文,导致DB中出现乱码(仅适于post方式):
request.setCharacterEncoding("GBK");//字符集和输入页面一致
str = request.getParameter("");
6、跳转 forward 、sendRedirect 区别:
forward: 服务器内部转;地址栏不变;共享同一个request;看到最后一个结果;站内资源
sendRedirect:client转;    变;    不共享reqeust;最后一个结果;转站外资源
7、request、session、ServletContext
-------------------------------------
一、连接池
1)配置 tomcat安装根目录/conf/context.xml
准备工作: ojdbc5.jar copy到 tomcat安装根目录/lib中。

</Context>结束标签前,添加
<Resource driverClassName=""
url=""
username=""
password=""
name="jdbc/yyl" 连接池的名称
type="javax.sql.DataSource" 连接池的类型
auth="Container" 由tomcat容器管理连接池
minActive="" 最小连接数
maxActive="" 最大连接数
maxIdle="" 最大空闲连接数
maxWait="" 最大等待时间,秒
/>
2)使用
JdbcUtilPool类中的getConn方法:
JNDI:Java Naming and Directory Interface Java的命名与目录接口
根据给定的路径和资源,获取对象
// 获取JNDI的默认根目录
Context context = new InitialContext();
// 根据给定的路径和资源,获取数据源
Object obj = context.lookup("java:comp/env/jdbc/yyl");
DataSource ds = (DataSource)obj;
// 获取连接
conn = ds.getConnection();

JdbcUtilPool类的release方法:
if (conn != null){
conn.close();//把连接归还给连接池
t.remove();
}
二、过滤器 Filter
JavaEE中的一类组件,用于对request和response做编码,然后根据结果继续传到web资源或client.

1)写代码
implements Filter   重写接口中的所有方法
public void init(FilterConfig conf)throws ServletException
public void destroy() 
puvlic void doFilter(ServletRequest req, ServletResponse res, 
FilterChain chain) throws java.io.IOException, ServletException{
// 过滤功能
// 把request或response往后传递
chain.doFilter(req,res);
}
2)配置 web.xml
<filter>
<filter-name></filter-name>
<filter-class></filter-class>
</filter>
<filter-mapping>
<filter-name></filter-name>
<url-pattern></url-pattern><!-- 指定需要过滤资源的URL格式 -->
<!--可能包含通配符*,必须以/开头-->
/*    当前web应用中的所有资源
/user/*   web应用中user子文件夹中的资源过滤
/abc/*
</filter-mapping>
<filter-mapping>
<filter-name></filter-name>
<url-pattern></url-pattern>
</filter-mapping>


应用:编码设置[重点] /*
登录验证[重点]
需要过滤的资源:添加/修改/删除/查询   servlet/html
把需要过滤的资源放到对应的文件夹中,html页面-->module文件夹中
                                   /原url-pattern-->/abc/原url-pattern










JSP: Java Server Page 
接收client的请求,处理,响应结果到client。
是 Servlet发展中的一个升级产品。
JSP界面;servlet功能逻辑。
---------------
一、语法
后缀.jsp
存放位置: web应用根目录或子目录中,除 WEB-INF 以外。(同html页)
html中的所有内容,加入特殊标签嵌入java代码。

运行: http://ip:port/web应用名称/路径/文件名

二、脚本元素
表达式脚本: <%=表达式 %>    计算表达式的值,并且输出
注意:不能有分号;表达式必须有计算结果。
声明脚本:<%!  %> 声明变量或方法
普通脚本:<%   %>   写普通的JAVA代码。通常用于写流程控制语句。

注意,三类脚本不允许嵌套。
JSP的注释:<%-- --%>
<!-- -->
三、JSP的执行过程
1)翻译   .jsp -> .java
2)编译  .java  -> .class
3)执行 .class

翻译规则:  <%= %>  -->  service方法的out.print()
<%!  %>  -->  成员变量和成员方法
<%  %> -->   service方法的普通语句
html内容-->   service方法的out.write()

代码阅读,有错改错,没错输出结果:
<%!
int m=5; //1
%>
<%
int m=10; //2
%>
<%=m%> //3   =10
<%=this.m%> 5
--------------------------
<%
int m=10; //1
%>
<%=m%> //2 10
<%=this.m%> //3 5
<%!
int m=5; //4
%>
注意:jsp在第1次执行时会慢,以后变慢。原因:(。。。。。)

四、指令元素
<%@ 指令名称 属性名="值" 属性名2="值2" %>
1、page指令: 整个JSP页面都有效特性说明。
1) contentType 设置页面的响应类型和字符集
<%@ page contentType="text/html" %>
<%@ page contentType="text/html;charset=gbk" %>
2) import  导包/类,默认导入4个包
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.*,java.io.*" %>
3)isErrorPage="true|false(默认)" 标识当前页面是否是错误处理页
<%=exception %>
4)errorPage="url"   标识当前页面出现异常,由url指定的资源来处理异常
5)session="true(默认)|false" 当前页面是否能直接获取session
6)language="java"
7)isELIgnored="true|false" 是否忽略EL表达式
isELIgnored="false"  运行EL
2、include 指令
<%@ include file="" %>
在当前页面包含另外一个页面。

特点:静态包含/源代码包含/编译前包含
注意:避免多个页面中,变量的重复定义。

3、taglib 指令  : 当前页面中,要使用标签库。
<%@ taglib prefix="" uri="" %>

五、动作元素
<jsp:动作名称 属性名="值" 属性名2="值2">标签内容</jsp:动作名>
<jsp:动作名称 属性名="值" 属性名2="值2" />
1、<jsp:include page="">
当前页面中包含另外一个页面。
特点:动态包含/结果包含/编译后包含

等价于:request.getRequestDispatcher("/logo").include(request,response);

<jsp:include page="">
<jsp:param name="" value="" /> 向被包含的页面传参数
</jsp:include>

logo.jsp 获取参数: str = request.getParameter("age");

2、<jsp:forward page="">
从当前页面,转发到,另外资源。

等价于:request.getRequestDispatcher("/logo").forward(request,response);

<jsp:forward page="">
<jsp:param name="" value=""/> 向被转向的页面传参数
</jsp:forward>

获取参数:str = request.getParameter("name");
------------------------------
JSP隐含对象
不需要new可以直接使用的1组对象。只能在<%= %>和<% %>中使用。
1、request -->  HttpServletRequest
str = request.getParameter("");
request.setAttribute(String , Object);
obj = request.getAttribute(String);
2、response -->  HttpServletResponse
response.sendRedirect("");
3、out --> javax.servlet.jsp.JspWriter
out.println()
out.write()
4、session -->   javax.servlet.http.HttpSession
session.setAttribute(String, Object);
obj = session.getAttribute(String)
session.removeAttribute(String);
session.invalidate();
5、application -->  javax.servlet.ServletContext
web.xml内存中的表现/作用域(最大)
application.setAttribute(String, Object);
obj = application.getAttribute(String)
application.removeAttribute(String);

str = application.getInitParameter(String);

6、config --> javax.servlet.ServletConfig
7、page  --> java.lang.Object
当前JSP页面本身,类似this。
8、exception   --> java.lang.Throwable
只能在 isErrorPage=true 的JSP页面中使用。
代表当前页面中捕获到异常信息。
9、pageContext  --> javax.servlet.jsp.PageContext
1)代表作用域,开始:访问页面开始
结束:离开当前面结束
代表当前JSP页面本身。
pageContext.setAttribute(String, Object);
obj = pageContext.getAttribute(String)
2)访问所有作用域中的命名属性
request.setAttribute("reqAtt","value1");

//依次从小到大,在4个作用域对象中,搜索命名属性,搜索到则停止。
pageContext.findAttribute("reqAtt");

//使用pageContext往session作用域中设置命名属性
pageContext.setAttribute(String,Object,PageContext.SESSION_SCOPE)
3)获取所有的JSP隐含对象
pageContext.getOut() --> out
pageContext.getServletContext() --> application
4)有inclue/forward方法
小结:
pageContext/request/session/application
response/out
exception
config/page
------------------------------------
EL Expression Language 表达式语言
${表达式}  计算表达式的值,并且输出
<%@ page isELIgnored="false" %>
1、普通运算
${1+1}
${5>3}   ${5 gt 3}
> gt >= ge
< lt <= le
== eq
!= ne
&& and
|| or
! not

${empty n }   判断n是否是null "" '',集合中是否有元素
2、访问作用域中的命名属性[重点]
<%=request.getAttribute("acc") %> ${acc}
<%=((Account)request.getAttribute("acc")).getAccountNo() %>   ${acc.accountNo}


${acc}   从小到大,搜索4个作用域,搜索到即停止
等价语句:<%=pageContext.findAttribute("acc") %>
${requestScope.acc }   从reqeust作用域中,获取命名属性
等价语句:<%=request.getAttribute("acc") %>
${acc.accountNo}
等价语句:obj = pageContext.findAttribute("acc");
 Account a = (Account)obj;
 a.getAccountNo();
${sessionScope.acc.accountNo}
等价语句:obj = session.getAttribute("acc");
 Account a = (Account)obj;
 a.getAccountNo();
3、获取client数据[重点]
<%=request.getParameter("username") %>  -->  ${param.username}
${param.username}
等价语句:<%=request.getParameter("username") %>

4、获取cookie的值
${cookie.JSESSIONID}   获取名字叫JSESSIONID的cookie
${cookie.JSESSIONID.value} 获取名字叫JSESSIONID的cookie的值
等价语句:Cookie[] cookies = request.getCookies();
for(int i=0; i<cookies.length; i++){
Cookie c = cookies[i];
if (c.getName().equals("JSESSIONID")){//获取名叫JSESSIONID的cookie
c.getValue();//获取cookie的值
}
}
小结: EL中的隐含对象,只能在EL中使用。
pageScope/requestScope/sessionScope/applicationScope   EL中的作用域对象
param client的请求数据
cookie client的cookie
pageContext

5、 pageContext 可以在EL中使用JSP隐含对象
${pageContext.request.contextPath}
${pageContext.session.id}
6、访问数组/集合
<%
int a[]={3,2,5,8};
%>
${a} //error. a必须是命名属性

<%
int a[]={3,2,5,8};
pageContext.setAttribute("mya",a);
%>
${mya} //ok.
${mya[0]}

${mylist[0].username} //mylist必须是命名属性
${mymap.keyname} //mymap必须是命名属性
//keyname是键名,作用根据键名取键值


JSTL
Java Standard Tag Library Java标准标签类库
<%@ taglib prefix="" uri="" %>
prefix:前缀,一旦定义当前JSP必须使用完全相同的前缀名,包括大小写
uri:tld(标签库描述符文件)文件的唯一标签
自定义标签使用:<前缀:后缀 属性名=“值”  属性名2=“值2”>内容</前缀:后缀>
<前缀:后缀 属性名=“值”  属性名2=“值2”/>

<jsp:forward page="" />
<yyl:hello count="5" />

JSTL的分类:核心标签库、国际化与格式化标签库、SQL标签库、XML标签库

1 [重点]、<c:if test="" >内容</c:if>    等价于if语句 
test: 判断条件,布尔表达式,可以是<%= %> ${}
注意,没有对应的else分支。
2、<c:choose> 等价于switch语句
<c:when test="">内容1</c:when> 等价于case语句
<c:when test="">内容1</c:when>
<c:otherwise>其它</c:otherwise> 等价于default语句
</c:choose>
test: 判断条件,布尔表达式,可以是<%= %> ${}
3 [重点]、<c:forEach begin="" end="" step="" var="">内容</c:forEach>   等价for语句(固定次数)
begin: 开始值
end: 结束值
step: 步长(增量)
var: 变量

<c:forEach items="" var="" varStatus="">内容</c:forEach>  等价forEach语句(对list遍历)
items: 要遍历的集合,注意,必须是集合的内容,通常<%=%> ${}
var: 变量,每遍历1次用于存放集合元素的变量

<c:forEach items="${map}" var="m" varStatus="s">内容</c:forEach>  等价forEach语句(对map遍历)
items: 要遍历的集合,注意,必须是集合的内容,通常<%=%> ${}
var: 变量,每遍历1次用于存放集合元素(键值对)的变量
${m.key} ${m.value}
varStatus: 迭代状态对象,count-迭代次数,从1开始,2,3。。。。
 index-迭代索引,集合迭代索引从0开始,依次+1
固定次数的循环,index同循环变量
4、<c:set var="" value="" scope=""/>     往作用域中设置命名属性
var: 命名属性的名
value: 命名属性的值,通常是常量、<%= %>、${}
scope: 作用域,可取值 page(默认)|request|session|application
5、<c:out value="" default="" escapeXml=""/>  输出命名属性
value: 要输出的内容,通常是<%= %> ${}
escapeXml: 是否显示HTML中的特殊标签如< >
可取值:true(默认)|false
6、<c:remove var="" scope=""/>  删除命名属性
var: 要删除的命名属性名称
scope: 作用域,默认不指定scope时,删除所有作用域中的。
7、<c:redirect url=""/>   重定向
url: 要转向的资源地址,注意:url是服务器上的路径。
----------------------------
MVC: 
设计模式,框架级的设计模式。不涉及具体的编程语言。
M: Model 模型-业务的实现和数据的表示。
V: View  视图-输入/输出界面。
C: Controller 控制器-控制整个应用的流程,从哪儿来--干什么--去哪儿。
1)获取view中的数据
2)调用Model中的方法,实现业务功能
3)根据结果,跳转到 View 显示

SUN基于MVC,进行了JAVA语言的实现,Model-II体系结构。
Model -- Java业务类和DAO类
View  --  JSP/html
Controller -- Servlet(ActionServlet)
1) 接收client请求,获取JSP数据
2) 调用业务层的方法,实现业务功能
3) 根据结果,跳转 JSP




<hr color = 'red'/>
  <table border = '1' align = 'center'>
  <tbody>
  <tr>
  <th colspan = '9' bgcolor = 'red'>通讯录信息</th>
  <th colspan ='3' bgcolor = 'red'>管理员操作</th>
  </tr>
 
  <tr align = 'center'>
  <td>序号</td>
  <td>名字</td>
  <td>电话</td>
  <td>座机</td>
  <td>邮箱</td>
  <td>城市</td>
  <td>生日</td>
  <td>分类</td>
  <td>删除标识</td>
  <td colspan = '3'>
  <a href = '/TelBook/ForceLogin/RegisterPersonView'>添加新的联系人</a>
  </td>
  </tr>
  </tbody>
  </table>











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值