servlet+jsp基础知识四

12年前
复习:
1、 JSP
jsp引擎
 一个程序,把jsp文本文件转换为servlet
jsp的语法
 <%......%>
 <%=表达式%>---<%out.write(表达式);%>
JSP内建对象和隐含对象
 SUN的标准规定的,由JSP引擎声明好的变量
 out
 request
 response
application
page
config
pageContext

JSP指令
 写在jsp文件中,指示jsp引擎在生成servlet的时候一些特性或者注意的问题
 <%@ .......%>
 <%@page import="java.sql.*,java.util.*"%>
 <%@include  file="head.jsp"  %>
 
2、cookie技术
 理解原理
 通过协议的方式来理解原理
 第一次发请求的时候
  GET /... HTTP/1.1
  .........
 响应
  HTTP/1.1 200 OK
  Set-Cookie:som_key=1000
  Set-Cookie:other_key=2000
  ...
  HTML....
 浏览器就会把这些信息保存在客户端

 当下一次浏览器又访问时
  GET /... HTTP/1.1
  .........
  Cookie:some_key=1000,other_key=2000

3、了解cookie api
 Cookie cookie=new Cookie("name","value");
 //cookie.setMaxAge(int s);   0  <0  >0
 Response.addCookie(cookie);
 将cookie放入到响应http包头
 Cookie [] cookies=request.getCookies();//注意null
4、用cookie的注意事情
 a、编码问题(特殊字符与协议的冲突)
  URLEncoder 和URLDecoder
 b、值得大小和个数
  4k,不能超过20个(一个域名20个cookie)
 c、cookie的安全问题
  cookie可以伪造
 d、路径问题
  test/a/save_cookie.jsp
  <%
   Cookie cookie = new Cookie("some_key","1000");
   cookie.setPath("/servlet04");//servlet04下面所有应用都带cookie
   //可以调用cookie的方法,可以设置路径
   //cookie.setPath("/test");//下一次请求,必须是/test下面的
   //默认值,如果不指定的话,/test/a
   response.addCookie(cookie);
  %>
  test/b/get_cookie.jsp

  <%
   Cookie[] cookies = request.getCookies();
   if (cookies != null) {
    for (int i = 0; i < cookies.length; i++) {
    Cookie cookie = cookies[i];
   
     out.println("<h1>" + cookie.getName() + ": "
     + cookie.getValue() + "</h1>");
    }
   }
  %>
5、session技术

 sesssion技术要利用cookie,cookie记录了session中的session id
 session 有一个session id,就是这个session的唯一id
 session会对服务器有压力,session如果用不好的话,服务器就完蛋了。
 什么时候用session,什么时候用cookie
  一个用户登陆一次之后,1年之内不用再登陆。----〉最好是用cookie,服务器不能存储那么多的session数据
  一个用户登陆了,半个小时之内不用再登陆。-----〉最好使用session。
 大型的门户系统,最好是用cookie;服务器的sesion肯定有个超时的机制
6、session基本原理,例子及抓包分析
我们来看下session到底是怎么回事//////
 基本原理:服务器端先要在内存中做一个对象session,这个session有一个唯一的Id(sessionid),当客户端来访问来
服务器时访,服务器响应的时候会把sessionid放到其cookies里面让客户端带走。当客户端再次访问服务器的时候,
就可以通过sessionid来获得对象。

 通过例子来深入了解session的工作原理
 public void service(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  /*
   * 如果当前的请求信息中有带有存储Session ID的Cookie,且该Session ID
   * 对应的Session对象存在,则返回该对象
   *
   * 否则,该方法将创建新的Session对象并且将新产生的Session ID 作为Cookie
   * 通过响应存到浏览器端
   */
  HttpSession session = request.getSession();//通过request.getSession获得一个session对象
  System.out.println(session.getId());//获得这个session的编号,SESSIONID
  
  
  // void setAttribute(String, Object)
  session.setAttribute("date", new Date());//存session属性,在这里是存储一个date对象
  
  //设置最大的不活动时间
  session.setMaxInactiveInterval(60*30);//默认半小时,   秒
  
  // 让Session失效一般用在login的登出
  //session.invalidate();//一调用这个方法,session立马失效
  
  // Object getAttribute(String)
  Object obj = session.getAttribute("date");//获得session的属性,在这里是获得一个date对象并返回
  System.out.println(obj);
 }
 session设置的cookie默认为负数

