第八章 基于JSP+JDBC+SQLServer开发的报价管理系统
8.1 项目背景
随着我国IT产业的迅速发展,电脑需求的规模越来越大,大型电脑采购都要采用招投标制,有多家企业竞争,很多工程招标金额达到几百万、数千万,中标状况的好坏对成套厂家的全年经营效益产生重大影响。随着国家对采购活动的日益规范,今后,通过工程招标进行产品销售的情况将成为主流。同时,由于国内外电脑的厂家数以千计,其产品更是五花八门,种类繁多,且更新换代很快,使其数量、规格难于统计,手工预算报价无论在时间上,还是在准确性上均越来越难以胜任;再加上招投标规定的实施,成套厂家迫切需要一种既迅速详细又可变灵活的预算手段来提高自己的市场竞争能力。
一次招标往往有数百台电脑,每种品牌的电脑都要生成详细的报价清单,总共需要形成上百张的表格,涉及许多计算,表格之间的数据还要协调一致,不能出错。若报价员手工做这样的报价,一般需要紧张工作几天时间才能完成,所以电脑报价工作量大,要求高。
为了市场竞争的需要,经常要对已有报价进行价格调整或者替换电脑配置,形成新的报价方案,必须很快完成,但用手工做是一个繁琐、费时的工作。
报价单或者标书的报价部分,最终需要输入计算机文件,打印出来提供给用户。手工形成的报价数据,人工录入计算机,既耗时又易出错。
8.2 业务概述
本系统提供网络环境下的电脑自动报价整体解决方案,系统应用目前流行的最新的Java技术和SQL Server 2000数据库,采用基于Web的B/S结构,专为电脑厂家报价及招投标量身打造,整合电脑信息,快速生成报价方案或报价标书,并快速方便的修改为多个报价方案。具有维护方便,操作简单,技术先进等优点。
8.3 系统特点
上面为读者讲述了系统的项目背景、业务概述,下面接着讲述本系统特点:
- 针对电脑厂家的报价和招投标工作而设计,使用户通过最少的输入选择就会产生准确,规范,美观的报价单,设备报价单,设备材料清单,分项报价单,报价规范书,极大地减轻工作量,提高企业形象。
- 基于Java平台和SQL Server 2000大型数据库,依靠完善可靠的网络体系,保证数据安全和应用系统的有效运行。
- 快速灵活的录入及查询功能。具有完备的电脑厂家库,型号库以及名称库等,可方便的查询,操作业务库。
- 创建结构化的报价方案库,可查阅或借用、参考以前的报价方案,促进网络环境下资源的共享、积累和继承。
- 完善的数据维护和安全管理机制。
8.4 用户界面
人性化用户界面设计,Web页面最上部为菜单条内容是一级菜单的名称,为页面整个宽度;高度方向上紧贴着一级菜单的是软件的Logo,为徽标图象及名称,靠左位置,而中部为当前执行的命令文字,右部根据执行的命令需要放置说明文字、命令按钮或联接。
Web页面左侧,也是菜单条,内容是当前执行的单条命令的二级菜单的名称。此条的右上角有一按钮,点击它可以显示或隐藏二级菜单。
Web页面的其它部分,则是执行具体命令的界面层。
8.5 系统功能
整个系统是由一台中心数据服务器和多台客户机通过网络互连而成,所有信息均存储在中心数据服务器中,客户机可通过浏览器来访问存储在服务器上的WEB页面,从而实现各种操作功能。而远程用户也可以拨号或专线方式,通过浏览器来远程访问存储在服务器上的WEB页面,取得相应需用数据。
系统采用模块化设计,包含功能:
1、新建报价工程
具有报价权限的用户(一般是报价员),执行新建报价工程命令,开始新的报价。
- 输入报价工程基本信息。 包括:报价单号、工程名称、呈送方(客户)单位信息、本单位信息、报价日期、有效日期。
- 确定计费方式、计费公式和参数。
- 输入报价工程产品分类名称。
- 输入每个产品分类的具体组成产品(电脑或其它),名称、型号、数量等。
- 每种电脑进行详细报价: 新建电脑,输入各类电脑的具体名称、型号、数量,调整价格。
可以一条条输入元件信息;也可以用以选择的电脑模板新建电脑的方式,插入一次方案、二次方案的方式快速输入电脑的组成配置。
2、查询报价工程
在当前用户具有访问权限的报价工程中,通过报价单号、工程名称、呈送方(客户)单位信息、报价日期、报价人等筛选条件,搜索出符合条件的报价工程。
打开报价工程、导入其它报价工程等操作都要执行此命令。
3、打开报价工程
以只读或者修改方式,打开查询报价工程结果中的一条。用户对被访问工程所具有的权限决定了打开报价工程方式。
- 只读方式打开后,可以查看其中的全部内容,可以另存为一其它名称的报价工程,可以保存电脑模板,可以保存一次、二次方案,可以生成总报价单、单台电脑明细报价、汇总统计、盈亏统计等所有统计报表。但不能修改任何内容。
- 修改方式打开后,除了以只读方式打开的全部功能,还能够进行各种修改。可以加入新的组成项目,替换配置,调整价格,生成新的报价方案(以相同报价单号、不同版本号方式记录)。
可以调整报价工程中产品分类下电脑或其它产品的排序顺序;在柜子中,也可以调整各类电脑配置的排序顺序。
8.6 系统设计和实现
报价管理系统的项目背景、业务概述、系统特点、用户界面和系统功能讲述完毕,下面讲述系统设计和实现,首先为大家讲述客户管理的设计和实现。
8.6.1 客户管理
首先为大家讲述报价管理系统的客户管理模块,本节从大多数开发人员的开发习惯着手,开始本章的开发之旅,下面为本节的开发步骤:
1. 建立Customer的数据传输对象(DTO)
2. 建立Customer基于主键的CustomerPk数据传输对象(DTO)
3. 根据Customer对象产生数据库表
4. 创建一个对Customer对象执行CRUD操作的DAO
5. 创建CustomerDaoException类
6. 建立访问数据库类-ResourceManager
7. 建立CustomerDao的实现类CustomerDaoImpl
8.6.1.1 建立Customer的数据传输对象(DTO)
我们要做的第一件事情就是建立一个需要持久化的对象,要在src/**/dto目录下建立一个简单的Customer对象,Customer类的图如图8-1所示:
图 8-1 Customer类的UML图
这个对象包括customercode、customercodeModified、customername、customernameModified、和phone属性,例如清单8-1所示:
光盘:Customer.java代码见光盘JSP/chapter08/src/com/relationinfo/quote/dto包中的Customer.java类。
清单8-1 Customer.java
package com.relationinfo.quote.dto;
import java.io.Serializable;
public class Customer implements Serializable
{
/**
*
*/
private static final long serialVersionUID = 4224332633725166867L;
/**
* customer 表列customercode.
*/
private String customercode;
/**
* 从数据库中查询判断数据库记录是否被修改.
*/
private boolean customercodeModified = false;
/**
* Method'equals'
*
* @param _other
* @return boolean
*/
public boolean equals(Object _other)
{
if (_other == null) {
return false;
}
if (_other == this) {
return true;
}
if (!(_other instanceof Customer)) {
return false;
}
final Customer _cast = (Customer) _other;
if (customercode == null ? _cast.customercode != customercode : !customercode.equals( _cast.customercode )) {
return false;
}
if (customercodeModified != _cast.customercodeModified) {
return false;
}
return true;
}
/**
* Method'hashCode'
*
* @return int
*/
public int hashCode()
{
int _hashCode = 0;
if (customercode != null) {
_hashCode = 29 * _hashCode + customercode.hashCode();
}
_hashCode = 29 * _hashCode + (customercodeModified ? 1 : 0);
if (customername != null) {
_hashCode = 29 * _hashCode + customername.hashCode();
}
_hashCode = 29 * _hashCode + (otherModified ? 1 : 0);
return _hashCode;
}
/**
* Method'createPk'
*
* @return CustomerPk
*/
public CustomerPk createPk()
{
return new CustomerPk(customercode);
}
/**
* Method'toString'
*
* @return String
*/
public String toString()
{
StringBuffer ret = new StringBuffer();
ret.append( "com.relationinfo.quote.dto.Customer: " );
ret.append( "customercode='" + customercode + "'" );
ret.append( ", customername='" + customername + "'" );
ret.append( ", phone='" + phone + "'" );
ret.append( ", address='" + address + "'" );
ret.append( ", relationman='" + relationman + "'" );
ret.append( ", other='" + other + "'" );
return ret.toString();
}
} |
Customer类为客户的数据传输对象(Data Transfer Object,简称DTO),实现了客户表的所有列,它必须继承Serializable,实现三个抽象方法(equals(), hashCode()和toString())需要你在Customer类里实现。Customer类为客户管理模块组件的核心组件之一,它为不同业务层之间的传输数据。
注意:如果你使用IntelliJ IDEA,你可以自动产生equals()和hashCode(),但没有toString()、
注意:所有实现Serializable的类,都必须增加Serial Version ID,否则Eclipse提示Warn信息。
技巧:类Customer的属性方法,定义基本的类型即可,关于每个属性的Getter、Setter方法,通过开发工具,如:Eclipse,可以实现这些属性的Getter、Setter的方法。
8.6.1.2 建立Customer基于主键的CustomerPk数据传输对象(DTO)
现在我们已经创建了Customer的数据传输对象(DTO),下面对于客户管理,需要创建针对主键的数据传输对象,要在src/**/dto目录下建立一个简单的CustomerPk对象,这个文件用来映射对象→ 表和属性(变量) → 主键字段,其中客户表中customercode为主键,为此CustomerPk类包括属性customerCode,UML图如图8-2所示,
图8-2 CustomerPk的UML图
代码例如清单8-2所示:
光盘:CustomerPk.java代码见光盘JSP/chapter08/src/com/relationinfo/quote/dto包中的CustomerPk.java类。
清单8-2 CustomerPk.java
package com.relationinfo.quote.dto;
import java.io.Serializable;
/**
* This class represents the primary key of the customer table.
*/
public class CustomerPk implements Serializable
{
/**
*
*/
private static final long serialVersionUID = 7430730414414541036L;
private String customercode;
/**
* Sets the value of customercode
*/
public void setCustomercode(String customercode)
{
this.customercode = customercode;
}
/**
* Gets the value of customercode
*/
public String getCustomercode()
{
return customercode;
}
/**
* Method 'CustomerPk'
*
*/
public CustomerPk()
{
}
/**
* Method'CustomerPk'
*
* @param customercode
*/
public CustomerPk(final String customercode)
{
this.customercode = customercode;
}
/**
* Method'equals'
*
* @param _other
* @return boolean
*/
public boolean equals(Object _other)
{
if (_other == null) {
return false;
}
if (_other == this) {
return true;
}
if (!(_other instanceof CustomerPk)) {
return false;
}
final CustomerPk _cast = (CustomerPk) _other;
if (customercode == null ? _cast.customercode != customercode : !customercode.equals( _cast.customercode )) {
return false;
}
return true;
}
/**
* Method'hashCode'
*
* @return int
*/
public int hashCode()
{
int _hashCode = 0;
if (customercode != null) {
_hashCode = 29 * _hashCode + customercode.hashCode();
}
return _hashCode;
}
/**
* Method'toString'
*
* @return String
*/
public String toString()
{
StringBuffer ret = new StringBuffer();
ret.append( "com.relationinfo.quote.dto.CustomerPk: " );
ret.append( "customercode='" + customercode + "'" );
return ret.toString();
}
} |
CustomerPk为客户表的主键数据传输对象,负责处理主键对象传输,本类在查询、删除、更新等功能操作过程中,作用明显,解决了开发过程中,传递多个参数来进行根据主键查询、删除、更新等的代码实现方法。将客户表的主键进行封装,组成主键的组件,在系统的不同层之间进行传输,主要作用为参数传递。
技巧:类CustomerPk的属性方法,定义基本的类型即可,关于每个属性的Getter、Setter方法,通过开发工具Eclipse,可以实现这些属性的Getter、Setter的方法。
8.6.1.3 根据Customer对象产生数据库表
Customer、CustomerPk创建完毕的情况下,可以通过Customer对象来建立Customer表,DTO的每个属性对应于数据库中的每个字段,对于对象的不同类型的字段,转换为数据库中的不同类型的列,例如清单8-3所示:
光盘:quote.sql代码见光盘JSP/chapter08/sql文件夹中的quote.sql文件中。
清单8-3 Customer表
create table customer (
customercode varchar(32) not null,
customername varchar(255) null,
phone varchar(20) null,
address varchar(255) null,
relationman varchar(30) null,
other varchar(255) null,
constraint PK_CUSTOMER primary key (customercode)
)
关于Customer数据表,符合SQLServer2000,如果读者使用其他数据库做为本系统的数据库,需要根据实际情况,改变数据表中的列的属性。
注意:读者根据实际情况,改变数据表在不同数据库中的字段类型,增加或者修改数据表的字段和属性。
在目前情况下,数据库创建完毕,还不可以实现业务逻辑程序,因为在类路径里还没有CustomerDAO.class,我们需要创建它。CustomerDAO.java是客户管理中的业务接口,而类CustomerDaoImpl.java是CustomerDAO的JDBC实现,让我们继续客户管理模块的开发旅程。
8.6.1.4 创建一个对Customer对象执行CRUD操作的DAO
现在,在src/ **/dao目录里建立PersonDAO.java接口,并且指定所有实现类要实现的基本CRUD操作,UML图如图8-3所示:
图 8-3 CustomerDao的UML图
代码例如清单8-4所示:
光盘:CustomerDao.java代码见光盘JSP/chapter08/src/com/relationinfo/quote/dao包中的CustomerDao.java类。
清单8-4 CustomerDao.java
package com.relationinfo.quote.dao;
import com.relationinfo.quote.dto.Customer;
import com.relationinfo.quote.dto.CustomerPk;
import com.relationinfo.quote.exceptions.CustomerDaoException;
public interface CustomerDao
{
public CustomerPk insert(Customer dto) throws CustomerDaoException;
public void update(CustomerPk pk, Customer dto)
throws CustomerDaoException;
public void delete(CustomerPk pk) throws CustomerDaoException;
public Customer findByPrimaryKey(CustomerPk pk)
throws CustomerDaoException;
public Customer[] findAll() throws CustomerDaoException;
public Customer findByPrimaryKey(String customercode)
throws CustomerDaoException;
public Customer[] findWhereCustomercodeEquals
(String customercode) throws CustomerDaoException;
public Customer[] findWhereCustomernameEquals
(String customername) throws CustomerDaoException;
public Customer[] findWherePhoneEquals
(String phone) throws CustomerDaoException;
public Customer[] findWhereAddressEquals
(String address) throws CustomerDaoException;
public Customer[] findWhereRelationmanEquals
(String relationman) throws CustomerDaoException;
public Customer[] findWhereOtherEquals
(String other) throws CustomerDaoException;
public void setMaxRows(int maxRows);
public int getMaxRows();
public Customer[] findByDynamicSelect
(String sql, Object[] sqlParams) throws CustomerDaoException;
public Customer[] findByDynamicWhere
(String sql, Object[] sqlParams) throws CustomerDaoException;
} |
通过对类Customer的分析,类CustomerDao定义了以下接口方法,为客户管理使用:
Ø insert(Customer dto):参数为Customer,返回值为CustomerPk;
Ø update(CustomerPk pk, Customer dto),参数为CustomerPk,Customer,返回值为void;
Ø delete(CustomerPk pk),参数为CustomerPk,返回值为void;
Ø findByPrimaryKey(CustomerPk pk),参数为CustomerPk,返回值为Customer;
Ø findAll(),参数为空,返回值为Customer[];
Ø findByPrimaryKey(String customercode),参数为customercode,返回值为Customer。
……..
通过对上述方法的分析和实现,大家会发现一个奇怪的现象,就是参数基本上是Customer、CustomerPk,返回值要么为void,要么为Customer或Customer[],从这些方面来看,本节设计的数据传输对象类Customer、CustomerPk符合程序设计的要求,这里大家看到设计数据传输对象类的长处。当然,有的方法参数或者返回值不是数据传输对象,这和我们设计数据传输对象类不相违背。
注意:类CustomerDAO的异常统一捕获异常类CustomerDaoException。在以上的方法声明上并没有CustomerDaoException说明,所以下面创建CustomerDaoException。
技巧:数据传输对象(DTO)类,在组件开发过程中,效果显著,建议读者根据实际情况实现类似于客户管理模块中的Customer、CustomerPk类,来实现组件之间的对象传输。
提示:类CustomerDAO定义了客户管理模块中所需要的所有方法接口,这样读者在开发过程中,可能不会用到其中几个接口方法,但是它不影响程序的运行效率和开发效率,所以建议读者在时间允许的情况,将各种可能用到的接口方法定义齐全,以备不时之需。
注意:本章对于查询列表返回值,统一采用DTO的数组方式来返回,如:Customer[]。 |
8.6.1.5 创建CustomerDaoException类
类CustomerDAO的开发过程中,我们定义了每个方法的异常捕获方法CustomerDaoException类,它是Customer的错误信息类,下面我们要在src/**/exception目录下建立类CustomerDaoException,实现捕获客户管理模块异常,UML图如图8-4所示:
图8-4 CustomerDaoException的UML图
代码例如清单8-5所示:
光盘:CustomerDaoException.java代码见光盘JSP/chapter08/src/com/relationinfo/quote/exceptions包中的CustomerDaoException.java类。
清单8-5 CustomerDaoException.java
package com.relationinfo.quote.exceptions;
public class CustomerDaoException extends DaoException {
/**
*
*/
private static final long serialVersionUID = -6185564047748062340L;
/**
* Method'CustomerDaoException'
*
* @param message
*/
public CustomerDaoException(String message) {
super(message);
}
/**
* Method'CustomerDaoException'
*
* @param message
* @param cause
*/
public CustomerDaoException(String message, Throwable cause) {
super(message, cause);
}
} |
类CustomerDaoException为客户管理模块的异常捕获类,定义两个方法:
Ø CustomerDaoException(String message):类CustomerDaoException的public方法,参数为message,调用了父类的同名方法;
Ø CustomerDaoException(String message, Throwable cause):类CustomerDaoException的public方法,参数为message、Throwable,执行父类的同名方法。
注意:类CustomerDaoException的两个方法,调用的都是父类DaoException的方法。
从类CustomerDaoException可以看出,它继承了类DaoException,因此这里必须创建DaoException类,例如清单8-6所示:
光盘:DaoException.java代码见光盘JSP/chapter08/src/com/relationinfo/quote/exceptions包中的DaoException.java类。
清单8-6 DaoException.java
package com.relationinfo.quote.exceptions;
public class DaoException extends Exception {
/**
*
*/
private static final long serialVersionUID = 5908588911001065350L;
protected Throwable throwable;
/**
* Method'DaoException'
*
* @param message
*/
public DaoException(String message) {
super(message);
}
/**
* Method'DaoException'
*
* @param message
* @param throwable
*/
public DaoException(String message, Throwable throwable) {
super(message);
this.throwable = throwable;
}
/**
* Method'getCause'
*
* @return Throwable
*/
public Throwable getCause() {
return throwable;
}
} |
从类DaoException可以看出它继承了类Exception(类Exception为Java的核心类,实现了异常捕获),同时提供了两个方法的具体实现,如:DaoException(String message)、DaoException(String message, Throwable throwable),根据参数的不同实现类似的功能。
技巧:本章所有的异常捕获类,统一继承类DaoException,实现错误异常的统一管理和实现。
至此,关于客户管理的Exception、DAO、DTO、DaoException类也开发完毕,下面在开始实现本节业务逻辑实现类CustomerDaoImpl前,先完成访问数据库类ResourceManager的设计和实现。
8.6.1.6 建立访问数据库类-ResourceManager.java
上述代码开发完毕后,下面讲述客户管理的具体实现,也就是与数据库进行交互的代码实现。
首先,我们创建访问数据库类,定义访问数据库的JDBC_DRIVER、JDBC_URL、JDBC_USER、JDBC_PASSWORD等,这里采用最简单的方式JDBC连接数据库,数据库为SQL Server,UML图如图8-5所示:
图8-5 ResourceManager的UML图
代码例如清单8-7所示:
光盘:ResourceManager.java代码见光盘JSP/chapter08/src/com/relationinfo/quote/jdbc包中的ResourceManager.java类。
清单8-7 ResourceManager.java
package com.relationinfo.quote.jdbc;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ResourceManager {
private static String JDBC_DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
private static String JDBC_URL = "jdbc:odbc:quote";
private static String JDBC_USER = "sa";
private static String JDBC_PASSWORD = "password";
private static Driver driver = null;
public static synchronized Connection getConnection() throws SQLException {
if (driver == null) {
try {
Class jdbcDriverClass = Class.forName(JDBC_DRIVER);
driver = (Driver) jdbcDriverClass.newInstance();
DriverManager.registerDriver(driver);
} catch (Exception e) {
System.out.println("Failed to initialise JDBC driver");
e.printStackTrace();
}
}
return DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
}
public static void close(Connection conn) {
try {
if (conn != null)
conn.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
public static void close(PreparedStatement stmt) {
try {
if (stmt != null)
stmt.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
public static void close(ResultSet rs) {
try {
if (rs != null)
rs.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
} |
ResourceManager为本系统的核心代码类,实现了获得数据库连接的功能,同时提供了close(Connection conn)、close(PreparedStatement stmt)、close(ResultSet rs)等方法,
8.6.1.7 建立CustomerDao的实现类CustomerDaoImpl
获得数据库连接的类实现完毕,让我们创建一个实现CustomerDAO的类CustomerDaoImpl,并使用insert/update/delete这个Customer对象,为此,我们在src/ **/ jdbc创建一个新类CustomerDaoImpl.java,它应该扩展AbstractDataAccessObject,并且实现CustomerDAO,UML图如图8-6所示:
图8-6 CustomerDaoImpl的UML图
代码例如清单8-8所示:
清单8-8 CustomerDaoImpl
package com.relationinfo.quote.jdbc;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.log4j.Logger;
import com.relationinfo.quote.dao.CustomerDao;
import com.relationinfo.quote.dto.Customer;
import com.relationinfo.quote.dto.CustomerPk;
import com.relationinfo.quote.exceptions.CustomerDaoException;
public class CustomerDaoImpl extends AbstractDataAccessObject implements
CustomerDao {
protected java.sql.Connection userConn;
protected static final Logger logger = Logger
.getLogger(CustomerDaoImpl.class);
protected final String SQL_SELECT = "SELECT customercode, customername, phone, address, relationman, other FROM "
+ getTableName() + "";
private int maxRows;
protected final String SQL_INSERT = "INSERT INTO "
+ getTableName()
+ " ( customercode, customername, phone, address, relationman, other ) VALUES ( ?, ?, ?, ?, ?, ? )";
protected final String SQL_UPDATE = "UPDATE "
+ getTableName()
+ " SET customercode = ?, customername = ?, phone = ?, address = ?, relationman = ?, other = ? WHERE customercode = ?";
protected final String SQL_DELETE = "DELETE FROM " + getTableName()
+ " WHERE customercode = ?";
protected static final int COLUMN_CUSTOMERCODE = 1;
protected static final int COLUMN_CUSTOMERNAME = 2;
protected static final int COLUMN_PHONE = 3;
protected static final int COLUMN_ADDRESS = 4;
protected static final int COLUMN_RELATIONMAN = 5;
protected static final int COLUMN_OTHER = 6;
protected static final int NUMBER_OF_COLUMNS = 6;
protected static final int PK_COLUMN_CUSTOMERCODE = 1;
…
} |
上述代码没有编写完毕,只是提供了CustomerDaoImpl类的常量定义,如:SQL_SELECT、SQL_INSERT、maxRows、SQL_UPDATE等,另外,定义了Customer表列的顺序编码。