级别: 初级 Michael Abernethy (mabernet@us.ibm.com), 软件工程师, IBM Kulvir Singh Bhogal (kbhogal@us.ibm.com), 软件工程师, IBM
2002 年 9 月 25 日
本文研究 UPC 符号和条形码,并描述如何使用 Java 来读取 UPC 条形码中的信息。作者说明了如何促进和组织围绕 IBM DB2 数据库的企业运营。本文中描述的样本应用程序包括可下载的代码。
简介 观察一下您的周围。您看到了什么?如果您是众多软件程序员中的一名,您所看到的不会太好。然而,我们不谈论您的同事;我们谈论条形码。因为飞速发展的经济已经迅速地采用了条形码提供的便利,所以它们已经普遍存在。从跟踪邮包到借阅图书馆书籍,条形码无处不在。 在本文中,我们仔细研究这些类似斑马(有黑白条纹)的东西,并向您显示如何使用 Java 来促进和组织围绕 IBM DB2 数据库的企业运营。 为了继续阅读本文,可以下载 jmart.jar 代码文件和必需的通信端口文件 barcode.zip。
条形码研究 有许多不同类型的条形码。在本文中,我们只关注一种 ― UPC 条形码。UPC 代表通用产品代码(Universal Product Code)。最初创建这些代码是为了促进杂货店收款处的运营和跟踪库存。由于它们的成功,这个想法就象野火般迅速传播到其它零售产品。 商品的 UPC 实际上是由一家名为 Uniform Code Council(UCC)的公司发布的。您基本上可以将 UCC 作为代理,以确保两个不同产品不会有相同的 UPC 代码。制造商必须向 UCC 提出申请并缴付年费,才能得到进入其系统的许可。作为交换,UCC 发给制造商一个制造商标识号。 UPC 符号通常有两个部分:
- 机器可读的条形码
- 人类可读的 12 位 UPC 号码
前 6 位 UPC 号码是 制造商标识号。接下来 5 位是 产品编号。制造商负责确保其任何两种产品不会有相同的产品编号。制造商销售的每件产品都必需有不同的产品编号。如果涉及到不同尺寸的产品包装或重新包装,那么这些情况也需要其自己的唯一产品编号。 至此,我们只说明了 12 位 UPC 号码中的 11 位。最后一位是 校验位。在没有深入钻研条形码科学的情况下,让我们姑且认为最后一位确保所扫描的 UPC 符号是准确的。这时,您可能想要停止并细察得到的每个 UPC 代码。注:您看到的一些 UPC 代码可能没有 12 位;短的那些称为消零条形码。本文中出现的代码本身不处理短条形码,但您应该能够方便地改写我们的代码以用于缩写的 UPC 代码。
项目概述 现在,继续我们的项目。存储在 UPC 代码中的唯一信息是制造商编号和产品编号。当前价格和数量这种有意义的信息没有存储在 UPC 符号中。这样做并不巧妙,因为这种设置无法适应价格变化和库存变化。那么如何起作用呢?当收款处的扫描器扫描 UPC 代码时,收银机将已扫描的 UPC 代码信息发送到销售点(POS)计算机。然后,POS 计算机返回有关已扫描商品的预编程信息,它可以包括当前价格、数量和其它请求信息。 BarCodeScanner 项目的目的是模拟上一段落中描述的设置。我们的代码将与来自可以挂接到串口的扫描器的数据一起工作。使用该数据,您将能够执行营业员在当地杂货店所做的工作。我们将友好的邻近杂货店称为 J*Mart。IBM DB2 数据库将充当后端,存储每件商品的信息。干脆让我们立即开始。
后端 我们将 DB2 数据库用做后端,将它称为 销售点(或 POS)数据库。接着,通过激活 DB2 命令行并发出下列语句来创建您的数据库: CREATE DB POSDB 下一步,需要一个表来存储数据。下表显示数据库中可能需要的项目:
特性名 | 示例值 | 数据库列名 | 列类型 | 产品编号 | 12345 | ITEMNUMBER | Integer | 制造商标识号 | 123456 | MANUFACTURERNUMBER | Integer | 产品名 | Doritos | NAME | Varchar(40) | 制造商名称 | Frito-Lay | MANUFACTURERNAME | Varchar(40) | 数量 | 23 | QUANTITY | Integer | 价格 | 1.67 | PRICE | Decimal(5,2) | 发出下列语句来创建该表: Create Table posTable (Itemnumber Integer NOT NULL, ManufacturerNumber Integer NOT NULL, name varchar(40) NOT NULL, manufacturerName varchar(40) NOT NULL, quantity Integer NOT NULL, Price decimal(5,2) NOT NULL) 请注意,所有这些数据库列都要求新记录没有空值。 前端将能够以许多种不同方法与数据库交互:
- 可以将新产品输入数据库
- 可以更新现有产品的数量
- 可以从 POS 数据库完全除去产品
- 可以在数据库中获取关于现有产品的信息
要完成这些任务,Java 程序使用 Java 数据库连接(JDBC)来与 IBM DB2 数据库交互。此时,查看 com.jmart.dbaccess ( 下载 JAR 文件,如果还未下载的话)中的代码对您会有帮助。尤其是,类 Database 是方便至 DB2 数据库连接的助手类。 DatabaseAccess 类具有函数 delInventory、qryInventory 和 updInventory 。代码载有文档,所以您应该能够不花费很大的气力就可以弄清发生了什么。 方法有一个入参类型 InventoryData 。 InventoryData 类是 com.jmart.data 包的一部分,该类在整个项目中用做临时存储工具,以来回传送数据。在 Java 程序员术语中, InventoryData 类可以被认作 JavaBean 组件。类充当通信管道。在整个代码中,我们充分利用了 Java 中的对象按引用传递这一事实。
|
必需的通信端口文件 要使 BarCodeScanner 运行,下载 barcode.zip文件并正确安装压缩文件。 comm.jar ― 包含运行 BarCodeScanner 和使用串或并口的其它应用程序所需的类。确保运行应用程序时类路径指向这个 JAR 文件。 javax.comm 特性 ― 将这个文件放在 jre 的 lib 目录中。 win32comm.dll― 将这个文件放在 Windows 目录(例如,c:/Windows)下。 要使该程序在其它操作系统上运行,请搜索您需要的 dll 文件。 | |
扫描概述 不管您信不信,条形码扫描已相当标准化。现在,您可以购买挂接到 PC COM 端口的手持式扫描器。扫描器将信号发送到 COM 端口,该端口可由被指定侦听进入流的程序获取。侦听工具在 Java 世界中相当原始;但这种原始使我们的生活更方便。将进入的扫描作为字节数组写入串口,由程序员决定将它转换成有用信息。 BarCodeScanner 本身的实现并不十分困难。我们知道将要获得信息,并知道我们的工作是将它转换成可以解析信息的有用字符串。 javax.comm 包提供了从串口或并口收集该信息所需的框架。(有关 javax.comm 包的更多信息,请参阅 参考资料。) javax.comm 包完全是其处理端口以及如何从端口检索信息的基本要素。现在是 21 世纪,从 1998 以来,OO 已经发展了很长一段时间。因此,要使条形码扫描器的行为象其它已建立的 Java 设计模型那样,我们为条形码创建了当扫描每个条形码时发出 BarCodeEvent 的事件/侦听器模型。从这个意义上讲,可以将条形码扫描器视作并编码成按钮按下或鼠标单击。现在,从代码开始…。
BarCodeScanner BarCodeScanner 类的构造器负责创建端口并设置条形码扫描器将使用的所有参数。大多数情况下,可以根据使用的条形码扫描器来更改这些设置。检查条形码扫描器设置并对代码进行相应的调整,或者(最好)从代码中除去它们并将它们放在特性文件中。
BarcodeScanner 构造器
CommPortIdentifier commPort = CommPortIdentifier.getPortIdentifier("COM1");
serialPort = (SerialPort)commPort.open("Barcode Scanner",3000);
inputStream = new BufferedReader (new InputStreamReader
(new BufferedInputStream(serialPort.getInputStream())));
serialPort.setInputBufferSize(200);
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
serialPort.setSerialPortParams(BAUD_RATE, SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); | 既然串口是打开的并且正在侦听,那么我们就必须准备好接收和解释条形码读取。 javax.comm 为端口提供它自己的事件/侦听器接口,即通知该类已将数据放入缓冲区的 serialEvent() 方法。现在由您决定处理该代码。 我们使用输入流来将字节数组转换成 String。但是,我们发现当使用不同的条形码扫描器和条形码时有下列问题。
- 当处理大于 100 个字符的大条形码时,会发出两个 serialEvent:第一个 serialEvent 的缓冲区只被部分填充,第二个 serialEvent 的缓冲区被完全填充。解决这个问题的最简单方法是当缓冲区中的数据不完整时忽略该事件,并等待直到它完整为止才将它传递到侦听器。
if (inputStream.available() < length)
return; | - 某些条形码扫描器在每个 String 读和/或新行字符的末尾插入回车。要改正这个问题并使代码可用于所有条形码,我们创建了大于条形码长度的缓冲区,并且在创建 String 时只使用我们感兴趣的字符。从本质上讲,这将忽略一些条形码最终所具有的无关字符。
barCodeString = new String(buffer, 0, length); | 最后,当读取和转换条形码时,我们必须通知所有侦听器有关新 String 的信息。但是,在侦听器的所有 barCodeScanned() 方法中循环很费时,尤其当它们查询数据库时(正如它在本案例中所做的)。在 Java 抽象窗口工具包(Java Abstract Window Toolkit,AWT)模型中,如果还没有完成第一个事件的 actionPerformed() ,那么单击按钮两次将不会发出两个 ActionEvent。这对于条形码扫描器来说是无法接受的,因为当快速读取条形码时,我们不能允许丢失它们。因此,我们必须为每个条形码读取产生一个新线程,使该线程调用并等待 AWT 线程,确保在发生“象机关枪扫射一样连续快速扫描”时不会丢失条形码。 为每个条形码读取创建新线程有其消极方面 ― 常见的线程问题产生了其消极的一面:锁定、监视器、争用条件以及在凌晨 2 点完成大学里 OS 课程的程序之前尝试忽略的所有事情。但是我们扯开一下……虽然演示应用程序不处理这些问题,因为用户在应用程序(如收款处)中不对条形码“象机关枪扫射一样连续快速扫描”,所以可以通过使用线程安全的队列和同步块来解决它们。
GUI 虽然不是以应用程序为中心,但简单的 GUI 让您了解了应用程序如何使用条形码扫描器。它是使用模型-视图-控制器(Model-View-Controller (MVC))设计来设置的。启动 J*Mart 应用程序的 main 方法也在 InventoryView 类中。 模型 InventoryData 在该应用程序中充当模型。它是一种简单的 JavaBean,包含需要从 View 传递到数据库的所有数据。 视图 InventoryView 显示运行该应用程序所需的所有信息,并处理某些方案:
- 读取数据库中不存在的条形码。然后,应用程序将让用户输入名称、价格和数量,以插入数据库中。
数据库中不存在条形码
- 读取 DB 中 确实存在的条形码。它将显示该信息,然后让用户添加一定数量的库存,删除一定数量的库存或从 DB 中删除整个产品。
数据库中存在条形码
在这些方案中,应用程序让用户完全控制以空表开始的 DB 中的库存。 控制器 InventoryController 充当 DatabaseAccess 类的网关,处理 BarCodeScanner 并将信息传递到 InventoryView 以供显示。
结束语 这个小应用程序的目的是显示如何开发一个有效的框架来使用条形码,并且通过将条形码中提供的信息用做搜索键来检索数据库中的信息。虽然这个应用程序是为 UPC 符号定制的,但只要在特性设置中做一些更改,BarCodeScanner 的代码就可完全重用于市场上的任何条形码设置和每种条形码扫描器。
参考资料
作者简介
| | | Kulvir Singh Bhogal 是一名 IBM 顾问,他设计并实现美国客户站点上以 Java 为中心的解决方案。可以通过 kbhogal@us.ibm.com与 Kulvir 联系。 | |