抓包分析Session工作原理
第一次访问
 请求信息
 GET /servlet/session HTTP/1.1
 HOST:localhost:8080
 ......
 Connection:keep-alive
 响应
 HTTP/1.1 200 OK
 Server:apache-coyote/1.1
 Set-Cookie:JSESSIONID=BCC703F862DC0CEF7CDEB6088C58E82A;Path=/servlet
 content-type:image/x-icon
 content-length:21333
 Date:...
 
 通过console显示sessionid也为:BCC703F862DC0CEF7CDEB6088C58E82A
 
刷新页面,实现第二次访问
 验证同一session:通过console查看sessionid还是BCC703F862DC0CEF7CDEB6088C58E82A
 第2次请求
 GET /servlet/session HTTP/1.1
 HOST:localhost:8080
 ......
 Connection:keep-alive
 Cookie:JSESSIONID=BCC703F862DC0CEF7CDEB6088C58E82A
 Cache-Control:max-age=0
 HTTP/1.1 200 OK
 Server:apache-coyote/1.1
 content-type:image/x-icon
 content-length:21333
 Date:...
关闭浏览器:重新访问该页面
 console上显示:其Cookie:JSESSIONID=F7CC58E8862DC0CEDEB60882ABCC703F
 
7、session与cookie的联系点 
session用cookie存储session id
session可以存储任何对象,而cookie相对来说编码麻烦点
8、session的序列化和反序列化
接着上面的例子,重新做个实验:
 1、第一次浏览session
 2、第二次浏览同一session
 3、第三四浏览器同一session
 4、关闭tomcat服务,但不关闭浏览器
 5、开启tomcat服务,刷新浏览器页面
 6、sessionid没有变化
 ??为什么呢?这就是序列化和反序列化
 
 服务器关闭的时候,会把当前内存中session都存储到服务器硬盘的一个文件里面。
当服务器启动起来之后,再把文件里面的内容读取到内存中。这就是序列化和反序列化session
注意:正常关闭的情况下才能序列化

 也就是说往session对象里面放置的对象必须是可序列化的!!!这也是一个重要规则
