WEB的学习
* 服务器
* 网络的架构(面试题)
* C/S client/server 客户端/服务器端 例子:QQ 快播 暴风影音
* 优点:交互性好,服务器压力小。
* 缺点:客户端更新了,下载。
* B/S browser/server 浏览器/服务器端 例子:购物网站 12306
* 缺点:服务器压力大。
* 优点:服务器更新就ok。
* WEB网页 javaweb 使用java开发网页。
* 静态的web资源
* HTML、CSS、JAVASCRIPT
* 动态的web资源
* 数据发生变化。例子:
* Servlet/JSP
* ASP .net 微软开发
* PHP 开源(开发网站那速度真快),提供模板。
* RUBY 小日本开发的。
* JAVA做什么?后台的管理系统,处理大数据。
* 服务器的介绍
* 原理:网络编程。
* 概念:
* 硬件:就是一台主机。
* 软件:安装了服务器的软件(tomcat)。
* 安装软件后,称为WEB服务器。
* 启动服务器,访问资源。
* 访问:http://+ip+端口号 找到主机。如果资源的文件,就可以访问了。
* 访问百度:http://www.baidu.com/
* HTTP协议默认端口号是80,可以不写。
* ping www.baidu.com 61.135.169.121
* 如果想访问本机的服务器(扩展)
* http://localhost:80
* http://127.0.0.1:80
* 常见服务器
* WebLogic BEA公司开发的(被Oracle收购了) 收费的 支持JAVAEE所有的规范(EJB servlet/jsp规范)
* (JAVA MySql(Oracle) WebLogic)
* WebSphere IBM公司开发的 收费的 支持JAVAEE所有的规范(EJB servlet/jsp规范)
* 银行喜欢用
* Tomcat apache(开源的组织,非常的伟大)只Servlet/JSP规范。免费的。
* Tomcat的安装和注意实现
* (前提条件)安装JDK,配置java的环境变量。
* 安装版(给客户用)
* 解压版:解压当前文件夹,目录不要有中文。
* 启动服务器:tomcat/bin/startup.bat(启动服务器)
* 访问:http://192.168.24.215:8080访问服务器的主页。
* 关闭服务器:点关闭按钮,shutdown.bat
* 常见启动问题
* 配置环境变量。C:\Program Files (x86)\Java\jdk1.6.0_16
* 一闪而过,环境变量没配置好。
* 端口占用问题
* java.net.BindException: Address already in use: JVM_Bind
* 端口被占用。
* 解决问题:
* 结束掉占用端口的应用程序。
* 找到占用端口的应用程序。
* 通过命令 netstat –ano,在任务管理器中结束程序。
* 修改端口号。
* 修改配置文件了。
* tomcat/conf/server.xml配置文件
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
* 有应用程序占用80端口,用上边的方式解决。
* 有的WIN7系统自带World wide web publish IIS服务,默认占用80端口。
* 如果有。去服务中,该手动启动。
* 概率非常小。操作系统占用80端口。
* Tomcat目录结构
* bin(**) 启动项,关闭项。
* conf(*****) 里面Tomcat配置文件
* lib(**) 服务器运行使用的jar包
* logs(***) 日志文件,运行时产生的日志。
* temp(**) 运行时临时文件
* webapps(*****) web applications(web的应用们)
* work(*****) JSP翻译成Servlet程序
* 如果动态的WEB资源,把WEB资源发布到服务器的webapps目录下的时候,有固定的目录结构。
* 静态的web资源
* HTML、CSS、JAVASCRIPT
* 动态的web资源
* 数据发生变化。例子:
* Servlet/JSP
* 开发动态的WEB资源程序,目录结构如下(必须记住)
* Servlet/JSP只要包含,就称为动态的WEB资源
website
| --- 存放 HTML CSS JAVASCRIPT JSP 图片
WEB-INF
|
web.xml 程序的入口。配置文件(必须有的)
classes 文件夹,名称固定的 可选的
lib 文件夹,名称固定 可选的
* Tomcat和MyEclipse的集成
* window -- 选项 -- MyEclipse -- servers -- 选择tomcat -- 选择Enable -- 选择tomcat根目录
* 配置JDK -- 选择JDK。
* 启动服务器。
* 虚拟路径(访问路径)
* 默认和项目名称是相同(不要去修改)。
* WebRoot变成了虚拟路径(访问的路径)
* 直接复制项目,需要修改虚拟路径。
* 项目上右键 -- 选择属性 -- MyEclipse -- web -- 修改虚拟路径。
* 扩展
* window -- show view -- servers
* Tomcat管理员的配置(了解)
* 在tomcat/conf/tomcat-user.xml
<role rolename="manager"/>
<user username="admin" password="admin" roles="manager"/>
* 如何部署WEB程序(三种)
* 项目复制到webapps目录下。
* 通过配置虚拟路径的方式。
* 直接修改配置文件
* 写到tomcat/conf/server.xml
* 找到<Host>标签,配置到Host标签的中间
* 目的:通过配置,配置访问路径,准确找到c:\bb的文件
* <Context docBase="文件夹的真实目录" path="虚拟路径(访问路径)" ><Context>
* <Context docBase="C:\bb" path="/itcast" ></Context>
* 访问:http://localhost:80/itcast
* 自己编写一个配置文件(格式)(推荐使用)
* 自定义xxx.xml结尾文件,在$CATALINA_HOME/conf/[enginename]/[hostname]/ directory.目录下。
* 把xxx当成虚拟(访问)路径。
* 在xml的文件中编写。
* 在哪个目录下:
* $CATALINA_HOME/conf/[enginename]/[hostname]/ directory.
* 如果找引擎的名称和主机的名称,在server.xml中找。
* tomcat/conf/Catalina/localhost/ccc.xml
* ccc.xml的文件编写什么内容?
* <Context docBase="C:\cc"></Context>
* 访问:http://localhost:80/ccc
* 配置虚拟主机(了解)
* HTTP的协议
* 啥是HTTP的协议:超文本传输协议。
* 基于TCP链接的传输协议。
* HTTP协议默认是80
* 基于请求和响应的模式。(先有请求,后有响应)
* www.baidu.com(访问百度的服务器)
* 百度的首页返回给我
* 客户端连上web服务器后,若想获得web服务器中的某个web资源,
需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式
* 使用telnet命令,快速了解http协议。
* 如果是win的系统,开启telnet的命令。
启动服务器.
cmd>telnet localhost 80 回车
telnet命令有一个乱码问题 解决乱码.
解决 ctrl+] 回车
注意:命令行中输入的内容不能写错,一旦写错了,不支持删除。
请求:
GET /aa/1.html HTTP/1.1
host:localhost
输入两次回车
* 使用telnet命令发送了一次请求
* 请求:
GET /aa/1.html HTTP/1.1
host:localhost
* 响应:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
ETag: W/"21-1416624196782"
Last-Modified: Sat, 22 Nov 2014 02:43:16 GMT
Content-Type: text/html
Content-Length: 21
Date: Sat, 22 Nov 2014 07:33:53 GMT
<h1>hello java!!</h1>
* HTTP协议的版本
* HTTP/1.0
* 链接后,只能获取一个web资源。
* 链接后,发送请求,服务器做出响应,链接立即断开。
GET /aa/1.html HTTP/1.0
host:localhost
* HTTP/1.1(使用)
* 链接后,可以获取多个web资源。
* 链接后,发送请求,服务器做出响应,链接不会立即断开。
再次发送请求,直接有一段时间没操作,自动断开。
GET /aa/1.html HTTP/1.1
host:localhost
* 请求:
* 请求行
* 请求方式
* POST、GET、HEAD、OPTIONS、DELETE、TRACE、PUT、CONNECT
* 常用post和get
* 区别:
* get把参数显示在地址栏上,安全级别低,不支持大数据。
* post把参数封装请求体中,安全级别高,支持大数据。
* 请求地址
* 请求资源
* 协议版本
* HTTP/1.1
* 请求头
Accept: text/html,image/*
Accept-Charset: ISO-8859-1
Accept-Encoding: gzip
Accept-Language:zh-cn
Host: www.itcast.com:80
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT
Referer: http://www.itcast.com/index.jsp
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)
Connection: close/Keep-Alive
Date: Tue, 11 Jul 2000 18:23:51 GMT
* 重要的头
* If-Modified-Since 必须和响应头信息一起来完成控制本地的缓存。
* Referer 当前的网页的来源。(防止盗链)
* User-Agent 判断浏览器的版本(文件下载的时候)
* 空行
* 请求体
* 封装post参数列表。
* 响应
* 响应行
* 协议版本
* HTTP/1.1
* 状态码
200 :请求成功处理,一切OK
302 :请求重定向
304 :服务器端资源没有改动,通知客户端查找本地缓存
404 :客户端访问资源不存在
500 :服务器内部出错
* 状态码描述
* 响应头
Location: http://www.it315.org/index.jsp
Server:apache tomcat
Content-Encoding: gzip
Content-Length: 80
Content-Language: zh-cn
Content-Type: text/html; charset=GB2312
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT
Refresh: 1;url=http://www.it315.org
Content-Disposition: attachment; filename=aaa.zip
Expires: -1
Cache-Control: no-cache
Pragma: no-cache
Connection: close/Keep-Alive
Date: Tue, 11 Jul 2000 18:23:51 GMT
* 重要的头
* Location 和302一起完成重定向。
* Last-Modified 和请求头If-Modified-Since一起控制缓存。和状态码304
* Refresh 完成页面的定时跳转
* Content-Disposition 设置文件是以附件打开
Expires: -1
Cache-Control: no-cache
Pragma: no-cache
* 禁用缓存(网银系统)
* 空行
* 响应体
* 存放真正的数据。
Servlet(好好学)
* 动态WEB的资源。
* 什么是Servlet
* 实现Servlet接口,重写5个方法。
* Servlet是一个小的java程序,运行在服务器中,接收和响应从客户端(浏览器)发送过来的请求。
* 快速入门
* 编写一个类,实现Servlet接口,重写5个方法。
* 编写一个类,继承GenericServlet类,重写一个方法。
* 配置文件,配置Servlet信息。(必须会)
<!-- 先配置Servlet信息 -->
<servlet>
<!-- 配置Servlet名称,名称必须唯一 -->
<servlet-name>ServletDemo1</servlet-name>
<!-- 配置Servlet的完全路径(包名+类名) -->
<servlet-class>cn.itcast.servlet.ServletDemo1</servlet-class>
</servlet>
<!-- 配置Servlet映射(访问路径) -->
<servlet-mapping>
<!-- 配置Servlet名称,和上面的名称必须相同 -->
<servlet-name>ServletDemo1</servlet-name>
<!-- 配置虚拟路径(访问路径) -->
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
* 编译
* javac -d . HelloServlet.java
* HelloServlet.java:4: 软件包 javax.servlet 不存在
* set classpath=%classpath%;servlet-api.jar 设置临时的环境变量,只对当前的窗口有效。
* Servlet的生命周期(面试题)
* 生命周期:实例被创建,对外提供服务,销毁。
* Servlet被创建后,然后调用init方法进行初始化
void init(ServletConfig config)
* 从客户端发送所有的请求是service方法进行处理的。
void service(ServletRequest req, ServletResponse res)
* 从服务器中移除服务,调用destroy方法。
void destroy()
* Servlet的生命周期:第一次请求的时候,Servlet实例被创建,立即调用init方法进行初始化。
实例通过service方法提供服务。服务器关闭或者移除服务时,调用destroy方法进行销毁。
* Servlet的关系
Servlet接口
|
GenericServlet(重写5个方法)
|
HttpServlet(继承GenericServlet实现了Servlet接口)
|
MyServlet
* 为什么会有GenericServlet?为什么有HttpServlet?
* 注意事项:
* 如果想重写init方法,重写无参数的init方法。
* 开发步骤(最终)
* 编写一个类,继承HttpServlet
* 重写doGet和doPost方法
* 在doPost方法中调用doGet方法。
* 表单是get方式,调用doGet
* 表单是post方法,调用doPost方法
* doGet和doPost互相调用。
<form action="http://localhost:80/day09/demo5" method="post">
姓名:<input type="text" name="username" />
<input type="submit" value="查询" />
</form>
* 在web.xml配置
* 修改Servlet模板
* 先找到MyEclipse的安装路径。
* \myeclipse10.7\Common\plugins\com.genuitec.eclipse.wizards.xxxx.jar
* 我自己的:com.genuitec.eclipse.wizards_9.0.0.me201211011550.jar
* 千万别解压,右键--选择压缩工具打开--templates--Servlet.java
* 拖回去之前,先MyEclipse关闭。
* Servlet自动加载
* Servlet默认是第一次访问时候创建实例。通过配置,服务器启动,创建实例。
* init做初始化的操作,非常消耗时间的。
* 在<servlet>标签下
<load-on-startup>3</load-on-startup>
* 值是正整数
* 如果值越小,优先级越高。
* 配置虚拟路径(访问路径)
<servlet-mapping>
<url-pattern>/demo5</url-pattern>
</servlet-mapping>
* 完全路径匹配
* 以/开头的 /demo5 /servlet/demo5
* 访问:http://localhost/day09/demo5
* 目录匹配
* 以/开头的 /*
* 访问:http://localhost/day09/demo5可以访问
* 扩展名匹配
* 不能以/开头的 *.do *.action
* 访问:http://localhost/day09/demo5.do
* 优先级:完全路径匹配 > 目录匹配 > 扩展名匹配(*****)
* WEB开发中路径的问题
* 相对路径
* 一个文件相对于另一个文件的位置的关系。
* 不能以/开头 写法: ./demo demo ../demo
* 访问1.html: http://localhost/day09/1.html
* 访问demo5: http://localhost/day09/demo5
* 从1.html中去访问demo5:./demo5 demo5
* 访问2.html: http://localhost/day09/html/2.html
* 访问demo5: http://localhost/day09/demo5
* 从2.html访问demo5:../demo5
* 绝对路径(推荐使用)
* 以/开头的
* 访问demo5: http://localhost/day09/demo5
* 从1.html使用绝对路径访问demo5:http://localhost/day09/demo5
* 简写方式:/day09/demo5
* 客户端绝对路径
* /day09/demo5 需要写项目名
* 服务器绝对路径
* /demo5 不能写项目名
* ServletConfig对象和配置文件相关
* 配置初始化参数
* 需要在<servlet></servlet>标签下配置。
* 如果要是配置在某个servlet的标签下,那么只能在该servlet中获取初始化参数。
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
* String getServletName() 获取配置文件中servlet的名称
* String getInitParameter(String name) 获取初始化参数
* Enumeration getInitParameterNames() 获取初始化参数的名称们
* ServletContext对象(域对象) (*****)
* 定义:WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
一个WEB应用对应一个ServletContext对象
一个WEB应用下有多个Servlet程序
所有的servlet程序都共享同一个ServletContext对象
demo1存入内容 ServletContext demo2中取出来
* 作用:
* 获取WEB应用全局初始化参数
* 在web.xml中配置
<context-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</context-param>
String getInitParameter(String name)
getInitParameterNames()
* 实现数据的共享(*****)
void setAttribute(String name, Object object) 存入数据
void removeAttribute(String name) 删除数据
Object getAttribute(String name) 获取数据
* 读取资源文件(*****)
InputStream getResourceAsStream(String path) 通过文件的地址获取输入流
String getRealPath(String path) 通过文件的地址获取文件的绝对磁盘路径
* 缺省的servlet(了解)
* 自己编写的servlet,不要配置/。
* 在tomcat/conf/web.xml中的配置。
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
response对象(响应对象)
* 想要获取客户端的内容,使用request对象。对客户端做出响应使用response对象。
* 响应:
* 响应行
* 状态码:
void setStatus(int sc) 设置状态码
* 响应头(key:value的形式,一个key对应一个value,一个key对应多个value)
* 设置头信息
void setHeader(String name, String value) (一个key对应一个value)经常使用的
setHeader("aa","bb");
setHeader("aa","cc");
结果:aa:cc
void setIntHeader(String name, int value)
void setDateHeader(String name, long date) 值是毫秒值(int 秒 long 毫秒)
void addHeader(String name, String value) (一个key对应多个value)
addHeader("aa","bb");
addHeader("aa","cc");
结果:aa:bb,cc
void addIntHeader(String name, int value)
void addDateHeader(String name, long date)
* 响应体
ServletOutputStream getOutputStream() 字节输出流
PrintWriter getWriter() 字符输出流
* 案例
重定向(登陆页面)
* 登陆页面重定向
* 状态码302 应头location
* 需求:登陆页面,用户名和密码的登陆页面,用户名和密码都是admin,
如果有一个不是,重定向到登陆页面,重新登陆。
// 设置302的状态码
response.setStatus(302);
// 设置地址
response.setHeader("location", "/day10/response/login.html");
// response对象提供了一个方法,可以完成重定向。
response.sendRedirect("/day10/response/login.html");
页面定时刷新(页面读秒操作)
* 响应的头 refresh
<meta http-equiv="refresh" content="5;url=/day10/response/login.html">
禁用浏览器缓存(三个头信息)
* 应用:网银。页面的数据是发送变化的。
Cache-Control : no-cache
Expires: -1 值是日期类型(setDateHeader())
Pragma : no-cache
向页面输出中文(乱码问题)
字节:ServletOutputStream getOutputStream() 字节输出流
* 字节的输出中文的乱码
* * 输出哈罗我的是否乱码呢?
* * 不一定乱码。
* * 解决办法
* * 设置浏览器打开文件时所采用的编码
* response.setHeader("Content-Type", "text/html;charset=UTF-8");
* * 获取字符串byte数组时编码和打开文件时编码一致。
* "哈罗我的".getBytes("UTF-8")
PrintWriter getWriter() 字符输出流
* 字符输出中文是否乱码呢?
* * 肯定乱码
* response缓冲区的编码,默认值ISO-8859-1
* * 设置response缓冲编码
* response.setCharacterEncoding("UTF-8");
* * 设置浏览器打开文件所采用的编码
* response.setHeader("Content-Type", "text/html;charset=UTF-8");
* * 简写方式
* response.setContentType("text/html;charset=UTF-8");
* 总结:response对象输出中文,产生乱码。
* 字节
* 解决方案
* 设置浏览器打开文件时采用的编码
response.setHeader("Content-Type", "text/html;charset=UTF-8");
* 获取字符串的byte数组采用的编码
"哈罗我的".getBytes("UTF-8")
* 字符
* 解决方法
* 设置浏览器打开文件时采用的编码
response.setHeader("Content-Type", "text/html;charset=UTF-8");
* 设置response缓冲区的编码
response.setCharacterEncoding("UTF-8");
* 简写的方式(等于上面的两句)
* response.setContentType("text/html;charset=UTF-8");
实现文件下载(初级的下载)
* 超链接,浏览器可以解析直接打开。
* 弹出下载窗口。
* 前提条件:先准备一个文件。
* 读取文件,读入到输入流中。
* 通过response读出到浏览器端。
* 设置头信息
* Content-Disposition attachment;filename=文件名称
* 以附件的形式打开。
实现验证码(*****)
request对象(请求对象)
ServletRequest
|
HttpServletRequest
获取客户机信息
getRemoteAddr(*****) 获取IP地址
getMethod() (*****) 获取请求方式
getContextPath()(*****) 获取虚拟路径
获取请求头信息
String getHeader(String name)
long getDateHeader(String name)
int getIntHeader(String name)
* 请求头
referer 记住当前网页的来源
User-Agent 判断浏览器
if-modified-since 控制缓存
获取请求参数(*****)
String getParameter(String name) (*****)
String[] getParameterValues(String name)(*****)
Map getParameterMap()(*****)
Enumeration getParameterNames()(用的比较少)
乱码问题解决:
* POST请求乱码 :request.setCharacterEncoding("utf-8");
* GET请求乱码
解决方案一:修改tomcat/conf/server.xml
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="utf-8"/>
* 必须有修改tomcat服务器配置文件权限
解决方案二:逆向编解码
username = URLEncoder.encode(username, "ISO8859-1");
username = URLDecoder.decode(username, "utf-8");
解决方案三:简写的方式(推荐使用)
username = new String(username.getBytes("ISO-8859-1"),"utf-8");
* request获取中文数据乱码(总结:)
* post提交
* 设置request缓冲区的编码
request.setCharacterEncoding("utf-8");
* get提交
* String构造方法
username = new String(username.getBytes("ISO-8859-1"),"utf-8");
利用请求域传递对象(request域对象)
重定向和转发的区别(转发)(*****)
* 域对象
ServletContext:服务器一启动,为每个web应用创建一个ServletContext对象,所有servlet实例共享对象。
request:一次请求的范围。
* setAttribute("","");
* getAttribute("");
* removeAttribute("");
* getRequestDispatcher(String path) ,返回是RequestDispatcher:对象
* RequestDispatcher:
* forward(ServletRequest request, ServletResponse response)(经常使用) 转发的方法
* include(ServletRequest request, ServletResponse response)(了解) 包含
JSP、EL表达式的入门(要用)
JSP简介
JSP的脚本元素(JSP的页面可以编写java代码)
<%! %> :定义类、定义变量、定义方法(不常用) 成员变量。
<%= %> :输出语句(输出到页面,不能有分号)
<% %> :定义变量、语句
EL快速入门
两个会话的技术
会话概念:打开浏览器,可以访问WEB资源,多次访问WEB资源,关闭浏览器,整个过程一次会话。
购买商品
用户点击超链接通过一个servlet购买了一个商品,程序应该保存用户购买的商品,
以便于用户点结帐servlet时,结帐servlet可以得到用户商品为用户结帐。
把商品存入到ServletContext获取request域中呢?
cookie
显示上次的访问时间(案例)
Cookie的API
String getValue() 获取cookie的值
void setMaxAge(int expiry) :设置有效时间
void setPath(String uri) :设置有效路径
void setDomain(String pattern) :设置有效域名
会话级别的cookie:默认保存到浏览器的内存中。
显示用户上次访问过的商品信息(需求)
session(服务器)
session域对象,范围一次会话范围,存个人相关的数据。
Object getAttribute(String name)
String getId() 获取seesion的唯一的ID
完成简单的购物车
购物车 Map
2.jdbc入门
编写一个jdbc入门代码,完成对数据库操作.
create table user(
id int primary key auto_increment,
username varchar(20) unique not null,
password varchar(20) not null,
email varchar(40) not null
);
INSERT INTO USER VALUES(NULL,'tom','123','[email protected]');
INSERT INTO USER VALUES(NULL,'fox','123','[email protected]');
1.下载驱动
将驱动jar包复制到lib下.
2.创建一个JdbcDemo1类
// 1.注册驱动
DriverManager.registerDriver(new Driver());
// 2.获取连接对象
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/day17", "root", "abc");
// 3.通过连接对象获取操作sql语句Statement
Statement st = con.createStatement();
// 4.操作sql语句
String sql = "select * from user";
// 操作sql语句(select语句),会得到一个ResultSet结果集
ResultSet rs = st.executeQuery(sql);
// 5.遍历结果集
// boolean flag = rs.next(); // 向下移动,返回值为true,代表有下一条记录.
// int id = rs.getInt("id");
// String username=rs.getString("username");
// System.out.println(id);
// System.out.println(username);
while(rs.next()){
int id=rs.getInt("id");
String username=rs.getString("username");
String password=rs.getString("password");
String email=rs.getString("email");
System.out.println(id+" "+username+" "+password+" "+email);
}
//6.释放资源
rs.close();
st.close();
jdbc操作详解
1.注册驱动
DriverManager.registDriver(new Driver());
1.DriverManager类
它是java.sql包下的一个驱动管理的工具类,可以理解成是一个容器(Vector),可以装入很多数据库驱动
它的registDriver方法分析
public static synchronized void registerDriver(java.sql.Driver driver)
参数:java.sql.Driver
我们传递的是 com.mysql.jdbc.Driver;
在com.mysql.jdbc.Driver类中有一段静态代码块:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
上述代码的问题:
1.在驱动管理器中会装入两个mysql驱动.
解决方案:使用反射
Class.forName("com.mysql.jdbc.Driver");
分析:使用反射的方式来加载驱动有什么好处?
1.只加载一次,装入一个驱动对象.
2.降低耦合,不依赖于驱动.
2.可以通过DriverManager来获取连接对象
Connection con=DriverManager.getConection(String url,String user,String password);
url作用:就是用于确定使用哪一个驱动.
mysql url: jdbc:mysql://localhsot:3306/数据库名.
oralce url: jdbc:oracle:thin:@localhost:1521:sid
总结:DriverManager作用:
1.注册驱动
2.获取连接Connection
-----------------------------------------------------------
关于url
url格式
主协议 子协议 主机 端口 数据库
jdbc : mysql ://localhost:3306/day17
mysql的url可以简写:
前提:主机是localhost 端口是3306
jdbc:mysql:///day17
了解:在url后面可以带参数
useUnicode=true&characterEncoding=UTF-8
------------------------------------------------------------------
2.Connection详解
java.sql.Connection,它代表的是一个连接对象。简单说,就是我们程序与数据库连接。
Connection作用:
1.可以通过Connection获取操作sql的Statement对象。
Statement createStatement() throws SQLException
示例:
Statement st=con.createStatement();
了解:
1.可以获取执行预处理的PreparedStatement对象.
PreparedStatement prepareStatement(String sql) throws SQLException
2.可以获取执行存储过程的 CallableStatement
CallableStatement prepareCall(String sql) throws SQLException
2.操作事务
setAutoCommit(boolean flag);开启事务
rollback();事务回滚
commit();事务提交
------------------------------------------------------------------
3.Statement详解
java.sql.Statement用于执行sql语句.
Statement作用:
1.执行sql
DML:insert update delete
int executeUpdate(String sql)
利用返回值判断非0来确定sql语句是否执行成功。
DQL:select
ResultSet executeQuery(String sql)
可以通过execute方法来执行任何sql语句.
execute(String sql):用于向数据库发送任意sql语句
2.批处理操作
addBatch(String sql); 将sql语句添加到批处理
executeBatch();批量执行
clearBatch();清空批处理.
---------------------------------------------------------------------
4.ResultSet详解
java.sql.ResultSet它是用于封装select语句执行后查询的结果。
常用API
1.next()方法
public boolean next();
用于判断是否有下一条记录。如果有返回true,并且让游标向下移动一行。
如果没有返回false.
2.可以通过ResultSet提供的getXxx()方法来获取当前游标指向的这条记录中的列数据。
常用:
getInt()
getString()
getDate()
getDouble()
参数有两种
1.getInt(int columnIndex);
2.getInt(String columnName);
如果列的类型不知道,可以通过下面的方法来操作
getObject(int columnIndex);
getObject(String columnName);
----------------------------------------------------------------
5.关闭资源
Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。
特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
jdbc示例—-CURD
1.查询
1.查询全部
2.条件查询—根据id
2.修改
3.删除
4.添加
关于JdbcUtils抽取:
只抽取到Connection。
public static Connection getConnection() throws ClassNotFoundException,
SQLException {
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
Connection con = DriverManager.getConnection("jdbc:mysql:///day17",
"root", "abc");
return con;
}
关于上述的抽取JdbcUtils的缺点:
1.它只能针对于mysql数据库。
2.每一次调用,都会注册一次驱动.
对于上述问题,对JdbcUtils进行修改:
1.将关于连接数据库的信息定义到配置文件中。
读取配置文件进行加载.
public class JdbcUtils {
private static final String DRIVERCLASS;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static {
DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass");
URL = ResourceBundle.getBundle("jdbc").getString("url");
USERNAME = ResourceBundle.getBundle("jdbc").getString("username");
PASSWORD = ResourceBundle.getBundle("jdbc").getString("password");
}
static {
try {
// 将加载驱动操作,放置在静态代码块中.这样就保证了只加载一次.
Class.forName(DRIVERCLASS);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
// 2.获取连接
Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
return con;
}
滚动结果集:(了解)
默认得到的ResultSet它只能向下遍历(next()),对于ResultSet它可以设置成是滚动的,可以向上遍历,
或者直接定位到一个指定的物理行号.
问题:怎样得到一个滚动结果集?
Statement st=con.createStatement();
ResultSet rs=st.executeQuery(sql);
这是一个默认结果集:只能向下执行,并且只能迭代一次。
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery(sql);
这个就可以创建滚动结果集.
简单说,就是在创建Statement对象时,不使用createStatement();
而使用带参数的createStatement(int,int)
Statement createStatement(int resultSetType,
int resultSetConcurrency)
throws SQLException
resultSetType - 结果集类型,它是 ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE 之一
resultSetConcurrency - 并发类型;它是 ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE 之一
第一个参数值
ResultSet.TYPE_FORWARD_ONLY 该常量指示光标只能向前移动的 ResultSet 对象的类型。
ResultSet.TYPE_SCROLL_INSENSITIVE 该常量指示可滚动但通常不受 ResultSet 底层数据更改影响的 ResultSet 对象的类型。
ResultSet.TYPE_SCROLL_SENSITIVE 该常量指示可滚动并且通常受 ResultSet 底层数据更改影响的 ResultSet 对象的类型。
第二个参数值
ResultSet.CONCUR_READ_ONLY 该常量指示不可以更新的 ResultSet 对象的并发模式。
ResultSet.CONCUR_UPDATABLE 该常量指示可以更新的 ResultSet 对象的并发模式。
以上五个值,可以有三种搭配方式
ResultSet.TYPE_FORWARD_ONLY ResultSet.CONCUR_READ_ONLY 默认
ResultSet.TYPE_SCROLL_INSENSITIVE ResultSet.CONCUR_READ_ONLY
ResultSet.TYPE_SCROLL_SENSITIVE ResultSet.CONCUR_UPDATABLE
常用API
next():移动到下一行
previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面
afterLast() :移动到resultSet的最后面
DAO模式
DAO模式(Data Access Object 数据访问对象):在持久层通过DAO将数据源操作完全封装起来,业务层通过操作Java对象,完成对数据源操作
* 业务层无需知道数据源底层实现 ,通过java对象操作数据源
DAO模式结构 :
1、数据源(MySQL数据库)
2、Business Object 业务层代码,调用DAO完成 对数据源操作
3、DataAccessObject 数据访问对象,持久层DAO程序,封装对数据源增删改查,提供方法参数都是Java对象
4、TransferObject 传输对象(值对象) 业务层通过向数据层传递 TO对象,完成对数据源的增删改查
使用dao模式完成登录操作
1.web层
login.jsp LoginServlet User
2.service层
UserService
3.dao层
UserDao
-------------------------
sql注入
由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL 关键字,
达到改变SQL运行结果的目的,也可以完成恶意攻击。
示例:
在输入用户名时 tom' or '1'='1
这时就不会验证密码了。
解决方案:
PreparedStatement(重点)
它是一个预处理的Statement,它是java.sql.Statement接口的一个子接口。
总结PreparedStatement使用:
1.在sql语句中,使用"?"占位
String sql="select * from user where username=? and password=?";
2.得到PreparedStatement对象
PreparedStatement pst=con.prepareStatement(String sql);
3.对占位符赋值
pst.setXxx(int index,Xxx obj);
例如:
setInt()
setString();
参数index,代表的是"?"的序号.注意:从1开始。
4.执行sql
DML: pst.executeUpdate();
DQL: pst.executeQuery();
注意:这两方法无参数
关于PreparedStatement优点:
1.解决sql注入(具有预处理功能)
jdbc处理大数据
mysql中有大数据
blob 大二进制
TINYBLOB(255)、BLOB(64kb)、MEDIUMBLOB(16m)和LONGBLOB(4g)
text(clob) 大文本
TINYTEXT(255)、TEXT(64kb)、MEDIUMTEXT(16m)和LONGTEXT(4g)
对于大数据操作,我们一般只有两种 insert select
演示1:
大二进制操作
create table myblob(
id int primary key auto_increment,
content longblob
)
向表中插入数据
问题1:java.lang.AbstractMethodError: com.mysql.jdbc.PreparedStatement.setBinaryStream(ILjava/io/InputStream;)V
原因:mysql驱动不支持setBinaryStream(int,InputStream);
修改成
pst.setBinaryStream(1, fis,file.length());
原因:因为mysql驱动不支持setBinaryStream(int,InputStream,long);
解决:
mysql驱动支持setBinaryStream(int,InputStream,int);
注意:如果文件比较大,那么需要在my.ini文件中配置
max_allowed_packet=64M
总结:
存
pst.setBinaryStream(1, fis, (int) (file.length()));
取
InputStream is = rs.getBinaryStream("content");
--------------------------------------------------------------
演示:存储大文本
create table mytext(
id int primary key auto_increment,
content longtext
)
存储
File file = new File("D:\\java1110\\workspace\\day17_3\\a.txt");
FileReader fr = new FileReader(file);
pst.setCharacterStream(1, fr, (int) (file.length()));
获取:
jdbc批处理
一次可以执行多条sql语句.
在jdbc中可以执行sql语句的对象有Statement,PreparedStatement,它们都提供批处理.
1.Statement执行批处理
addBatch(String sql); 将sql语句添加到批处理
executeBatch(); 执行批处理
clearBatch();
2.PreparedStatement执行批处理
addBatch();
executeBatch();
clearBatch();
以上两个对象执行批处理区别?
1.Statement它更适合执行不同sql的批处理。它没有提供预处理功能,性能比较低。
2.PreparedStatement它适合执行相同sql的批处理,它提供了预处理功能,性能比较高。
注意;mysql默认情况下,批处理中的预处理功能没有开启,需要开启
1.在 url下添加参数
url=jdbc:mysql:///day17?useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true
2.注意驱动版本
Mysql驱动要使用mysql-connector-java-5.1.13以上
事务:
问题:事务是什么,有什么用?
事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不成功。
在开发中,有事务的存在,可以保证数据完整性。
问题:事务怎样操作
创建表:
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
insert into account values(null,'ccc',1000);
1.mysql下怎样操作
方式1:
start transaction 开启事务
rollback 事务回滚
commit 事务提交
方式2:
show variables like '%commit%'; 可以查看当前autocommit值
在mysql数据库中它的默认值是"on"代表自动事务.
自动事务的意义就是:执行任意一条sql语句都会自动提交事务.
测试:将autocommit的值设置为off
1.set autocommit=off 关闭自动事务。
2.必须手动commit才可以将事务提交。
注意:mysql默认autocommit=on oracle默认的autocommit=off;
2.jdbc下怎样操作
java.sql.Connection接口中有几个方法是用于可以操作事务
1.setAutocommit(boolean flag);
如果flag=false;它就相当于start transaction;
2.rollBack()
事务回滚。
3.commit()
事务特性(重点) ACID
? 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
? 一致性(Consistency)
事务前后数据的完整性必须保持一致。
? 隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
? 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
-------------------------------------
如果不考虑事务的隔离性,会出现什么问题?
1.脏读 一个事务读取到另一个事务的未提交数据。
2.不可重复读
两次读取的数据不一致(update)
3.虚读(幻读)
两次读取的数据一一致(insert)
4.丢失更新
两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了。
-----------------------------------------
演示以上问题,以及问题的解决方案
对于以上的问题,我们可以通过设置事务的隔离级别来解决。
1.事务的隔离级别有哪些?
1 Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
2 Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
3 Read committed:可避免脏读情况发生(读已提交)
4 Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
2.怎样设置事务的隔离级别?
1.mysql中设置
1.查看事务隔离级别
select @@tx_isolation 查询当前事务隔离级别
mysql中默认的事务隔离级别是 Repeatable read.
扩展:oracle 中默认的事务隔离级别是 Read committed
2.mysql中怎样设置事务隔离级别
set session transaction isolation level 设置事务隔离级别
2.jdbc中设置
在jdbc中设置事务隔离级别
使用java.sql.Connection接口中提供的方法
void setTransactionIsolation(int level) throws SQLException
参数level可以取以下值:
level - 以下 Connection 常量之一:
Connection.TRANSACTION_READ_UNCOMMITTED、
Connection.TRANSACTION_READ_COMMITTED、
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE。
(注意,不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。)
--------------------------------------------------
3.演示
1.脏读
一个事务读取到另一个事务的为提交数据
设置A,B事务隔离级别为 Read uncommitted
set session transaction isolation level read uncommitted;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
这时,B事务读取时,会发现,钱已经汇完。那么就出现了脏读。
当A事务提交前,执行rollback,在commit, B事务在查询,就会发现,钱恢复成原样
也出现了两次查询结果不一致问题,出现了不可重复读.
2.解决脏读问题
将事务的隔离级别设置为 read committed来解决脏读
设置A,B事务隔离级别为 Read committed
set session transaction isolation level read committed;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
这时B事务中,读取信息时,是不能读到A事务未提交的数据的,也就解决了脏读。
让A事务,提交数据 commit;
这时,在查询,这次结果与上一次查询结果又不一样了,还存在不可重复读。
3.解决不可重复读
将事务的隔离级别设置为Repeatable read来解决不可重复读。
设置A,B事务隔离级别为 Repeatable read;
set session transaction isolation level Repeatable read;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
当A事务提交后commit;B事务在查询,与上次查询结果一致,解决了不可重复读。
4.设置事务隔离级别 Serializable ,它可以解决所有问题
set session transaction isolation level Serializable;
如果设置成这种隔离级别,那么会出现锁表。也就是说,一个事务在对表进行操作时,
其它事务操作不了。
--------------------------------------------------
总结:
脏读:一个事务读取到另一个事务为提交数据
不可重复读:两次读取数据不一致(读提交数据)---update
虚读:两次读取数据不一致(读提交数据)----insert
事务隔离级别:
read uncommitted 什么问题也解决不了.
read committed 可以解决脏读,其它解决不了.
Repeatable read 可以解决脏读,可以解决不可重复读,不能解决虚读.
Serializable 它会锁表,可以解决所有问题.
安全性:serializable > repeatable read > read committed > read uncommitted
性能 :serializable < repeatable read < read committed < read uncommitted
结论: 实际开发中,通常不会选择 serializable 和 read uncommitted ,
mysql默认隔离级别 repeatable read ,oracle默认隔离级别 read committed
丢失更新
多个事务对同一条记录进行了操作,后提交的事务将先提交的事务操作覆盖了。
查看图.
问题:怎样解决丢失更新问题?
解决丢失更新可以采用两种方式:
1.悲观锁
悲观锁 (假设丢失更新一定会发生 ) ----- 利用数据库内部锁机制,管理事务
提供的锁机制
1.共享锁
select * from table lock in share mode(读锁、共享锁)
2.排它锁
select * from table for update (写锁、排它锁)
update语句默认添加排它锁
2.乐观锁
乐观锁 (假设丢失更新不会发生)------- 采用程序中添加版本字段解决丢失更新问题
create table product (
id int,
name varchar(20),
updatetime timestamp
);
insert into product values(1,'冰箱',null);
update product set name='洗衣机' where id = 1;
解决丢失更新:在数据表添加版本字段,每次修改过记录后,版本字段都会更新,如果读取是版本字段,
连接池
问题:连接池是什么,有什么用?
连接池:就是创建一个容器,用于装入多个Connection对象,在使用连接对象时,从容器中获取一个Connection,
使用完成后,在将这个Connection重新装入到容器中。这个容器就是连接池。(DataSource)
也叫做数据源.
我们可以通过连接池获取连接对象.
优点:
节省创建连接与释放连接 性能消耗 ---- 连接池中连接起到复用的作用 ,提高程序性能
-----------------------------------------------------------------------------------
自定义连接池
1.创建一个MyDataSource类,在这个类中创建一个LinkedList<Connection>
2.在其构造方法中初始化List集合,并向其中装入5个Connection对象。
3.创建一个public Connection getConnection();从List集合中获取一个连接对象返回.
4.创建一个 public void readd(Connection) 这个方法是将使用完成后的Connection对象重新装入到List集合中.
代码问题:
1.连接池的创建是有标准的.
在javax.sql包下定义了一个接口 DataSource
简单说,所有的连接池必须实现javax.sql.DataSource接口,
我们的自定义连接池必须实现DataSource接口。
2.我们操作时,要使用标准,怎样可以让 con.close()它不是销毁,而是将其重新装入到连接池.
要解决这个问题,其本质就是将Connection中的close()方法的行为改变。
怎样可以改变一个方法的行为(对方法功能进行增强)
1.继承
2.装饰模式
1.装饰类与被装饰类要实现同一个接口或继承同一个父类
2.在装饰类中持有一个被装饰类引用
3.对方法进行功能增强。
3.动态代理
可以对行为增强
Proxy.newProxyInstance(ClassLoacer ,Class[],InvocationHandler);
结论:Connection对象如果是从连接池中获取到的,那么它的close方法的行为已经改变了,不在是销毁,而是重新装入到连接池。
--------------------------------------------------------------------
1.连接池必须实现javax.sql.DataSource接口。
2.要通过连接池获取连接对象 DataSource接口中有一个 getConnection方法.
3.将Connection重新装入到连接池 使用Connection的close()方法。
==================================================================================================
开源连接池
1.dbcp(了解)
dbcp是apache的一个开源连接池。
要想使用DBCP连接池,要下载jar包
导入时要导入两个
commons-dbcp-1.4.jar
commons-pool-1.5.6.jar
关于dbcp连接池使用
1.手动配置(手动编码)
BasicDataSource bds = new BasicDataSource();
// 需要设置连接数据库最基本四个条件
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///day18");
bds.setUsername("root");
bds.setPassword("abc");
// 得到一个Connection
Connection con = bds.getConnection();
2.自动配置(使用配置文件)
Properties props = new Properties();
FileInputStream fis = new FileInputStream("D:\\java1110\\workspace\\day18_2\\src\\dbcp.properties");
props.load(fis);
DataSource ds = BasicDataSourceFactory.createDataSource(props);
2.c3p0(必会)
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。
目前使用它的开源项目有Hibernate,Spring等。
c3p0与dbcp区别
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接功能
c3p0连接池使用
1.导包
c3p0-0.9.1.2.jar
使用
1.手动
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("abc");
2.自动(使用配置文件)
c3p0的配置文件可以是properties也可以是xml.
c3p0的配置文件如果名称叫做 c3p0.properties or c3p0-config.xml 并且放置在classpath路径下(对于web应用就是classes目录)
那么c3p0会自动查找。
注意:我们其时只需要将配置文件放置在src下就可以。
使用:
ComboPooledDataSource cpds = new ComboPooledDataSource();
它会在指定的目录下查找指定名称的配置文件,并将其中内容加载。
*tomcat内置连接池管理
tomcat内置连接池使用的是dbcp。
问题1:tomcat怎样管理连接池?(配置)
要想将一个dbcp连接池让 tomcat管理,只需要创建一个context.xml配置文件,在配置文件中
配置相关信息,
<Context>
<Resource name="jdbc/EmployeeDB" auth="Container"
type="javax.sql.DataSource" username="root" password="abc"
driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day18"
maxActive="8" maxIdle="4"/>
</Context>
问题:context.xml文件位置:
1.在tomcat/conf/context.xml 这时这个连接池是给整个服务器使用的。
2.在tomcat/conf/Catalina/localhost 这时这个连接池只给localhost虚拟主机使用。
3.将context.xml文件放置在web应用的META-INF下
注意:如果是全局设置,那么我们需要将数据库驱动放置在tomcat/lib目录下
问题2:怎样从tomcat中获取连接池?
我们在servlet中获取连接池对象。
Context context = new InitialContext();
Context envCtx = (Context)context.lookup("java:comp/env"); 固定路径
DataSource datasource = (DataSource) envCtx.lookup("jdbc/EmployeeDB");
JNDI-----> JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,
JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI SPI的实现,由管理者将JNDI API映射为特定的命名服务
和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是一种命名服务,
2.元数据
问题:元数据是什么,有什么作用?
元数据(metaData) 指数据库中 库、表、列的定义信息
1.DataBaseMetaData 数据库元数据(了解)
问题:怎样获取一个DataBaseMetaData?
Connection接口中定义了一个方法 getMetaData();
问题:常用API
String driverName = dmd.getDriverName(); //获取驱动名称
System.out.println(driverName);
String userName = dmd.getUserName();//获取用户名
System.out.println(userName);
String url = dmd.getURL();//获取url
System.out.println(url);
String databaseProductName = dmd.getDatabaseProductName(); //获取数据库名称
System.out.println(databaseProductName);
String databaseProductVersion = dmd.getDatabaseProductVersion();//获取数据库版本.
System.out.println(databaseProductVersion);
ResultSet getPrimaryKeys(String catalog,
String schema,
String table)
throws SQLException
获取表中主键相关描述
每个主键列描述都有以下列:
TABLE_CAT String => 表类别(可为 null)
TABLE_SCHEM String => 表模式(可为 null)
TABLE_NAME String => 表名称
COLUMN_NAME String => 列名称
KEY_SEQ short => 主键中的序列号(值 1 表示主键中的第一列,值 2 表示主键中的第二列)。
PK_NAME String => 主键的名称(可为 null)
2.ParameterMetaData 参数元数据
参数元数据主要用于获取:sql语句中占位符的相关信息.
问题:怎样获取ParameterMetaData?
在PreparedStatement中有一个方法getParameterMetaData可以获取.
问题:怎样使用?
int count = pmd.getParameterCount(); // 获取参数 个数
System.out.println(count);
String type1 = pmd.getParameterTypeName(1);//获取参数的类型
System.out.println(type1);
注意:在获取参数类型时会产生异常
java.sql.SQLException: Parameter metadata not available for the given statement
解决方案:
在url后添加参数
jdbc:mysql:///day18?generateSimpleParameterMetadata=true
添加这个参数后,我们在获取,它的结果也是varchar,原因:是mysql驱动的支持问题。
3.ResultSetMetaData 结果集元数据
问题:怎样获取结果集元数据?
可以通过ResultSet的getMetaData()方法获取.
问题:怎样使用?
System.out.println(rsmd.getColumnCount());//获取结果集中列数量
System.out.println(rsmd.getColumnName(2));//获取结果集中指定列的名称.
3.dbutils工具
问题:dbutils是什么,有什么作用?
它就是一个简单的jdbc封装工具.
使用dbutils可以简化操作.
要使用dbutils需要导入jar包.
dbutils核心
1.QueryRunner类
它是用于执行sql语句的类。
1.query 用于执行select
2.update 用于执行update delete insert
3.batch 批处理
2.ResultSetHandler接口
用于定义结果集的封装
它提供九个实现类,可以进行不同的封装。
3.DbUtils类
它提供关于关闭资源以及事务rollback,commit操作。
-----------------------------------------------------
Dbutlis详解
1.QueryRunner
1.QueryRunner怎样获取
1.new QueryRunner()
如果是使用这种构造创建的QueryRunner,它的事务是手动控制.
2.new QueryRunner(DataSource ds);
如果是使用这种构造,它的事务是自动事务,简单说,一条sql一个事务。
2.QueryRunner中的三个核心方法
query
update
batch
对于上述三个方法,它们提供很多重载。
如果QueryRunner在创建时,没有传递DataSource参数,那么在使用
query,update,batch方法时,要传递Connection参数
如果QueryRunner在创建时,传递了Dataource参数,好么在使用
query,update,batch方法时,不需要传递Connection参数。
总结:
怎样配套使用:
QueryRunner runner=new QueryRunner();
runner.query(Connection,sql,ResultSetHandler,Object... param);
runner.update(Connection,sql,Object...param);
runner.batch(Connection con,sql,Object[][] objs);
QueryRunner runner=new QueryRunner(DataSource ds);
runner.query(sql,ResultSetHandler,Object... param);
runner.update(sql,Object...param);
runner.batch(sql,Object[][] objs);
-----------------------------------------------------------------
ResultSetHandler接口
用于封装结果集.
============================================================================
模仿QueryRunner
1.query方法模仿
public <T> T query(Connection con, String sql, MyResultSetHandler<T> mrs,Object... params) throws SQLException {
PreparedStatement pst = con.prepareStatement(sql); // 得到一个预处理的Statement.
// 问题:sql语句中可能存在参数,需要对参数赋值。
ParameterMetaData pmd = pst.getParameterMetaData();
// 可以得到有几个参数
int count = pmd.getParameterCount();
for (int i = 1; i <= count; i++) {
pst.setObject(i, params[i - 1]);
}
ResultSet rs = pst.executeQuery(); // 得到了结果集,要将结果集封装成用户想要的对象,但是,工具不可能知道用户需求。
return mrs.handle(rs);
}
2.update方法模仿
public int update(Connection con, String sql, Object... params) throws SQLException {
PreparedStatement pst = con.prepareStatement(sql); // 得到一个预处理的Statement.
// 问题:sql语句中可能存在参数,需要对参数赋值。
ParameterMetaData pmd = pst.getParameterMetaData();
// 可以得到有几个参数
int count = pmd.getParameterCount();
for (int i = 1; i <= count; i++) {
pst.setObject(i, params[i - 1]);
}
int row = pst.executeUpdate();
// 关闭资源
pst.close();
return row;
ResulsetHandler九个实现类
ArrayHandler, 将结果集中第一条记录封装到Object[],数组中的每一个元素就是记录中的字段值。
ArrayListHandler, 将结果集中每一条记录封装到Object[],数组中的每一个元素就是记录中的字段值。在将这些数组装入到List集合。
BeanHandler(重点), 将结果集中第一条记录封装到一个javaBean中。
BeanListHandler(重点), 将结果集中每一条记录封装到javaBean中,在将javaBean封装到List集合.
ColumnListHandler, 将结果集中指定列的值封装到List集合.
MapHandler, 将结果集中第一条记录封装到Map集合中,集合的 key就是字段名称,value就是字段值
MapListHandler, 将结果集中每一条记录封装到Map集合中,集合的 key就是字段名称,value就是字段值,在将这些Map封装到List集合
KeyedHandler,在使用指定的列的值做为一个Map集合的key,值为每一条记录的Map集合封装。
ScalarHandler 进行单值查询 select count(*) from account;
---------------------------------------------------------
扩展:实现BeanHandler
使用BeanUtils实现
Object obj = null;
Map<String, String[]> map = new HashMap<String, String[]>();
ResultSetMetaData md = rs.getMetaData();
int count = md.getColumnCount();
if (rs.next()) {
try {
obj = clazz.newInstance();
for (int i = 1; i <= count; i++) {
map.put(md.getColumnName(i),
new String[] { rs.getString(md.getColumnName(i)) });
}
BeanUtils.populate(obj, map);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return obj;
使用虚拟主机可以将项目部署成顶级域名
1.在service.xml文件
1.端口修改为80
2. 配置主机
<Host name="www.customer.com" appBase="D:\java1110\workspace\day19_2"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<Context path="" docBase="D:\java1110\workspace\day19_2\WebRoot" />
</Host>
3.在hosts文件中配置
127.0.0.1 www.customer.com
分页查询
问题:什么是分页,为什么使用分页?
分页就是将数据以多页去展示,使用分页可以提高客户的感受。
分页分类:
1.物理分页
只从数据库中查询出当前页的数据。
优点:不占用很多内存
缺点:效率比较低
2.逻辑分页
从数据库中将所有记录查询出业,存储到内存中,要想展示当前页
数据,直接从内存中获取。
优点:效率高
缺点:占用内存比较高
在java开发领域,我们使用的比较多的是物理分页。
物理分页的实现:
1.直接使用jdbc完成
使用滚动结果集. 优点:跨数据库。缺点:性能低。
2.使用数据库本身提供的分页操作.
会使用每一个数据库特定的分页函数,优点:性能高 缺点:不能跨数据库。
mysql:limit
sqlservlet:top
oracle:rownum
介绍limit使用.
select * from 表 limit m,n;
m:代表的是从第几条开始 注意:它是从0开始记录.
n:代表查询几条记录.
示例:分页音,每页显示6条,要查询第2页的数据.
select * from 表 limit (页码-1)*每页条数,每页条数;
------------------------------------------------------------------------------
分页分析:
1.页码 默认第一页
2.每页条数 人为定义
3.总条数 select count(*) from 表
4.总页数 总页数=总条数%每页条数==0?总条数/每页条数:总条数/每页条数+1
总页数=Math.ceil(总条数*1.0/每页条数);
5.当前页的数据 List<?>----->select * from 表 limit (页码-1)*每页条数,每页条数;
监听器:
问题:什么是监听器?监听器有什么作用?
概念:
1.事件 ActionEvent
2.事件源 JButton
3.监听器 ActionListener
4.注册监听 addActionListener();
监听器就是可以监听某一个事件在执行一个特定操作时,我们可以让其触发一个操作。
可以在满足特定条件的情况下执行一段操作。
问题:我们现在学习的是javaweb,那么在javaweb中有什么监听器,有什么作用?
javaweb中的监听器,主要用于监听javaweb中常用对象(request(HttpServletRequest),session(HttpSession),application(ServletContext))的三种类型操作
1.对象的创建与销毁
2.对象的属性变化
3.session绑定javaBean.
在javaweb中servlet规范中定义了三种技术 servlet Listener Filter
1.监听创建与销毁
HttpServletRequest
监听器:ServletRequestListener可以监听request对象的创建与销毁.
HttpSession
监听器:HttpSessionListener可以监听session对象的创建与销毁.
ServletContext
监听器:ServletContextListener可以监听application对象的创建与销毁。
2.监听web对象的属性变化
HttpServletRequest属性变化
监听器:ServletRequestAttributeListener监听request对象的属性变化
HttpSession属性变化
监听器:HttpSessionAttributeListener 监听session对象的属性变化
ServletContext属性变化
监听器:ServletContextAttributeListener监听application对象的属性变化。
------------------------------------------------------------------------------------
演示关于web中监听器怎样使用?
创建监听器步骤:
1.创建一个类,去实现指定的监听器接口.
2.重写接口中方法。
3.在web.xml文件中配置注册监听。
演示:
1.监听application对象的创建与销毁.
问题:application对象什么时候创建,什么时候销毁的?
application对象是服务器启动时创建,
服务器关闭时销毁。
2.监听session对象创建与销毁
问题:session对象什么时候创建,什么时候销毁?
session对象创建:
reqeust.getSession();它是用于获取session.
是否创建,分以下几种情况:
1.请求中如果没有jsessionid,那么就是创建session对象。
2.如果请求头中有jsessionid值:
1.如果在服务器端,有一个session的id值与其一样,不创建,直接使用。
2.如果在服务器端,没有这个session的id值,那么会创建。
session销毁:
1.默认超时 30分钟
2.设置session超时时间
setMaxInactiveInterval(int interval)
3.invalidate()手动销毁.
4.关闭服务器
3.监听request对象创建与销毁
问题:request对象什么时候创建,什么时候销毁?
请求发生,request对象创建,响应产生request对象销毁。
----------------------------------------------------------------------
演示监听属性变化
演示监听session的属性变化
问题:在监听器中是否可以得到属性值?
常识:在java的监听机制中,是可以在监听器中获取事件源的。
我们在开发中,如果有到了事件触发机制,那么一般情况下,都可以使用
方法的参数(事件对象)来获取想要的信息.
----------------------------------------------------------
思考一个问题:这些监听器在开发中有什么用?
在主流中应用比较少,但是可以完成一些性能监试操作。
-----------------------------------------------------------------------------
监听器案例:
功能:扫描session对象在指定时间内没有使用,人为销毁。
分析:
1.怎样知识session多长时间没有使用?
当前时间-最后使用时间(public long getLastAccessedTime())
2.什么时候开始扫描,扫描多长时间?
可以使用Timer完成
完成定时扫描session,如果超时没有使用,销毁案例:
1.要将所有的session对象得到,保存到集合中。
1.创建一个监听器 ServletContextListener,它服务器启动时,创建一个集合保存到ServletContext域。
2.创建一个监听器 HttpSessionListener,当创建一个session时,就从ServletContext域中获取集合,将session对象储存到集合中。
2.定时扫描
问题:
1.session超时,不能只销毁session,还要从集合中移除。
2.我们的操作,它是多线程的,要考虑集合的同步问题。
1.集合需要是线程安全的。
2.需要使用迭代器进行遍历。
---------------------------------------------------------------------------------------
session绑定javaBean
1.HttpSessionBindingListener
这个监听器,可以让javaBean对象,感知它被绑定到session中或从session中移除。
2.HttpSessionActivationListener
这个监听器,可以让javaBean感知,被钝化或活化。
钝化--->将session中的javaBean保存到文件中.
活化--->从文件中将javaBean直接获取。
需要创建一个配置文件context.xml
这个文件保存到META-INF目录下.
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="it315"/>
</Manager>
</Context>
这两个监听器特点;
1.它们是由javaBean实现.
2.它们不需要在web.xml文件中配置.
javaWeb之过滤器
Fileter介绍
? Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能
? Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截
? Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源(拦截url)进行拦截后,WEB服务器每次在调用web资源之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
? 调用目标资源之前,让一段代码执行
? 是否调用目标资源(即是否让用户访问web资源)。
? web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
? 调用目标资源之后,让一段代码执行
开发Fileter步骤
? Filter开发分为二个步骤:
? 编写java类实现Filter接口,并实现(三个方法)其doFilter方法。
? 在 web.xml 文件中使用和元素对编写的filter类进行注册,并设置它所能拦截的资源。
? Filter链 — FilterChain
? 在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
? web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
? Filter链实验(查看filterChain API文档)
Filter的生命周期
? init(FilterConfig filterConfig)throws ServletException:
? 和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法进行初始化(注:filter对象只会创建一次,init方法也只会执行一次。示例 )
? 开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。(filterConfig对象见下页PPT)
? doFilter(ServletRequest,ServletResponse,FilterChain)
? 每次filter进行拦截都会执行
? 在实际开发中方法中参数request和response通常转换为HttpServletRequest和HttpServletResponse类型进行操作
? destroy():
? 在Web容器卸载 Filter 对象之前被调用。
FilterConfig接口
? 用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
? String getFilterName():得到filter的名称。
? String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
? Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
? public ServletContext getServletContext():返回Servlet上下文对象的引用。
注册与映射Filter
? 注册
testFitler
org.test.TestFiter
word_file
/WEB-INF/word.txt
? 用于为过滤器指定一个名字,该元素的内容不能为空。
? 元素用于指定过滤器的完整的限定类名。
? 元素用于为过滤器指定初始化参数,它的子元素指定参数的名字,指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。
? 映射Filter
? 元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
? 子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字
? 设置 filter 所拦截的请求路径(过滤器关联的URL样式)
? 指定过滤器所拦截的Servlet名称。
? 指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个 子元素用来指定 Filter 对资源的多种调用方式进行拦截。
? 子元素可以设置的值及其意义:
? REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
? INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
? FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
? ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
testFilter
/index.jsp
REQUEST
FORWARD
Filter示例
示例1全站统一字符编码过滤器
编写jsp 输入用户名,在Servlet中获取用户名,将用户名输出到浏览器上
处理请求post乱码代码
request.setCharacterEncoding(“utf-8”);
设置响应编码集代码
response.setContentType(“text/html;charset=utf-8”);
经常会使用,而过滤器可以在目标资源之前执行,将很多程序中处理乱码公共代码,提取到过滤器中 ,以后程序中不需要处理编码问题了
示例2禁用所有JSP页面缓存
因为动态页面数据,是由程序生成的,所以如果有缓存,就会发生,客户端查看数据不是最新数据情况 ,对于动态程序生成页面,设置浏览器端禁止缓存页面内容
response.setDateHeader(“Expires”,-1);
response.setHeader(“Cache-Control”,”no-cache”);
response.setHeader(“Pragma”,”no-cache”);
将禁用缓存代码,提起到过滤器中,通过url配置,禁用所有JSP页面的缓存
示例3设置图片过期时间
Tomcat缓存策略
对于服务器端经常不变化文件,设置客户端缓存时间,在客户端资源缓存时间到期之前,就不会去访问服务器获取该资源 ——– 比tomcat内置缓存策略更优手段
* 减少服务器请求次数,提升性能
设置静态资源缓存时间,需要设置 Expires 过期时间 ,在客户端资源没有过期之前,不会产生对该资源的请求的
* 设置Expires 通常使用 response.setDateHeader 进行设置 设置毫秒值
示例4自动登录案例(MD5加密)
在访问一个站点,登陆时勾选自动登陆(三个月内不用登陆),操作系统后,关闭浏览器;过几天再次访问该站点时,直接进行登陆后状态
在数据库中创建 user表
create table user (
id int primary key auto_increment,
username varchar(20),
password varchar(40),
role varchar(10)
);
insert into user values(null,’admin’,’123’,’admin’);
insert into user values(null,’aaa’,’123’,’user’);
insert into user values(null,’bbb’,’123’,’user’);
自动登陆 :未登录、存在自动登陆信息、自动登陆信息正确
在用户完成登陆后,勾选自动登陆复选框,服务器端将用户名和密码 以Cookie形式,保存在客户端 。当用户下次访问该站点,AutoLoginFilter 过滤器从Cookie中获取 自动登陆信息
1、判断用户是否已经登陆,如果已经登陆,没有自动登陆的必要
2、判断Cookie中是否含有自动登陆信息 ,如果没有,无法完成自动登陆
3、使用cookie用户名和密码 完成自动登陆
如果将用户密码保存在cookie文件中,非常不安全的 ,通常情况下密码需要加密后才能保存到客户端
* 使用md5算法对密码进行加密
* md5 加密算法是一个单向加密算法 ,支持明文—密文 不支持密文解密
MySQL数据库中提供md5 函数,可以完成md5 加密
mysql> select md5(‘123’);
+———————————-+
| md5(‘123’) |
+———————————-+
| 202cb962ac59075b964b07152d234b70 |
+———————————-+
加密后结果是32位数字 16进制表示
Java中提供类 MessageDigest 完成MD5加密
将数据表中所有密码 变为密文 update user set password = md5(password) ;
在登陆逻辑中,对密码进行md5 加密
在AutoLoginFilter 因为从Cookie中获得就是加密后密码,所以登陆时无需再次加密
示例5 URL级别的权限控制
认证:who are you ? 用户身份的识别 ———— 登陆功能
权限:以认证为基础 what can you do ? 您能做什么? 必须先登陆,才有身份,有了身份,才能确定可以执行哪些操作
示例6通用get和post乱码过滤器
Decorator模式
1、包装类需要和被包装对象 实现相同接口,或者继承相同父类
2、包装类需要持有 被包装对象的引用
在包装类中定义成员变量,通过包装类构造方法,传入被包装对象
3、在包装类中,可以控制原来那些方法需要加强
不需要加强 ,调用被包装对象的方法
需要加强,编写增强代码逻辑
ServletRequestWrapper 和 HttpServletRequestWrapper 提供对request对象进行包装的方法,但是默认情况下每个方法都是调用原来request对象的方法,也就是说包装类并没有对request进行增强
在这两个包装类基础上,继承HttpServletRequestWrapper ,覆盖需要增强的方法即可
系统中存在很多资源,将需要进行权限控制的资源,放入特殊路径中,编写过滤器管理访问特殊路径的请求,如果没有相应身份和权限,控制无法访问
在Filter中,对request对象进行包装,增强获得参数的方法
getParameter
getParameterValues
getParameterMap
ServletResponseWrapper 和 HttpServletResponseWrapper 提供了对response 对象包装,继承 HttpServletResponseWrapper ,覆盖需要增强response的方法
文件上传与下载
上传
文件上传概述
? 实现web开发中的文件上传功能,需完成如下二步操作:
? 在web页面中添加上传输入项
? 在servlet中读取上传文件的数据,并保存到服务器硬盘中。
? 如何在web页面中添加上传输入项?
? 标签用于在web页面中添加文件上传输入项,设置文件上传输入项时须注意:
? 1、必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。
? 2、必须把form的enctype属值设为multipart/form-data.设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。
? 3、表单的提交方式要是post
? 如何在Servlet中读取文件上传数据,并保存到本地硬盘中?
? Request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作,示例。
? 为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
? 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io。commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持。
文件上传步骤
? 实现步骤
1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
? True 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
? False 为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
FileUpload上传操作核心API
1、DiskFileItemFactory 磁盘文件项工厂类
public DiskFileItemFactory(int sizeThreshold, java.io.File repository) 构造工厂时,指定内存缓冲区大小和临时文件存放位置
public void setSizeThreshold(int sizeThreshold) 设置内存缓冲区大小,默认10K
public void setRepository(java.io.File repository)设置临时文件存放位置,默认System.getProperty(“java.io.tmpdir”).
内存缓冲区: 上传文件时,上传文件的内容优先保存在内存缓冲区中,当上传文件大小超过缓冲区大小,就会在服务器端产生临时文件
临时文件存放位置: 保存超过了内存缓冲区大小上传文件而产生临时文件
* 产生临时文件可以通过 FileItem的delete方法删除
2、ServletFileUpload 文件上传核心类
static boolean isMultipartContent(javax.servlet.http.HttpServletRequest request) 判断request的编码方式是否为multipart/form-data
java.util.List parseRequest(javax.servlet.http.HttpServletRequest request) 解析request,将请求体每个部分封装FileItem对象,返回List
void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小
void setSizeMax(long sizeMax) 设置总文件上传大小
void setHeaderEncoding(java.lang.String encoding) 设置编码集 解决上传文件名乱码 *
3、FileItem 表示文件上传表单中 每个数据部分
boolean isFormField() 判断该数据项是否为文件上传项,true 不是文件上传 false 是文件上传
if(fileItem.isFormField()){
// 不是上传项
java.lang.String getFieldName() 获得普通表单项name属性
java.lang.String getString() / java.lang.String getString(java.lang.String encoding) 获得普通表单项value属性 传入编码集用来解决输入value乱码
}else{
// 是上传项
java.lang.String getName() 获得上传文件名 (注意IE6存在路径)
java.io.InputStream getInputStream() 获得上传文件内容输入流
// 上传文件
void delete() 删除临时文件(删除时,必须要管理输入输出流)
}
注意事项:因为文件上传表单采用编码方式multipart/form-data 与传统url编码不同,所有getParameter 方法不能使用 setCharacterEncoding 无法解决输入项乱码问题
JavaScript的多文件上传表单
? 技巧:
? 每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的div中,并对删除按纽的onclick事件进行响应,使之删除删除按纽所在的div。
? 如:
this.parentNode.parentNode.removeChild(this.parentNode);
上传文件存在的问题
? 上传文件后,在服务器端保存位置
第一类存放位置:直接存放WebRoot目录下 和 除WEB-INF META-INF的其它子目录下 例如: WebRoot/upload
* 客户端可以直接在浏览器上通过url访问位置(资料无需通过权限控制,而可以直接访问) —- 对上传资源安全性要求不高、或者资源需要用户直接可见
* 例如:购物商城商品图片
第二类存放位置:放入WEB-INF及其子目录 或者 不受tomcat服务器管理目录 例如: WebRoot/WEB-INF/upload 、c:\ 、d:\abc
* 客户端无法通过URL直接访问,必须由服务器内部程序才能读取 (安全性较高,可以很容易添加权限控制)
* 例如:会员制在线视频
? 上传文件在同一个目录重名问题
如果文件重名,后上传文件就会覆盖先上传文件
文件名 UUID
filename = UUID.randomUUID().toString() + “_” + filename;
? 为了防止同一个目录下方上传文件数量过多 —- 必须采用目录分离算法
1) 按照上传时间进行目录分离 (周、月 )
2) 按照上传用户进行目录分离 —– 为每个用户建立单独目录
3) 按照固定数量进行目录分离 —— 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
4) 按照唯一文件名的hashcode 进行目录分离
public static String generateRandomDir(String uuidFileName) {
// 获得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
// 获得一级目录
int d1 = hashcode & 0xf;
// 获得二级目录
int d2 = (hashcode >>> 4) & 0xf;
return "/" + d2 + "/" + d1;// 共有256目录l
}
? 乱码问题
普通编写项 value属性乱码 ————- fileItem.getString(编码集);
上传文件项 文件名乱码 ——— fileupload.setHeaderEncoding(编码集);
下载
常见文件下载有两种方式
1、超链接直接指向下载资源
如果文件格式浏览器识别,将直接打开文件,显示在浏览器上, 如果文件格式浏览器不识别,将弹出下载窗口
对于浏览器识别格式的文件,通过另存为进行下载
客户端访问服务器静态资源文件时,静态资源文件是通过 缺省Servlet返回的,在tomcat配置文件conf/web.xml 找到 — org.apache.catalina.servlets.DefaultServlet
2、编写服务器程序,读取服务器端文件,完成下载
必须设置两个头信息 ,来自MIME协议 Content-Type Content-Disposition
response.setContentType(getServletContext().getMimeType(filename));
response.setHeader(“Content-Disposition”, “attachment;filename=” + filename); // 以附件形式打开,不管格式浏览器是否识别
处理IE浏览器与Firefox浏览器乱码问题
if (agent.contains(“MSIE”)) {
// IE浏览器
filename = URLEncoder.encode(filename, “utf-8”);
filename = filename.replace(“+”, ” “);
} else if (agent.contains(“Firefox”)) {
// 火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = “=?utf-8?B?”
+ base64Encoder.encode(filename.getBytes(“utf-8”))
+ “?=”;
} else if (agent.contains(“Chrome”)) {
// google浏览器
filename = URLEncoder.encode(filename, “utf-8”);
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, “utf-8”);
国际化
问题:什么是国际化,国际化作用?
软件的国际化:软件开发时,要使它能同时应对世界不同地区和国家的访问,并针对不同地区和国家的访问,
提供相应的、符合来访者阅读习惯的页面或数据。
国际化又称为 i18n:internationalization
对于程序中固定使用的文本元素,例如菜单栏、导航条等中使用的文本元素、或错误提示信息,状态信息等,
需要根据来访者的地区和国家,选择不同语言的文本为之服务。
对于程序动态产生的数据,例如(日期,货币等),软件应能根据当前所在的国家或地区的文化习惯进行显示。
可以提供更友好的访问习惯。
问题:怎样实现国际化?
针对于不同的国家与地区要显示的信息,都配置到配置文件中,
根据当前访问者的国家或语言来从不同的配置文件中获取信息,
展示在页面上。
问题:关于配置文件?
所谓的配置文件就是一组properties文件,它们叫做资源包。
相关的概念:
对于软件中的菜单栏、导航条、错误提示信息,状态信息等这些固定不变的文本信息,可以把它们写在一个properties文件中,
并根据不同的国家编写不同的properties文件。这一组properties文件称之为一个资源包。
ResourceBundler,它是用于从资源包中获取数据的。
关于资源文件(properties)命名:
基名_语言_国家.properties
message_zh_CN.properties
message_en_US.properteis
----------------------------------------------------------
编码演示 properties文件操作以及通过ResourceBundler来获取资源包中信息.
1.资源包文件一般都放置在classpath下(对于myeclipse就是src下)
2.关于ResourceBundle使用
创建:
ResourceBundle bundle = ResourceBundle.getBundle("message");
ResourceBundle bundle = ResourceBundle.getBundle("message",Locale.US);
获取:
bundle.getString(String name);
扩展:关于properties文件中中文问题处理?
在jdk中有一个命令native2ascii.exe。
1.进行一次翻译
native2ascii 回车
中文 回车
2.批量翻译
native2ascii 源文件路径 目录文件路径
例如: native2ascii d:/a.txt d:/a.properties
-------------------------------------------------------------------
国际化的登录页面
1.创建登录页面
2.创建配置文件
3.在登录页面上根据不同的国家获取ResourceBundle
4.在页面上需要国际化的位置,通过ResourceBundle.getString()来获取信息.
问题:在页面上使用了jsp脚本.解决方案:使用标签 。
在jstl标签库中提供了国际化标签.
------------------------------------------------------------------------
关于日期国际化
DateFormat类.
作用:
1.可以将一个Date对象格式化成指定效果的String format方法
2.可以将一个String解析成Date对象 parse方法
1.DateFormat对象创建
DateFormat df1 = DateFormat.getDateInstance(); // 只有年月日
DateFormat df2 = DateFormat.getTimeInstance(); // 只有小时分钟秒
DateFormat df3 = DateFormat.getDateTimeInstance();// 两个都有
DateFormat df1 = DateFormat.getDateInstance(DateFormat.FULL); // 只有年月日
DateFormat df2 = DateFormat.getTimeInstance(DateFormat.MEDIUM); // 只有小时分钟秒
DateFormat df3 = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.SHORT);// 两个都有
DateFormat df1 = DateFormat.getDateInstance(DateFormat.FULL,Locale.US); // 只有年月日
DateFormat df2 = DateFormat.getTimeInstance(DateFormat.MEDIUM,Locale.US); // 只有小时分钟秒
DateFormat df3 = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.SHORT,Locale.US);// 两个都有
关于货币国际化
NumberFormat类
1.对数值进行格式化
NumberFormat nf = NumberFormat.getIntegerInstance();
2.对数值进行百分比
NumberFormat nf = NumberFormat.getPercentInstance(Locale.FRANCE);
3.对数值进行以货币显示
NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.US);
MessageFormat
动态文件格式化.
MessageForamt可以对一个模板中的信息进行动态赋值.
1.MessageFormat使用
MessageForamt.format(String pattern,Object... params);
2.说明一下关于动态文本中的占位符?
例如:{0} is required
1.注意占位符只能使用{0}---{9}之间的数值.
2.关于占们符的格式
{argumentIndex}: 0-9 之间的数字,表示要格式化对象数据在参数数组中的索引号
{argumentIndex,formatType}: 参数的格式化类型
{argumentIndex,formatType,FormatStyle}: 格式化的样式,它的值必须是与格式化类型相匹配的合法模式、或表示合法模式的字符串。
formatType可以取的值有:number date time
formatStyle可以取的值有
number类型可以取:integer currency percent
date类型可以取的:short medium full long
time类型可以取的:short medium full long
1.注解
问题:什么是注解,它有什么作用?
@xxx就是一个注解。
注释:它是用于描述当前代码功能,是给程序员使用的。
注解:它是描述程序如果运行,是给编译器,解释器,jvm使用。
jdk中自带三个注解:
1.@Override
是给编译器使用,用于描述当前的方法是一个重写的方法。
注意:在jdk1.5与jdk1.6中有区别
jdk1.5中@Override它只能描述继承中的重写.
jdk1.6中@Override它不仅能描述继承中的重写,还可以描述实现中的重写.
2.@Deprecated
它是用于描述方法过时。
问题:方法什么时候过时?
1.有新的版本的方法替换旧版本方法。
2.在旧的版本中存在安全隐患的方法
3.@SuppressWarnings
去除程序中的警告信息
unused 变量未使用
deprecation 使用了不赞成使用的类或方法时的警告
unchecked 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
fallthrough 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
path 在类路径、源文件路径等中有不存在的路径时的警告。?
serial 当在可序列化的类上缺少 serialVersionUID 定义时的警告。?
finally 任何 finally 子句不能正常完成时的警告。
all 关于以上所有情况的警告。
-------------------------------------------------------
关于定义注解
1.定义注解
@interface 名称 就定义了一个注解,要想使用 在类,方法,属性上直接 @名称.
问题:@interface 名称,是声明了一个注解,它的本质是什么?
@interface MyAnnotation{}
它的本质就是
import java.lang.annotation.Annotation;
interface MyAnnotation extends Annotation
{
}
注解的本质就是一个接口,它继承了Annotation接口。
所的的注解都实现了这个接口,但是,不能手动实现。
注解是jdk1.5的新特性.
------------------------------------------------------
2.注解中的成员
接口中的成员:
属性:public static final
方法: public abstract
注解成员:
1.可以有属性
注解中可以有属性,但是基本不使用。
2.可以有方法
在开如,一般使用注解时,只研究它的方法,我们一般管它叫做注解中的属性.
1.关于注解中的属性的类型问题.
它的类型只能是以下几种:
1.基本类型
整型:byte short int long
浮点:float double
字符:char
逻辑:boolean
2.String
3.Class
4.enum
5.Annotation
6.以上类型的一维数组。
2.关于注解中有属性,使用的问题
如果一个注解中有属性,并且属性没有默认值,那么我们在使用注解时,必须给注解的属性赋值.
关于属性赋值方式:
1.默认值问题
String st() default "abc";
2.如果是单值
注解(属性名称=值)
例如:@MyAnnotation3(i=1)
3.如果是数组
1.如果只赋一个值
注解(属性名称=值)
例如:@MyAnnotation3(i=1)
2.如果要赋多个值
注解(属性名称={值1,值2,...})
例如:@MyAnnotation3(i={1,2,3})
4.关于属性名称value问题
可以省略属性名称
例如 @MyAnnotation3("hello");
如果value属性是一个数组:
@MyAnnotation3({"a","b"})
如果注解中有value属性,还有其它属性:
那么value属性名称不能在省略.
------------------------------------------------------------------
3.元注解
修饰注解的注解
1.@Retention
作用:是指定注解给谁使用.
它的属性值只能是以下三个
RetentionPolicy.SOURCE 给编译器使用 使用后抛弃
RetentionPolicy.CLASS 给解析器使用。当jvm加载完成后,就抛弃.
RetentionPolicy.RUNTIME jvm加载完成后,还存在。开发人员可以通过反射来获取注解相关信息.
2.@Target
作用:就是定义注解在什么位置使用
3.@Documented
作用:是通过javadoc生成的文档中是否抽取注解描述.
4.@Inherited
作用:是描述当前注解是否具有继承性
想要开发,有功能的注解,对于程序员,一定会使用的元注解是:
@Retention
@Target
------------------------------------------------------------------------
注解案例---银行最大转账金额:
这个案例的目的:
1.怎样通过反射来操作注解
2.注解可以替换配置文件。
代码实现:
1.将银行最大转账金额,定义在配置文件中,使用时,直接从配置文件中读取.
2.使用注解来替换配置文件。
1.定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankInfo {
int maxMoney();
}
2.通过反射来获取注解信息
1.获取当前方法的Method对象。
1.得到Class对象
1.类名.class
2.对象.getClass()
3.Class.forName(String className);
2.得到Method对象
Class.getDeclaredMethod(String methodName,Class...paramClass);
2.在Method类中有一个 getAnnotation(Class annotationClass),可以获取一个注解对象.
3.通过注解对象来调用其属性.
-----------------------------------------------
注解可以替换配置文件,替换的是什么?
配置文件的出现,它的主要目的就是解耦合。但是随着现在开发程序越来越庞大,配置文件的缺点
就出现了,配置文件内容越来越庞大,就不利于我们开发与阅读.
这时就出现了注解,因为注解可以直接写在代码上,并且,通过注解也可以解耦合。
----------------------------------------------------------------------------------------------
注解示例2--jdbc连接
=====================================================================================================================
2.servlet3.0特性(了解)
在servlet3.0中可以使用注解来替代我们配置文件.
简单说:在servlet3.0中可以没有web.xml文件。
servlet3.0
servlet2.5
问题:怎样知道我们当前使用的是哪个版本?
在web.xml文件中有一个属性version=""它就可以标识当前是哪个版本.
版本对应关系
servlet2.5 javaee5.0 tomcat 5.x tomcat6 jdk1.5
servlet3.0 javaee6.0 tomcat7.0 jdk1.6
------------------------------------------------------
关于servlet3.0特性:
1.使用注解来替换配置文件
@WebServlet("/hello") 用于配置servlet
@WebFilter("/*") 用于配置Filter
@WebListener 用于配置Listener
关于这些注解细节:
以@WebServlet("/hello") 为例
注意:属性urlpatterns与values它们都是描述访问当前servlet的路径,但它们不能一起出现,只能使用一个.
<servlet>
<servlet-name></servlet-name> String name() default "";
<servllet-class></servlet-class>
<init-param> WebInitParam[] initParams() default {};
<param-name>
<param-value>
</init-param>
<load-on-startup> int loadOnStartup() default -1;
</servlet>
<servlet-mapping>
<servlet-name></servlet-name>
<url-pattern></url-pattern> String[] urlPatterns() default {}; String[] value() default {};
</servlet-mapping>
在servlet中怎样获取初始化参数
ServletConfig对象获取
在web.xml文件中的属性 metadata-complete,可以取值为true,false,
如果为false,代表servlet3.0中的注解可以使用,如果为true,代表不可以使用注解。
2.servlet3.0中的文件上传
浏览器端:
1.method=post
2.encType="multipart/form-data"
3.使用<input type="file" name="f">
服务器端:
servlet3.0完成。
1.要在servlet上添加注解@MultipartConfig
表示Servlet接收multipart/form-data 请求
2.在servlet中要想得到上传信息,通过request对象获取一个Part对象。
Part part=request.getPart();
part.write(String filename);
问题:
1.关于上传文件中文名称乱码问题
因为上传是post请求,直接使用post乱码解决方案就可以 request.setCharacterEncoding("utf-8");
2.关于获取上传文件名称
通过Part获取一个header
String cd = part.getHeader("Content-Disposition");
在这个header中包含了上传文件名称,直接截取出来就可以。
String filename = cd.substring(cd.lastIndexOf("\\") + 1,cd.length() - 1);
3.如果多文件上传怎样处理?
request.getParts();
3.servlet3.0中异步处理
本质就是在服务器端开启一个线程,来完成其它的操作。
1.必须在注解添加一项
@WebServlet(value = "/reg", asyncSupported = true)
asyncSupported=true,代表当前servlet支持异步操作.
2.需要一个异步 上下文对象,通过这个对象,可以获取request,response对象.
AsyncContext context = req.startAsync();
还可以对异步上下文进行监听,在它的监听器方法中有一个onComplete,可以用于判断结束。
=============================================================================================================
3.动态代理
1.代理模式
代理模式作用:
屏蔽真实行为的访问,让程序更加安全。
可以对真实行为的调用进行控制。
通过一个案例:来说明代理的实现以及代理的作用
代理模式实现:
1.代理类与被代理类要实现同一个接口.
2.在代理类中持有被代理对象.
3.在代理类中调用被代理的行为。
AOP:面向方面的编程。
AOP的底层实现就是通过动态代理来做到的。
2.动态代理
它就是在代理模式基础上发展的,它不在是对单一的类型进行代理,
而是可以对任意的一个实现了接口的类的对象做代理。
3.动态代理实现
有两种方式:
1.通过jdk中提供的Proxy类来实现
这种方式要求,被代理类必须实现接口。
简单说,只能为接口做代理.
2.通过cglib来实现。
它不要求,实现接口。
我们讲第一个.
代码实现:
Proxy类中有一个方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h);
参数:
loader:
要求,传递的是被代理类的类加载器ClassLoader.
类加载器怎样获取:
得到其Class对象。在Class类中提供一个方法 getClassLoader();
interfaces:
要求:得到被代理对象所实现的接口的所有Class对象。
怎样获取所有实现接口的Class对象?
得到其Class对象,在Class类中提供一个方法 getInterfaces();
它返回的是Class[],就代表所实现接口的所有Class对象。
h:
它的类型是InvocationHandler,这是一个接口。
InvocationHandler 是代理实例的调用处理程序 实现的接口。
InvocationHandler接口中有一个方法invoke;
// 参数 proxy就是代理对象
// 参数method就是调用方法
// 参数args就是调用的方法的参数
// 返回值,就是真实行为执行后返回的结果,会传递给代理对象调用的方法.
public Object invoke(Object proxy, Method method, Object[] args);
.类加载器
问题:什么是类加载器,有什么作用?
类加载器的作用就是将java中的字节码文件(.class文件)转换成Class对象。
当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构:
1.引导类加载器 BootStrap jre/lib/rt.jar
2.扩展类加载器 ExtClassLoader JRE/lib/ext/*.jar
3.应用类加载器(系统类加载器) AppClassLoader SystemClassLoader CLASSPATH指定的所有jar或目录
在java中ClassLoader代表类加载器,所有的类加载器都是ClassLoader的子.
演示类加载器:
问题:类加载器如果获取?
在Class类中有一个方法 getClassLoader()它返回的就是一个类加载器.
1.获取引导类加载器
ClassLoader cl = String.class.getClassLoader();
System.out.println(cl);
结果是null.
原因:引导类加载器特殊,它根本就不是java实现。所有在得到引导类回载器是结果就是null.
2.扩展类加载器
ClassLoader cl = AccessBridge.class.getClassLoader();
System.out.println(cl); //sun.misc.Launcher$ExtClassLoader@9cb0f4
3.应用类加载器
ClassLoader cl = this.getClass().getClassLoader();
System.out.println(cl); //sun.misc.Launcher$AppClassLoader@164dbd5
----------------------------------------
全盘负责委托机制
全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。
委托机制:先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。
-------------------------------------------
自定义类加载器
创建了一个类 javax.activation.MimeType,在这个类中有一个方法show();
当jvm加载这个类时,因为在rt.jar包下也存在一个MimeType类,并且包名都一样,
这时jvm就会使用引导类加载器加载这个类,而我们想得到的其实是应该由应用类加载
器加载的Class.
解决方案:
自定义类加载器.
1.创建一个类,去继承自ClassLoader
2.重写findClass方法,在这个方法中通过
泛型反射
问题:在BaseDaoImpl类中需要得到当前这个类上的泛型的Class对象,而直接通过T.class这是不对的.
public class BaseDaoImpl<T> implements BaseDao<T> {
public T findById(int id) {
// Session session=HibernateUtils.getSession();
// session.get(T.class,id);
return null;
}
怎样得到当前这个类上的泛型的Class?
Type type = this.getClass().getGenericSuperclass(); // 得到当前类上的泛型--父类型
Type[] params = ((ParameterizedType) type).getActualTypeArguments(); // 得到当前类上所有的泛型类型Class
clazz = (Class) params[0];