本文示范了如何用一个java servlet、一个jsp页面和一个静态java类构造出一个sql网关应用。利用这个应用,你可以直接在浏览器内执行sql命令,浏览器将把sql命令提交给远程服务器上的数据库系统,然后返回结果。
如果你正在使用isp(internet service provider)提供的数据库,可能已经熟悉sql网关应用的概念了。有的isp会提供一个操作数据库的html页面,就象本文提供的网关应用一样。如果isp没有提供这样的界面,你可以把本文的程序上载到服务器,以后要访问isp服务器上的数据库就很方便了。
sql网关应用不仅可以用于开发过程,而且还可以直接提供给比较熟悉系统的最终用户使用。当然,允许最终用户直接在数据库上运行sql命令会带来一些安全隐患,应当慎重考虑。
本文要求读者具备一定的java、servlet、jsp和数据库的基础知识,如果要运行本文的程序,还要有一个servlet/jsp服务器和数据库服务器。在下面的说明中,我们要使用的是tomcat 4.0和mysql,但它应该也能在其他jsp/servlet容器中运行;如果你要改用mysql之外的其他数据库,只要提供一个适当的驱动程序,然后修改数据库连接字符串就可以了。
一、用户界面
图1就是本文sql网关的用户界面。在这个界面中,sql网关已经执行了一条sql命令并返回了结果。
图1:sql网关的用户界面
从图1可以看出,页面底部的一条信息显示出最近执行的sql命令影响的行数。如果sql命令是一个select语句,当select语句执行成功,页面底部将用html表格显示出查询结果,如图二所示。
图2:html表格显示出查询结果集
当然,如果sql命令执行失败,sql网关将返回异常信息。
二、设计jsp页面
在jsp页面中,我们首先放入一个scriptlet,它的功能是从session对象提取两个属性:
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<%
string sqlstatement=(string)session.getattribute("sqlstatement");
if (sqlstatement == null) sqlstatement = "";
string message = (string) session.getattribute("message");
if (message == null) message = "";
%>
第一个属性sqlstatement 表示sql命令字符串,第二个属性message 是包含结果信息的字符串。如果这两个属性的值是null,则表示它们尚未被设置,我们把sqlstatement和message变量设置成空字符串。
jsp页面还有一个html表单,html表单包含一个文本区域(textarea)和一个“执行”按钮。
<form action="../servlet/test.sqlgatewayservlet" method="post">
<b>sql命令:</b><br>
<textarea name="sqlstatement" cols=60 rows=8>
<%=sqlstatement%></textarea><br>
<br>
<input type="submit" value="执行">
</form>
表单中的文本区域用来输入sql命令。我们将sqlstatement变量的值作为文本区域的默认内容,文本区域的大小是宽60字符、高8行。当jsp页面第一次运行时,这个文本区域的内容为空。如果用户点击文本区域下面的“执行”按钮,jsp页面把表单内容提交给sqlgatewayservlet(稍后再详细介绍)。
jsp页面底部的表格显示出message字符串的内容。如前所述,message的内容是运行sql命令的结果。
<b>sql命令执行结果:</b><br>
<table cellpadding="5" border="1">
<%=message%>
</table>
三、编写servlet
sqlgatewayservlet首先导入java.sql包以便使用jdbc类。另外,它还要声明一个connection对象,以便servlet之内的所有方法都可以使用数据库连接。
package test;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class sqlgatewayservlet extends httpservlet{
private connection connection;
当servlet引擎开始运行这个servlet,servlet的init方法就打开一个数据库连接:
public void init() throws servletexception{
try{
class.forname("org.gjt.mm.mysql.driver");
string dburl = "jdbc:mysql://localhost/murach";
string username = "root";
string password = "";
connection = drivermanager.getconnection
(dburl, username, password);
}
catch(classnotfoundexception e){
system.out.println("找不到数据库驱动程序.");
}
catch(sqlexception e){
system.out.println("不能打开数据库连接: "
+ e.getmessage());
}
}
在这个例子中,servlet利用一个mysql数据库的驱动程序打开murach数据库的连接,数据库和servlet运行在同一个服务器上。此外,servlet使用mysql的默认用户名字root,密码为空。不过,你可以修改这里的代码,只要有适当的驱动程序,就可以让servlet连接到任何服务器上的任何数据库(有关mysql数据库的更多信息,请参见www.mysql.com)。
servlet引擎关闭servlet之前,调用destroy方法关闭数据库连接,释放连接资源:
public void destroy() {
try{
connection.close();
}
catch(sqlexception e){
system.out.println("不能关闭数据库连接: " + e.getmessage());
}
}
前面介绍的jsp页面要调用servlet的dopost方法,dopost方法调用doget方法:
public void dopost(httpservletrequest request,
httpservletresponse response)
throws ioexception, servletexception{
doget(request, response);
}
在doget方法之中,第一个语句首先获取用户在jsp页面中输入的sql命令,第二个语句声明message变量:
public void doget(httpservletrequest request,
httpservletresponse response)
throws ioexception, servletexception{
string sqlstatement = request.getparameter("sqlstatement");
string message = "";
然后,在try块之内,第一个语句利用connection对象创建statement对象,接下来的两个语句利用string对象的trim方法和substring方法返回用户输入的sql命令的前六个字符。
try{
statement statement = connection.createstatement();
sqlstatement = sqlstatement.trim();
string sqltype = sqlstatement.substring(0, 6);
如果sql命令的前六个字符是“select”,则利用statement的executequery方法执行sql语句,获得一个resultset对象,把这个对象传递给sqlutil类(稍后详细说明)的gethtmlrows方法,gethtmlrows方法将把记录集中的记录格式化成html表格并返回。
if (sqltype.equalsignorecase("select")){
resultset resultset = statement.executequery(sqlstatement);
// 构造一个string,其中包含html表格形式的结果集数据
message = sqlutil.gethtmlrows(resultset);
}
如果sql语句的前六个字符不是“select”,则我们调用statement对象的executeupdate方法,executeupdate方法返回当前操作影响的行数——如果这个数字是0,则该sql命令是一个ddl命令,例如drop table或create table等;否则,则表明sql命令可能是dml命令,如insert、update或delete命令。无论是哪一种sql命令,我们都把message变量设置成相应的消息。
else
{
int i = statement.executeupdate(sqlstatement);
if (i == 0) // 这是一个ddl命令
message =
"<tr><td>" +
"命令执行成功." +
"</td></tr>";
else // 这是一个insert、update或delete命令
message =
"<tr><td>" +
"sql命令执行成功。<br>" +
"已更改" + i + " 行。" +
"</td></tr>";
}
statement.close();
}
如果try块里面的任何一个语句抛出一个sqlexception,catch块就设置message变量,使其包含有关该sqlexception的信息。例如,如果在表单中输入的sql命令语法错误,下面设置的message变量值将帮助你排解错误。
catch(sqlexception e){
message = "<tr><td>执行sql命令时遇到错误:<br>"
+ e.getmessage() + "</tr></td>";
}
在catch块之后,接下来的三个语句获得session对象,把sqlstatement和message变量设置为session的属性:
httpsession session = request.getsession();
session.setattribute("message", message);
session.setattribute("sqlstatement", sqlstatement);
接下来,最后两个语句创建一个requestdispatcher,并转发request和response对象给前文介绍的jsp页面:
requestdispatcher dispatcher =
getservletcontext().getrequestdispatcher(
"/sql/sql_gateway.jsp");
dispatcher.forward(request, response);
四、编写工具类
下面来看看工具类sqlutil的代码:
package test;
import java.sql.*;
public class sqlutil{
sqlutil类包含一个gethtmlrows静态方法,前面的servlet正是通过调用该方法将结果集格式化成html表格。gethtmlrows的输入参数是一个resultset对象,其返回值是一个string对象,这个string对象的内容是记录集的所有列表题和行的html代码。为了构造出这样一个string对象,gethtmlrows声明了一个名为htmlrows的stringbuffer对象,然后在方法执行过程中向这个stringbuffer对象追加数据。在gethtmlrows方法的末尾,我们用tostring方法将stringbuffer的内容转换成string,最后将这个string返回给servlet:
public static synchronized string gethtmlrows(resultset results)
throws sqlexception{
stringbuffer htmlrows = new stringbuffer();
resultsetmetadata metadata = results.getmetadata();
int columncount = metadata.getcolumncount();
// 将记录集中列的名称作为html表格列的标题
htmlrows.append("<tr>");
for (int i = 1; i <= columncount; i++)
htmlrows.append("<td><b>" + metadata.getcolumnname(i) + "</td>");
htmlrows.append("</tr>");
// 对于结果集中的每一行…
while (results.next()){
htmlrows.append("<tr>");
// 将该行中的每一个列转换成一个表格单元
for (int i = 1; i <= columncount; i++)
htmlrows.append("<td>" + results.getstring(i) + "</td>");
}
htmlrows.append("</tr>");
return htmlrows.tostring();
}
为了获得记录集对象的列标题,gethtmlrows方法利用resultset的getmetadata方法来创建一个resultsetmetadata对象,resultsetmetadata对象包含了有关记录集的描述信息,例如列的数量、列的名称可以分别调用resultsetmetadata的getcolumncount和getcolumnname方法获得。
为了提取记录集的数据,gethtmlrows方法利用一个嵌套的循环,即while循环里面嵌套的for循环,来提取每一个行里面每一个列的值。在循环之内,我们用记录集的getstring方法来获取各个字段的值,不管字段值原来的类型是什么,getstring方法都会将它转换成string。
请注意这个方法的声明中带有synchronized关键词,这是为了避免两个或两个以上的servlet线程同时执行该方法。