(从管理维护的角度来看,在用的系统时,如需关机维护时,需先停止服务,使其序列化,然后再关机
等计算机启动起来之后,再启动服务,使其反序列化)
如下例:
public class User implements java.io.Serializable {
 //============================
  //必须要加上这个才能序列化
  //必须实现了序列化接口才能最终序列化
 private String id;
 private String name;
 public String getId() {
  return id;
 }
 public void setId(String id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
9、极品问题
 a、
 ........
 response.sendRedirect("1.jsp");//执行完这句还执行下句吗?
 System.out.println("hahahhahah");
 .....
  肯定是要执行的。
  response.sendRedirect只是在响应的时候,告诉浏览器响应代码为302,其location为1.jsp而已
  整个这个方法还是要执行完的。
 b、......
    response.sendRedirect("1.jsp");//执行完这句还执行下句吗?如果执行的话,会输出到1.jsp中吗?
    PrintWriter out=response.getWriter();
    out.println("hahahhahaha");
    ......
  肯定会执行,但是在真正响应的时候,只是将其转向到1.jsp,而后面语句的html语句不会带到1.jsp中
  会丢弃掉,只发送一个响应302转发而已。
  
10、文件上传和验证码  
验证码程序
 1、客户端首先要访问服务器,servlet就产生一个验证码图片(服务器要保存验证码)
 2、客户端输入一个识别的验证码,发给服务器
 3、服务器要对验证码进行比对,如果一样,则通过
 是不是可以通过session来保存验证码呢? 
 
 
前面的模式:
 a、servlet或者jsp里面包含对数据库的连接和操作,也包含了相关的业务逻辑
 b、这种情况下,如果程序比较大的话,后期的维护工作量比较大
 怎么来解决这种问题呢?
  把对数据库的连接和操作封装起来,比如说弄个工具类
  
  JSP+JavaBean
 
11、JSP+JavaBean,一般不写servlet;
   JSP负责页面,尽量不写servlet;
   JavaBean一堆java的类:封装员工数据本身,封装对数据的操作
  
12、例子:显示员工列表,带分页功能
   a、一堆jsp:
     显示结果jsp:如emp_list.jsp
     表单jsp:login_form.jsp
     操作jsp:接收用户输入,可以调用javabean类来访问相关数据
    jsp中只会调用javaBean,不能写sql语句
     
   b、java类:封装对数据的访问增删改查
设计从后往前方式:从无到有
 1、数据库
  table---emp
 2、javaBean
  a、先考虑好类名,不要考虑其方法和属性如:Emp类
  b、类名Dao:封装一些方法,完成对员工数据的增删改查 EmpDao
  一般情况下,一个table对应一个类,一个Dao
 3、页面
  emp_list.jsp
  
搭建:
 org.e2learning.domain//存放类
 org.e2learning.dao//存放操作数据库的类

需求:
 员工列表
  -----------------------  |
  |姓名   id    salary  |
  |          |
  |       |
  |       |
  |       |
  |上一页  2   下一页
jsp页面
 emp_list
浏览器          jsp              DAO
 | ——〉  |  方法  |    获得第一页的数据
 |     |————〉  |
 | <——  |     |
 | ——〉  |     |   再次发请求,必须显示哪一页n的请求
 |     |————〉  |方法n
 |  <—— |     |
 |     |     |
 |     |     |隐含存在一个返回一个总的页数
 |     |     |
 |     |     |

13、构建一个工具类,通过properties类来将数据库的连接导入
dbo.properties
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@192.168.0.20:1521:test1
user=scott
password=tiger
thin:
 连接数据库的这一端不用oracle的任何组件

package org.whatisjava.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class ConnectionUtils {
 private static String driver;
 private static String url;
 private static String user;
 private static String password;
// Class.forName("");
// Connection con=DriverManager.getConnection("","","");
// //一般情况下,我们会把数据库等的驱动,用户名密码等写到一个配置文件中
// //数据结构比较简单用属性文件,比较复杂的话用xml
 static {
  //静态块初始化这些属性
  try {
   Properties props = new Properties();
   //Properites类
   
   //props.load(new FileInputStream("c:/db.properties"));//难点
   //在这里开发,在这里运行。以后开发的时候,这里开发,放到其它服务器上安装
   //在这里不要用文件流了。按照下面的方式
   //放到src下面的非java文件都会copy到classes目录下
   //java虚拟机可以提供一种机制,可以把classes下面的所有文件装载。如下
   //像以前学的hibernate
   //按照java反射
   props.load(ConnectionUtils.class.getClassLoader()
     .getResourceAsStream("org/whatisjava/dao/db.properties"));
   driver = props.getProperty("driver");
   url = props.getProperty("url");
   user = props.getProperty("user");
   password = props.getProperty("password");
   Class.forName(driver);
  } catch (Exception e) {
  }
 }
 public static Connection getConnection() throws SQLException {
  Connection con = DriverManager.getConnection(url, user, password);
  return con;
 }
}
 
14、我们接下来看下EmpDao是怎么写的?
package org.whatisjava.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.whatisjava.domain.Emp;
public class EmpDao {
 private static final String getEmpList =
  "select empno, ename, sal from(select empno,ename,sal, " +
  "rownum num from emp where rownum<?) where num>=?";
 //SQL里面的分页技术
 //select empno,enmae,sal from emp where rownum<=20
 //rownum使用<号是没有问题的。使用〉号会出现问题
 //rownum是数据先查询出来,再进行的rownum编号
 //
 //select empno,ename,sal from(select empno,ename,sal,rownum num from emp where rownum<?) where num>=?
 
 //问题:我要对工资进行排序之后再分页
 //要搞清楚先order by还是先rownum,
 //也可以用hibernate的分页查询
 
 public List<Emp> getEmpList(int page, int rowsPerPage) throws SQLException {
  //返回参数
  //首先要获得连接,用刚才写得类
  Connection con = ConnectionUtils.getConnection();
  PreparedStatement stmt = con.prepareStatement(getEmpList);
  //计算公式可以按照实际的来进行计算
  int start = (page - 1) * rowsPerPage + 1;
  int end = start + rowsPerPage;

  //
  stmt.setInt(1, end);
  stmt.setInt(2, start);
  ResultSet rs = stmt.executeQuery();
  ArrayList empList = new ArrayList();
  while (rs.next()) {
   Emp emp = new Emp();
   emp.setId(rs.getInt(1));
   emp.setName(rs.getString(2));
   emp.setSalary(rs.getDouble(3));
   empList.add(emp);
  }
  return empList;
 }

 public int getTotalPages(int rowsPerPage) {
  //每一页有多少行的情况下,返回多少页 
  return 0;
 }
}

15、Oracle中ROWNUM伪列
重点: rownum是在结果集上增加的一列
**********************************************************************************************
ORACLE 中ROWNUM用法总结!(来源于网络)
对于 Oracle 的 rownum 问题,很多资料都说不支持>,>=,=,between...and,只能用以上符号(<、<=、!=),
并非说用>,>=,=,between..and 时会提示SQL语法错误,而是经常是查不出一条记录来,还会出现似乎是莫
名其妙的结果来,其实您只要理解好了这个 rownum 伪列的意义就不应该感到惊奇,同样是伪列,rownum
与 rowid 可有些不一样,下面以例子说明
假设某个表 t1(c1) 有 20 条记录
如果用 select rownum,c1 from t1 where rownum < 10, 只要是用小于号,查出来的结果很容易地与一般
理解在概念上能达成一致,应该不会有任何疑问的。
可如果用 select rownum,c1 from t1 where rownum > 10 (如果写下这样的查询语句,这时候在您的头脑
中应该是想得到表中后面10条记录),你就会发现,显示出来的结果要让您失望了,也许您还会怀疑是不谁
删了一些记录,然后查看记录数,仍然是 20 条啊?那问题是出在哪呢?
先好好理解 rownum 的意义吧。因为ROWNUM是对结果集加的一个伪列,即先查到结果集之后再加上去的一
个列 (强调:先要有结果集)。简单的说 rownum 是对符合条件结果的序列号。它总是从1开始排起的。所以
你选出的结果不可能没有1,而有其他大于1的值。所以您没办法期望得到下面的结果集:
11 aaaaaaaa
12 bbbbbbb
13 ccccccc
.................
rownum >10 没有记录,因为第一条不满足去掉的话,第二条的ROWNUM又成了1,所以永远没有满足条件的记录。
或者可以这样理解:
ROWNUM是一个序列,是oracle数据库从数据文件或缓冲区中读取数据的顺序。它取得第一条记录则rownum值为1,
第二条为2,依次类推。如果你用>,>=,=,between...and这些条件,因为从缓冲区或数据文件中得到的第一条记
录的rownum为1,则被删除,接着取下条,可是它的rownum还是1,又被删除,依次类推,便没有了数据。
有了以上从不同方面建立起来的对 rownum 的概念,那我们可以来认识使用 rownum 的几种现象
select rownum,c1 from t1 where rownum != 10 为何是返回前9条数据呢?它与 select rownum,c1 from
tablename where rownum < 10 返回的结果集是一样的呢?
因为是在查询到结果集后,显示完第 9 条记录后,之后的记录也都是 != 10,或者 >=10,所以只显示前面9条记录。
也可以这样理解,rownum 为9后的记录的 rownum为10,因条件为 !=10,所以去掉,其后记录补上,rownum又是10,
也去掉,如果下去也就只会显示前面9条记录了
为什么 rownum >1 时查不到一条记录,而 rownum >0 或 rownum >=1 却总显示所以的记录
因为 rownum 是在查询到的结果集后加上去的,它总是从1开始
为什么 between 1 and 10 或者 between 0 and 10 能查到结果,而用 between 2 and 10 却得不到结果
原因同上一样,因为 rownum 总是从 1 开始
从上可以看出,任何时候想把 rownum = 1 这条记录抛弃是不对的,它在结果集中是不可或缺的,少了rownum=1
就像空中楼阁一般不能存在,所以你的 rownum 条件要包含到 1
但如果就是想要用 rownum > 10 这种条件的话话就要用嵌套语句,把 rownum 先生成,然后对他进行查询。
select *
from (selet rownum as rn,t1.* from a where ...)
where rn >10
一般代码中对结果集进行分页就是这么干的。
另外:rowid 与 rownum 虽都被称为伪列,但它们的存在方式是不一样的,rowid 可以说是物理存在的,表示记录在
表空间中的唯一位置ID,在DB中唯一。只要记录没被搬动过,rowid是不变的。rowid 相对于表来说又像表中的一般列,
所以以 rowid 为条件就不会有 rownum那些情况发生。
另外还要注意:rownum不能以任何基表的名称作为前缀。

根据以上理解:
 select  empno,ename,sal from emp where rownum<=20 and rownum>11;//此语句有问题
 select empno,ename,sal from(select  empno,ename,sal,rownum num from emp where rownnum<20)
  where num>11;