正文
将可复用程序进行组件化是J2EE的核心思想之一。组件表示的是开发和发布的单元,从组件用户的角度来看,组件可以在一个应用程序中使用多次,并在多个位置使用,在JAVA爱好者中一个广为人知的组件实例就是免费的商业组件SmartUpload。这里向大家介绍的是个人在实践中为了简化工作而制作的一个简单的自动插入数据的java组件,目的是将个人编制组件时的思考方法和技术实践拿出来与大家交流切磋。
需求:
在J2EE项目开发中,实现数据插入是一个基本的功能,在一些大中型项目中往往有几十上百的数据单表插入的操作,对这一部分工作实现组件化可以大幅减轻开发工作量,提高工作效率。
分析:
在不考虑EJB组件的J2EE(b/s)技术中实现一个数据行的插入一般需要以下技术和实现流程:
用户在网页(HTM或JSP)表单中填写将要插入到数据库中数据,提交表单; 由系统指定的jsp或servlet处理提交的表单,组装插入数据的SQL语句,通过系统数据接口(JDBC或其他方式)建立数据库连接,执行插入数据的SQL操作,将从页面表单中获取的输入数据插入到指定的数据表中,完成数据插入。 简单分析一下,在上述第一步的操作中通常都是不确定的,在完成设计之前是不知道需要将怎样的数据插入到哪一张数据表中的,而在第二步中只有两个半细节是不确定的:
组装的数据插入SQL语句将随表单中的页面元素变化而变化; 插入数据的数据表不确定; 系统的数据库接口不确定(但对于一个已经完成设计的系统来说,这是确定的,因此姑且把这一条件算作半个不确定因素)。 因此如果能将上述a、b两个细节实现动态组装(暂时不考虑c细节)那就可以编制出我们自己的通用数据插入组件。
技术实现:
完成初步分析后,我们来看看Java中能为这一需求提供技术实现的方法:HttpServletRequest接口是对Http协议请求消息的包装,同时还继承了ServletRequest接口中定义的一些获取表单数据的基本方法--凡是实现了HttpServletRequest接口的对象,都可以通过调用这些方法获得客户端提交上来的表单数据,在这些方法中,对于我们实现通用数据插入组件有用的的方法之一是getParameterNames(),这一方法返回一个枚举对象(Enumeration),其中包含有所有提交的表单元素的名字(属性name中的值)。我们将以此方法作为开发数据自动插入组件的基础。下面是实现这一组件的主要代码:
1、提交数据的网页,需要按照以下规则进行页面元素命名:
表单中凡是不用于组装数据插入SQL语句的页面元素都加上"_submit"后缀;
用于插入数据的数据表名放到名为"tabname_Submit"的页面元素中;
完成数据插入操作后的系统提示页面名(如显示"你已成功完成数据提交!"信息)放到名为" sendRedirect_Submit"的页面元素中;
用于插入数值的页面元素都加上"_NWWW"后缀(说明一下,在数据插入中主要是分两类数据操作:字符型(包括日期型)与数值型,因此对其加以区分,选用"_NWWW"后缀没有特别的意义,只是考虑这么难看的后缀很少被引用,避免命名冲突,嘿嘿……);
表单中凡是用以组装数据插入SQL语句的页面元素的名字都必须与数据表中的相关字段同名(这也意味着数据表中数值型的字段名都应加上"_NWWW"后缀)。 下面是一张简单的web中数据表单的代码样例:
<form name="form1" action="AutoInsertServlet">
<table align="center">
<tr valign="baseline">
<td nowrap align="right">Name:</td>
<td>
//这里是字符型的数据
<input type="text" name="name" value="" size="32">
</td>
</tr>
<tr valign="baseline">
<td nowrap align="right">A_NWWW:</td>
<td>
//这里是数值型的数据
<input type="text" name="a_NWWW" value="" size="32">
</td>
</tr>
<tr valign="baseline">
<td nowrap align="right"> </td>
<td> </td>
</tr>
</table>
<p align="center">
//这里是无关的页面元素,加" _Submit"后缀
<input type="submit" name="Submit_Submit" value="提 交" >
//这里是放置的插入表名的页面元素,必须名为"tabname_Submit"
<input type="hidden" name="tabname_Submit" value="smartin">
//这里是放置插入后系统提示页面名的页面元素,必须名为"sendRedirect_Submit"
<input type="hidden" name="sendRedirect_Submit" value="saved.jsp">
</p>
</form>
对应的,在数据表"smartin"中有两个字段:字符型字段"name"和数值型字段"a_NWWW"。
下面是通用自动插入数据组件AutoInsertServlet中的主要代码:
...
public void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
//获取数据表名
String mytable=(String)request.getParameter("tabname_Submit");
//获取系统提示页面名
String myjsp=(String)request.getParameter("sendRedirect_Submit");
//用于放置SQL语句的变量
String mysql="INSERT INTO "+mytable+" (";
String myvalue=") VALUES (";
//使用getParameterNames()方法获取提交表单中的元素名
Enumeration ctrlNames=request.getParameterNames();
String itsValue=null;
int i=0;
//数据间连接符号","的动态组装
String u=null;
while(ctrlNames.hasMoreElements()){
if(i==0){
u="";
}else{
u=",";
}
String itsName=(String)ctrlNames.nextElement();
//如果不是专用传递页面元素(无"_Submit"后缀),就用于组装SQL语句
if(itsName.lastIndexOf("_Submit")==-1){
//组装字段名
mysql=mysql+u+itsName;
//获取字段值(同时进行中文处理)
itsValue= new String
(request.getParameter(itsName).getBytes("ISO-8859-1"),"GBK");
//如果是字符型(无"_NWWW"后缀),就在组装语句时前后加"'"
if(itsName.lastIndexOf("_NWWW")==-1){
itsValue="'"+itsValue+"'";
}
//组装字段实际插入值
myvalue= myvalue+u+itsValue;
i++;
}
}
//完成完整的插入数据SQL语句的动态组装
mysql=mysql+myvalue+")";
//执行专用数据库操作方法
smartInsert(mysql);
//完成后跳转到指定的显示页面
response.sendRedirect(myjsp);
...
专用数据库操作方法smartInsert(这里是使用jdbc连接到本地的SQL Server2000数据库):
private void smartInsert(String mysql) {
String MYDRIVER = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
String MYUSERNAME = "sa";
String MYPASSWORD = "";
String MYSTRING = "jdbc:microsoft:sqlserver://localhost:1433;";
try{
Driver DriverPrepared1 =
(Driver)Class.forName(MYDRIVER).newInstance();
}catch(ClassNotFoundException ex)
{System.out.println("ClassNotFoundException"+ex);
}catch(InstantiationException ex)
{System.out.println("InstantiationException"+ex);
}catch(IllegalAccessException ex)
{System.out.println("illegalAccessException"+ex);
}
try{
Connection ConnPrepared1 =
DriverManager.getConnection(MYSTRING,MYUSERNAME,MYPASSWORD);
PreparedStatement Prepared1 =
ConnPrepared1.prepareStatement(mysql);
Prepared1.executeUpdate();
ConnPrepared1.close();
}catch(SQLException ex){System.out.println("SQLException"+ex);
}
}
至此,我们已经完成了一个通用自动插入数据组件的编制,它可以对符合我们定义的Web表单提交自动进行数据插入操作,但是还存在不足,主要有以下三点:
数据库接口部分没有提供灵活的方式,一旦改变数据库接口配置就需要重新编写部分代码(改写smartInsert()方法)。
数据字段名和表名暴露在View层的页面中,可能会带来安全问题;
只能进行单表插入操作; 提升打磨:
个人认为自制的组件其实就是开发人员自制的工具,如果本人是木匠,上面谈到的通用自动插入数据组件就算是一把刀,目前这把刀已经完成淬火、磨刃,砍点小树细枝还是不错的,但是包装还很粗糙,不大拿得出手,下面谈谈解决的方法。对于前述三个缺陷,第一个问题解决方法很容易,就是把"MYDRIVER"、"MYUSERNAME"、"MYPASSWORD"、"MYSTRING"四个变量的初始值放到一个指定的初始化位置中,使用时将其取出即可,实现这一方案主要有两种方法:
在web.xml中通过ServletContext对象的参数设置来设定四个变量的初始值,然后取出即可。以MYDRIVER为例,在web.xml中加入如下内容: <context-param>
<param-name>MYDRIVER</param-name>
<param-value>com.microsoft.jdbc.sqlserver.SQLServerDriver
</param-value>
</context-param>
注意:context-param标志必须放在web-app标志的开始位置,也就是说只能跟在<web-app>或是其后的<description></description>后面。 同时,将上述的AutoInsertServlet中方法smartInsert()里的代码:
String MYDRIVER = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
改写为:
ServletContext context=getServletContext();
String MYDRIVER = context.getInitParameter("MYDRIVER");
其它变量的处理方法相似,就不再赘述了。
使用专用的xml文件设置,四个变量的初始值,然后取出。 经上述处理后,我们的刀就算是有了刀鞘,但还不够锋利,对于第二个缺陷(一般在企业内部网和对数据要求不严的中小网站中可以不必太在乎这一问题),个人认为最好的方法是使用专用的命名编码规则,将取到的表单元素按一定规则重新命名后再组装成新的SQL语句。
对于第三个缺陷,要想实现多表操作可就麻烦多了,不仅代码和表单元素的编码规则要复杂很多,还要综合考虑数据操作的事务保护等问题,不过大家静下心来慢慢琢磨,是定有解决之道的,这里只是抛砖引玉,希望大家多多交流切磋。