第七章 超长文本的操作——Clob类型数据的存取
回到我编写留言板的时候,当时要存放留言板的正文内容,发现VARCHAR2()(可变长度的字符串)只能存4000字节,也就是2000个汉字,这也太少了啊,查一下数据库类型的资料,发现有这么几个类型:LONG,2G(要是我没记错的话,它是为了向前兼容,不推荐使用);CLOB,4G,字符;BLOB,4G,二进制。看来超长文本应该使用CLOB了,图片自然是用BLOB了,询问了一下别人,知道这两种类型是不能像VARCHAR2()那样直接存的,只好作罢,先用VARCHAR2()顶一阵。
后来我终于有空了,决心要完成这个任务,在网上查了一番资料,看了别人的例子,总算是无师自通看明白了:存的时候需要使用empty_clob()(这个不是Java的函数)先存一个空的标识(用我的理解就是先初始化一下),然后通过“流”将数据写入。下面是代码,其中try里面的是CLOB类型的存操作:
-----------------------------------save_new.jsp------------------------------------------
<%@ include file="include.inc"%>
<%@ page contentType="text/html;charset=gb2312" errorPage="request_error.htm"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>无标题文档</title>
</head>
<body>
<%
String title = request.getParameter("title");
String kind=request.getParameter("kind");
String newtitle=title.replaceAll("'","''");//用replaceAll()将text字串中所有的单引号改成连续两个单引号
String text = request.getParameter("text");
//String text1=text.replaceAll("'","''");存clob时不需将单引号改成连续两个单引号
String text2=text.replaceAll("<","<");//用replaceAll()将字串中所有的<改成<
String newtext=text2.replaceAll(">",">");//用replaceAll()将字串中所有的>改成>
//replace只能处理单个字符!!
//改'是为了不影响数据库的查询语句
//改<>是防止网页把他们生成标签,比如:<table>,<form>等
String author=session.getAttribute("name").toString();
out.println(author);
long ID=System.currentTimeMillis();//取得一个时间,从1970-1-1 0:00:00开始到当前时间的毫秒数,用这个数作为该文章的ID标识
java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //格式化日期
java.util.Date currentTime_1 = new java.util.Date();//得到当前系统时间
String strdate = formatter.format(currentTime_1); //将日期时间转换成字符串形式
Connection con = null;
PreparedStatement stmt = null;//不能用Statement,我也不知道为什么,查了API,说这个PreparedStatement可以用于
//高效的多次执行语句
/**
(后补:推荐全面使用PreparedStatement代替Statement,具体原因很多,请参阅互联网资料)
*/
ResultSet rs = null;
try
{
Class.forName(CLASSFORNAME);//载入驱动程式类别
con=DriverManager.getConnection(SERVANDDB);//建立数据库连接
con.setAutoCommit(false);//设置不自动提交
String sql="insert into article(id,author,title,time,kind,text_clob) values ('"+ID+"','"+author+"','"+newtitle+"','"+strdate+"','"+kind+"',empty_clob())";//我的数据库中存文本的CLOB型字段名为:text_clob
stmt=con.prepareStatement(sql);//添加一条clob字段为空的记录,
stmt.executeUpdate();//执行
stmt=null;//下次使用前清空
sql="select text_clob from article where id='"+ID+"' for update";//正是由于这条语句,id这个标识就必须得唯一!!!!
//如果数据库中已有一条记录的id与当前的id值相同,那么会查到那条记录,也就无法向新插入的记录中的clob字段进行写入!
stmt=con.prepareStatement(sql);//查找刚刚添加的那条记录
rs=stmt.executeQuery();
oracle.sql.CLOB osc = null;//初始化一个空的clob对象
if (rs.next())
osc=(oracle.sql.CLOB)rs.getClob("text_clob");
Writer w=osc.getCharacterOutputStream();//使用字符输出流
w.write(newtext);//将字符串str_text写到流中
w.flush();//输出流中数据,大概是正式向clob中写了
w.close();
con.commit();//执行
response.sendRedirect("index.jsp?page=1");//回主页面
}
catch(Exception e)
{out.println(e);}
finally
{
if (rs!=null)
rs.close();
if (stmt!=null)
stmt.close();
if (con!=null)
con.close();
}
%>
</body>
</html>
--------------------------------------------------------------------------
取的时候就相对简单了,主要就两句,看下面的代码:
--------------------------------------------------------------------------
<%
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
long ID=Long.parseLong(request.getParameter("ID"));//将接收到的字符串转成long型
try
{
Class.forName(CLASSFORNAME);//载入驱动程式类别
con=DriverManager.getConnection(SERVANDDB);//建立数据库连接
stmt=con.createStatement();
String sql="select * from Article where ID='"+ID+"'";
rs=stmt.executeQuery(sql);
if (rs.next())
{ //下2行是用于从clob类型里读数据的,转成字符串。
oracle.sql.CLOB osc=(oracle.sql.CLOB)rs.getClob("text_clob");//我的数据库中存文本的CLOB型字段名为:text_clob
String str_text=osc.getSubString((long)1,(int)osc.length());//subString是截取字符串(从1截到length),如果用 osc.getString的话出错。
out.print(str_text);
}//if
}//try
catch(Exception e){}
rs.close();
stmt.close();
con.close();
%>
--------------------------------------------------------------------------
现在对CLOB类型的存取问题已经解决,但是当你操作文本字符串的时候你会发现很多问题,比如说,文本里有单引号(')、标签(如<table>、<br>),还有回车和空格的问题等等,都需要你在实践中发现并解决。
下一章说说BLOB。
第八章 图片文件的操作——Blob类型数据的存取和使用第一个Servlet
关于这部分内容,我在网上找到一些资料,最后按照我的需求,经过改编得到了下面的代码:
------------------------------upphoto.htm------------------------------------
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>无标题文档</title>
head>
<body>
上传图片:
<form name="form1" method="post" action="upphoto.jsp">
<input name="id" type="text">
(输入一个整数作为该图片的ID)<br>
<input size="50" name="file" type="file">
<br>
<input type="submit" name="Submit" value="提交">
</form>
<p> </p>
<p> </p>
显示图片:<br>
<br>
<form name="form2" method="post" action="showphoto.jsp">
<input type="text" name="vid">
(输入该图片的ID)<br>
<input type="submit" name="Submit2" value="提交">
</form>
</body>
</html>
---------------------------------------------------------------------------
upphoto.htm包括两个<form>,form1用于选择要存于数据库的图片;form2用于显示一张数据库里的图片。
-----------------------------upphoto.jsp----------------------------------
<%@ include file="include.inc"%>
<%@ page contentType="text/html;charset=gb2312"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>无标题文档</title>
</head>
<body>
<%
int id=Integer.parseInt(request.getParameter("id"));
request.setCharacterEncoding("gb2312");
String f=request.getParameter("file");//得到路径,如:c:\d\e.jpg
String fpath=f.replaceFirst("\\\\","\\\\\\\\");//把一个\变成两个\,即路径改成:c:\\d\e.jpg
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
Class.forName(CLASSFORNAME);//载入驱动程式类别
con=DriverManager.getConnection(SERVANDDB);//建立数据库连接
con.setAutoCommit(false);
String sql="insert into blb(id,blob) values("+id+",empty_blob())";//数据库里那个表名叫blb,字段的名就叫blob
pstmt=con.prepareStatement(sql);//添加一条blob字段为空的记录,
pstmt.executeUpdate();
pstmt=null;
sql="select * from blb where id="+id+" for update";
pstmt=con.prepareStatement(sql);//查找刚刚添加的那条记录
rs=pstmt.executeQuery();
if (rs.next())
{
oracle.sql.BLOB osb = (oracle.sql.BLOB) rs.getBlob("blob");
//到数据库的输出流
OutputStream outStream = osb.getBinaryOutputStream();
//这里用一个文件模拟输入流
File file = new File(fpath);
InputStream inStream = new FileInputStream(file);
//将输入流写到输出流
byte[] b = new byte[osb.getBufferSize()];
int len = 0;
while ( (len = inStream.read(b)) != -1)
{
outStream.write(b, 0, len);
}
//依次关闭(注意顺序)
inStream.close();
outStream.flush();
outStream.close();
con.commit();
rs.close();
pstmt.close();
con.close();
out.print("<script>");
out.print("alert('操作成功!');");
out.print("window.location.href='upphoto.htm';");
out.print("</script>");
}//if
}//try
catch(Exception e)
{out.print(e);}
%>
</body>
</html>
----------------------------------------------------------------------------
upphoto.jsp对图片进行存入数据库操作。注意需要将得到的文件的路径改变一下格式:c:\d\e.jpg改成c:\\d\e.jpg
------------------------------showphoto.jsp---------------------------------
<%@ page contentType="text/html;charset=gb2312"%>
<html>
<head>
<title>显示图片</title>
</head>
<body>
<%
String id=request.getParameter("vid");
%>
<table>
<tr>
<td colspan="3"> <img border="1" src=">
</tr>
</table>
</body>
</html>
---------------------------------------------------------------------------
showphoto.jsp的这句是关键:src=">",它说明调用了一个Servlet,这个Servlet的名字叫photo,而且需要给它传一个值(id)。下面看这个Servlet的代码:
---------------------------PhotoServlet.java------------------------------
package ringz.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.lang.*;
import java.sql.*;
public class PhotoServlet extends HttpServlet//和javabean一样是“类”,所以类名同样要和文件名一致
{
private String CLASSFORNAME = "oracle.jdbc.driver.OracleDriver";
private String SERVANDDB = "jdbc:oracle:thin:name/password@ringz:1521:rock";
Connection con = null;
PreparedStatement psmt = null;
ResultSet rs = null;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
int id = Integer.parseInt(request.getParameter("id"));
try
{
Class.forName(CLASSFORNAME);
con=DriverManager.getConnection(SERVANDDB);
con.setAutoCommit(false);
String sql = "select * from clb where id="+id;
psmt = con.prepareStatement(sql);
rs = psmt.executeQuery();
if (rs.next())
{
Blob bb = rs.getBlob("blob");
InputStream instream = bb.getBinaryStream();
response.setContentType("image/*");
OutputStream outStream = response.getOutputStream();
byte[] bytes = new byte[1024];
int i=0;
while ( (i = instream.read(bytes)) != -1)
{
outStream.write(bytes, 0, i);
}
instream.close();
outStream.close();
outStream = null;
con.commit();
rs.close();
psmt.close();
con.close();
}//if
}//try
catch (Exception ex)
{}
}//doGet
}
--------------------------------------------------------------------------
关于Servlet的知识请详细参阅有关参考书。下面说编译的事情:Servlet像javabean一样需要编译成.class文件,编译方法也和javabean一样。但是我在编译的时候发现出了错误,错误提示如下:
package javax.servlet does not exist
import javax.servlet.*;
^
我分析是少了什么东西造成的,于是在网上查资料,最后终于得出原因:环境变量里没有指出servlet相关的包的位置。解决办法:将原来的环境变量里的classpath添加一条:d:\j2sdk1.4.2_07\lib\servlet.jar;
以我的为例,这是原来的:
classpath——.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;
这是修改后的:
classpath——.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\servlet.jar;
现在顺利的编译出了.class文件,但是同样有问题:文件放在哪?和使用javabean时一样,放在根目录e:\MyJsp下的WEB-INF里,并且可以使用自己的包,比如我的在:e:\MyJsp\WEB-INF\ringz\servlet下。接下来还有一个工作:给这个servlet进行“注册”:在WEB-INF下建一个web.xml文件,内容大致如下:
----------------------------------web.xml---------------------------------------
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>RingZ's Station</display-name>
<description>
RingZ's JSP
</description>
<servlet>
<servlet-name>PhotoServlet</servlet-name>
<display-name>Servlet</display-name>
<servlet-class>ringz.servlet.PhotoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PhotoServlet</servlet-name>
<url-pattern>/photo</url-pattern>
</servlet-mapping>
</web-app>
---------------------------------------------------------------------------
其中最重要的是<servlet-name>、<servlet-class>和<url-pattern>,如果你有其他的servlet也需要在这里“注册”一下。关于servlet的配置还有很多其他的内容,比如初始化参数、优先级、映射等等,请学习其他专业资料。顺便说一下:你可以到D:\Tomcat 5.0\conf里看看那里那个web.xml文件,我最开始的时候曾经把它复制到我的WEB-INF下,然后在里面添加了关于PhotoServlet的内容,结果Tomcat服务出错,我猜想是里边的一些内容发生的冲突,但具体是哪些我不清楚,也没有有研究。
现在,配置完毕,可以使用了。
结语
对实例的学习进行到现在,我已经对Jsp和Java有了初步的了解了,我知道是时候了——该回头找些书看看理论部分了,否则我永远也成不了一个程序员。
学习还要继续,本文就先到这里,假如大家认为我这个东西还有那么一点用处的话,我会把以后的阶段相继写出来;如果没有必要,那就算了吧:)。
请大家指教,扔玉——为了本民族的软件事业。
续 第九章 异地文件上传到服务器数据库内
上面的第八章里简单介绍了如何把文件上传到数据库的BLOB字段中,但是它并非严格意义上的“上传”,它有一个局限:只能把服务器本地的文件存入数据库,那么如何把异地的文件存到服务器的数据库内呢?
通常我们需要一个实现异地上传的组件(相当于别人写好的一个可以完成一定功能的类包),一般简单而且常用的是SmartUpload。首先我们使用SmartUpload把文件上传到服务器的某个目录下,然后我们得到服务器目录中这个文件的路径,使用这个路径通过第八章的方法把文件存入数据库的BLOB字段中,最后,删除服务器目录中的文件。
使用SmartUpload的时候需要注意几个地方:
1、把jspsmartupload.jar这个文件放到web应用程序的根目录(e:\MyJsp\)下的WEB-INF\lib下;
2、表单<form>中加入encType="multipart/form-data"属性;
3、使用SmartUpload的类的文件需要加上:<%@ page import="com.jspsmart.upload.*" %>;
4、由于提交信息的页面里表单里有encType="multipart/form-data"这条属性(这个属性使该表单提交的数据都是二进制形式的),它导致接收信息的页面无法通过request.getParameter()来获取信息[用request.getParameter()得到的全都是NULL],必须使用SmartUpload提供的方法来解决这个问题——getRequest().getParameter("textfield")
下面看一个实例,这个实例是把表单提交的所有信息都保存到数据库,如果没有上传图片文件的话,就保存其他的所有信息:
提交信息的页面:
----------------------------------------------------SmartUpload.htm------------------------------------------------------------
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>SmartUpload上传,注意看SmartUpload.jsp的代码</title>
</head>
<script language="JavaScript">
function get_file(val) {//图片预览功能的实现
if ( val.search(/\.bmp|\.jpg|\.gif/i) >=0 ) {
form1.imgPrev.src = 'file://'+val;
form1.imgPrev.style.display='';
}
else {
form1.imgPrev.style.display='none';
}
}
</script>
<body leftmargin="0" topmargin="30" topmargin="30" onload="getonlinenumextend(),loadrun()" >
<form name="form1" method="post" encType="multipart/form-data"action="SmartUpload.jsp">
<!--
使用SmartUpload组件上传,<form>里必须加一个encType="multipart/form-data"属性
-->
<table width="760" border="0" align="center" cellpadding="0" cellspacing="0" class="textbox-border1">
<tr>
<td width="109" height="27" align="right">图片预览:</td>
<td height="190"> <IMG alt="preview" src="" name="imgPrev" style='display:none'width="150" height="180"/></td>
</tr>
<tr>
<td width="109" height="27" align="right">图片名称:</td>
<td colspan="3"> <input type="text" name="textfield"></td>
</tr>
<tr>
<td width="109" height="27" align="right">上传图片:</td>
<td colspan="3"> <input name="filepath" type="file" class="td-border" size="69" onpropertychange="get_file(this.value)"></td>
</tr>
<tr>
<td height="30" colspan="4" align="right"><div align="center">
<input type="submit" name="Submit" value="提交">
</div></td>
</tr>
</table>
</form>
</body>
</html>
------------------------------------------------------------------------------------------------------------------------------
接收信息并保存的页面,把图片名改为temp另存到服务器硬盘目录中,然后保存到数据库(这时删除硬盘目录中生成的图片),保存图片完毕之后调用显示图片的servlet(见第八章)显示数据库里刚刚上传的那张图片:
----------------------------------------------------SmartUpload.jsp------------------------------------------------------------
<%@ page contentType="text/html; charset=gb2312" language="java" errorPage="" %>
<%@ page import="java.sql.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<%@ page import="oracle.jdbc.driver.OracleDriver"%>
<%@ page import="java.lang.*"%>
<%@ page import="com.jspsmart.upload.*" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>无标题文档</title>
</head>
<body>
<%
request.setCharacterEncoding("gb2312");
String CLASSFORNAME="oracle.jdbc.driver.OracleDriver";
String SERVANDDB="jdbc:oracle:thin:name/password@ringz:1521:rock";
boolean flag=false;
//准备使用上传组件SmartUpload:
SmartUpload mySmartUpload =new SmartUpload();//实例化
mySmartUpload.initialize(pageContext);//初始化
String saveAsUrl="";//上传后文件在服务器中的完全路径
String url="SmartUploadFile/photo/";//*************应保证在根目录(放有jspsmartupload.jar的WEB-INF所在目录)中有此目录的存在
mySmartUpload.setMaxFileSize(10 * 1024*1024);//设置上传文件的最大值为10M
try {
mySmartUpload.setAllowedFilesList("jpg,gif,bmp");//只允许上载此类文件
mySmartUpload.upload();
}
catch (Exception e){
%>
<SCRIPT language=javascript>
alert("只允许上传.jpg、.gif和.bmp类型图片文件");
window.location.href='SmartUpload.htm';
</script>
<%
}
com.jspsmart.upload.File myFile = mySmartUpload.getFiles().getFile(0);//得到文件
//得到表单提交的“file”类之外的信息
//无法使用request.getParameter(),因为表单提交的时候<form>里有encType="multipart/form-data"属性,
//这个属性使表单提交的数据都是二进制形式的,request.getParameter()无法捕获,所以需要使用SmartUpload的getRequest().getParameter()来实现。
String title = mySmartUpload.getRequest().getParameter("textfield");
//将图片文件存入服务器目录中:
if (!myFile.isMissing()){
String ext= myFile.getFileExt(); //取得后缀名
String saveurl="";
saveurl=url+"temp."+ext;
//假如上传的文件是.jpg文件,则saveurl=SmartUploadFile/photo/temp.jpg
myFile.saveAs(saveurl,mySmartUpload.SAVE_VIRTUAL); //文件被存放至Web应用程序的根目录下SmartUploadFile文件夹内,文件名改为temp加后缀名
saveAsUrl=request.getRealPath("/")+saveurl;//另存后的文件的完全路径和名称,后面通过该地址取数据存入数据库
//假如上传的文件是.jpg文件,则saveAsUrl=e:\MyJsp\SmartUploadFile/photo/temp.jpg,文件被保存在此
flag=true;//这个标志为真表示有图片文件上传
//out.println(saveAsUrl);
}//if
//上传文件完毕。
//将文件之外的数据保存到数据库中:
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
Class.forName(CLASSFORNAME);
con=DriverManager.getConnection(SERVANDDB);
con.setAutoCommit(false);
String sql="insert into uptest(text) values('"+title+"')";
//out.println(sql+"<br>");
pstmt=con.prepareStatement(sql);
pstmt.executeUpdate();
//信息保存到数据库完毕。
//二、如有图片文件,把硬盘中的图片文件存入数据库中(名称为photo的blob字段):
if (flag){
pstmt=null;
sql="update uptest set photo=empty_blob() where text="+title;
pstmt=con.prepareStatement(sql);
pstmt.executeUpdate();
pstmt=null;
sql="select * from uptest where text='"+title+"' for update";
pstmt=con.prepareStatement(sql);//查找刚刚添加的那条记录
rs=pstmt.executeQuery();
if (rs.next()){
oracle.sql.BLOB osb = (oracle.sql.BLOB) rs.getBlob("photo");//
//到数据库的输出流
OutputStream outStream = osb.getBinaryOutputStream();
//这里用一个文件模拟输入流
java.io.File file = new java.io.File(saveAsUrl);//!!!!!!!!!!!!!!!!!!!和com.jspsmart.upload.File区别开,读入上传的图片
InputStream inStream = new FileInputStream(file);
//将输入流写到输出流
byte[] b = new byte[osb.getBufferSize()];
int len = 0;
while ( (len = inStream.read(b)) != -1){
outStream.write(b, 0, len);
}
//依次关闭(注意顺序)
inStream.close();
outStream.flush();
outStream.close();
file.delete(); //删除生成服务器目录中生成的临时文件
}//if
rs.close();
}//if
//图片存入数据库完成。
con.commit();
pstmt.close();
con.close();
out.print("<script>");
out.print("alert('信息录入成功!');");
out.print("</script>");
}//try
catch(Exception e)
{out.println(e);}
%>
<!--显示数据库里刚刚保存进去的图片-->
<img border="1" src=">" width="800" height="600">
</body>
</html>
------------------------------------------------------------------------------------------------------------------------------
关于smartupload的使用详情请到网上查找更加具体的介绍。
续 第十章 一个最简单的model 2实现
最初在我打算自学Jsp的时候我就知道MVC模式了,并且知道这种模式的好处,但是我一直不知道应该怎么去实现,因为第一找不到代码,第二那时对代码的理解力还不如现在。(有关MVC、Model 1、Model 2这些概念我在这里就不介绍了,请查阅互联网,可以搜到很多)
至此为止,我对Model1已经有了基本的掌握了,一些不太复杂的东西我都已经可以写出来了,但是一直以来有两个问题困扰着我:代码的重用和移植(我这里说的移植不是指在不同的操作系统平台移植,指的就是更换Web服务器和数据库服务器)——我这个时候写的代码如果更换了数据库服务器的话需要修改的地方很多,一旦不小心忘记了修改一个地方,那么就不知道会在什么时候什么地方程序发生“罢工”了——这个时候我打算学习Model 2的写法,期望能够最大限度的解决此类问题。
很多人喜欢用HelloWorld作为第一个程序,而我对Login情有独钟,因为Login包含了一个在程序中普遍出现的步骤——连接数据库。这个时候我打算拿Login开刀了,使用Model 2的写法替代之前的Model 1写法。万事开头难,面对一直让我一筹莫展的几个实例代码,那天下午,突然,我看懂了,也明白——原来这就是Model 2的实现啊。
我这个时候的实现是:使用一个servlet(这种叫法可能并不准确,我们知道实际上一个.jsp文件就是一个servlet。这里说的servlet是指需要在web.xml里进行配置的extends HttpServlet的类,本文以后所指的都是这个)接收请求并进行业务处理,将处理的结果传给javabean,然后视图转向到用来显示结果的.jsp文件,该文件从javabean里取值并显示。
注意,上面说的是一种不规范的Model 2实现,一个最简单的原因就是——每处理一个特定业务就需要写一个servlet,对应的就需要在web.xml里写上这个servlet的相关配置信息。当你的这个系统里有几百个业务的话(我猜测一个大的系统的话,有几百个业务要处理应该是一件正常的事情),你试想一下吧……
在朋友的指点下,并简单了解了一下struts这个框架后,我确定我应该这样实现:一个(一个就够了)作为“控制”的servlet来决定把接收到的请求转发给谁,然后由接收转发请求的类(我们通常称之为action)进行业务处理,处理结果传给javabean,派发视图到显示页面(.jsp文件),页面从javabean中取值并显示。(我并没有学习和使用struts框架,这是因为我学习java web主要是一种爱好,正因为是爱好所以我打算用比较原始的方式自己编写代码解决各种问题)
下面我们具体看看这个例子的代码吧:
提交请求的文件的主要代码:
--------------------------------------------login.htm-----------------------------------------------------------------
<form name="form1" method="post" action="Login.do">
<table width="300" border="0" align="center">
<tr>
<td colspan="2"><div align="center">输入登录信息</div></td>
</tr>
<tr>
<td>用户名:</td>
<td>
<input name="textfield1" type="text" id="textfield1"> </td>
</tr>
<tr>
<td>密 码:</td>
<td><input type="password" name="textfield2"></td>
</tr>
<tr>
<td colspan="2"><div align="center">
<input type="submit" name="Submit" value="提交">
</div></td>
</tr>
</table>
</form>
-----------------------------------------------------------------------------------------------------------------
action="Login.do"这个“.do”是什么文件呢?其实它并不是什么文件,请看web.xml文件里相关的这部分:
<servlet>
<servlet-name>controlservlet</servlet-name>
<servlet-class>per.WBF.servlets.ControlServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>controlservlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
从这些代码我们可以看出:只要是.do形式的请求(不是.do结尾的请求,例如show.do?id=123同样是合法的)就把请求给per.WBF.servlets.ControlServlet来处理。
值得注意的一个小细节是:我们知道<url-pattern>和</url-pattern>之间的内容应该由“/”开始,但是这里你绝对不可能写成这样:<url-pattern>/*.do</url-pattern>,因为这个时候“/*.do”代表的是一个以“/”开头的路径,显然路径名(文件、文件夹)不可能包含“/”符号,你一定没见过哪个文件夹叫“/file”吧?没明白?仔细想想^_^,这个时失误折磨了我两天啊,呵呵,就是因为一个粗心+眼神不好使造成的。
接着看per.WBF.servlets.ControlServlet这个类,或者说这个servlet:
---------------------------------------------ControlServlet.java------------------------------------------------------
package per.WBF.servlets;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import per.WBF.classes.*;
import per.WBF.actions.*;
public class ControlServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("gb2312");
String path = (String) request.getRequestURI();
String actionString = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
try {
//增加单位
if (actionString.equals("login"))
new LoginAction().excute(request, response);
else if (actionString.equals("SaveInfo"))
new SaveInfoAction().excute(request, response);
// ……
}
catch (ActionException ex) {
}
catch (Exception ex) {
}
}
public void destroy() {
}
}
-----------------------------------------------------------------------------------------------------------------
从这段代码可以知道:action="login.do"的话,请求最终由LoginAction这个类的excute方法来处理。下面写出LoginAction.java之前,先把ControlServlet里面涉及到的ActionException的代码给出:
----------------------------------------------ActionException.java---------------------------------------------------------
package per.WBF.classes;
public class ActionException extends Exception {
public ActionException(Exception e) {
super(e.getMessage());
System.out.println(e.getMessage());
}
}
-----------------------------------------------------------------------------------------------------------------
再来看loginAction.java:
--------------------------------------------LoginAction.java----------------------------------------------------------
package per.WBF.actions;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.sql.*;
public class Action{
private Connection con;
public void excute(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException,ActionException,Exception{
//调用类DataBaseCon的方法getCon获得数据库连接
this.con=DataBaseCon.getCon();
boolean succeed = false;//登录是否成功的标志
String msg=null;//登录是否成功的信息
String username = request.getParameter("textfield1");
String password = request.getParameter("textfield2");
String sql="select usern,passw from mvctest where usern='"+username+"' and passw='"+password+"'";//注意这条语句,它是一条有严重漏洞的语句,进行登陆验证时千万不能使用这样的语句!
PreparedStatement pstmt=con.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
msg = "您输入的信息正确,登录成功!";
succeed = true;
}
else
msg = "您输入的信息错误,请重新登录!";
//关闭数据库连接
rs.close();
stmt.close();
try {
if(con!=null && !con.isClosed())
con.close();
}
catch (Exception e) {
System.out.println("关闭数据库连接时出现异常!");
e.printStackTrace();
}
//向MessageBean传值
MessageBean mb = new MessageBean();
mb.setMessage(msg);
mb.setSucceed(succeed);
request.setAttribute("mb",mb);
//派发视图到目标:login.jsp
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/login.jsp");
requestDispatcher.forward(request,response);
}
}
-----------------------------------------------------------------------------------------------------------------
(这个LoginAction实际上有个问题:验证信息是保存在MessageBean这个javabean中的,而这个MessageBean的生命周期却是request,这通常是不正确的,其实它的生命周期应该是session)
LoginAction和servlet的最大区别就是它没有extends HttpServlet,它实现了serlet接收请求并进行操作的功能,但是它不需要在web.xml里进行配置。
LoginAction是通过工具类DataBaseCon来建立数据库连接的,下面是DataBaseCon.java的代码:
------------------------------------------------DataBaseCon.java----------------------------------------------------------
//连接数据库专用类
package per.WBF.classes;
import java.sql.*;
public class DataBaseCon {
//这是一个静态方法
public static Connection getCon() {
Connection con=null;
String CLASSFORNAME="oracle.jdbc.driver.OracleDriver";
String SERVANDDB="jdbc:oracle:thin:name/password@172.16.20.59:1521:mysid";
try {
Class.forName(CLASSFORNAME);
con=DriverManager.getConnection(SERVANDDB);
}
catch(ClassNotFoundException cnfe) {
System.out.println("建立数据库连接时出现CNF异常!"+cnfe);
}
catch(SQLException sqle) {
System.out.println("建立数据库连接时出现SQL异常!"+sqle);
}
catch(Exception e) {
System.out.println("建立数据库连接时出现其他异常!"+e);
}
return con;
}
}
-----------------------------------------------------------------------------------------------------------------
我的朋友建议我尽可能的把业务逻辑写在action中,而不要写在javabean中。我遵从这个原则,所以我现在基本不在javabean内写任何逻辑相关的语句,下面是这个MessageBean的代码:
----------------------------------------------MessageBean.java-----------------------------------------------------------
//保存返回信息
package per.WBF.javabeans;
public class MessageBean {
private String message;
private String url;
private boolean succeed;
public void setMessage(String msg) {
this.message=msg;
}
public String getMessage() {
return this.message;
}
public void setUrl(String url) {
this.url=url;
}
public String getUrl() {
return this.url;
}
public void setSucceed(boolean succeed) {
this.succeed=succeed;
}
public boolean isSucceed() {
return this.succeed;
}
}
-----------------------------------------------------------------------------------------------------------------
实际上你可以发现,MessageBean里有一个String url,它的两个方法都未曾被LoginAction.java使用过。没关系,这表示这个同样可以被别的action使用。
对于最后的显示页面,只写下下面的部分主要代码:
-------------------------------------------login.jsp---------------------------------------------------------
<jsp:useBean id="mb" scope="request" class="model2_class.MessageBean"/>
<%
String message;
boolean succeed=false;
message=mb.getMessage();
succeed=mb.isSucceed();
if (message!=null) out.println(message);
%>
-----------------------------------------------------------------------------------------------------------------
怎么?实际上boolean succeed并没有被使用?没错。但是你打算把login.htm的内容也写在login.jsp中,它就有用了。或者,在你做真正的登陆功能的时候,它就有用了。
怎么样,我们把一个很简单的一个.jsp文件就可以搞定的功能分成这么多块来实现,是不是很没劲?但是,第一,随着你不断深入你就会发现这样做的好处;第二,这只是介绍一下Model 2的具体实现的一个小例子——在我们很多小的应用上其实没有太大必要这么干,反正随你喜好了。