跳到主要内容

Java 简易通讯录 自带数据库(第一版)

阅读需 10 分钟

最近有一个作业,用 Java 连接数据库制作简易通讯录。

1 作业要求

① 实现界面。

② 功能包括信息新增,以姓名为空进行修改和查询,当姓名为空时,查询显示所有的记录。

③ 通讯信息保存在数据库里,查询结构显示在文本域内。

虽然只要求在 IDE 中成功运行即可,但是我的想法是,把数据库嵌入到程序里面,再把 Java 程序打包成 exe 文件。目前已全部实现。

2 实现效果

源码地址和推荐资料放在这篇结尾

界面对比

二、过程

1 功能实现

因为想把数据库嵌入程序,肯定就不能太大了。所以采用了 derby,一个非常小的数据库,只有 3MB 大小。这个数据库是完全用 java 编写的,可以嵌在应用程序中,也可以作为单独的数据库服务器(需要再加载另一个 jar 文件)。


**BUG 1.1:**某些按键功能不正常。

原因: 判断输入框( JTextFiled )判断为空条件不准确。

解决方法:

if(tpname.getText()!=null||tpname.getText().length>0){} //前者永远为 true,去掉前面条件
if(tpname.getText().length>0){} //正常运行

**BUG 1.2:**以姓名为空进行查询时,只能显示第一个手机号码。

原因:查询语句不正确和 getstring() 不正确。可以看出下图的两个查询语句的不同:"select * from" 和 "select phone from" ,前者是输出这一行的数据,一次while会输出两个语句,但是后者只会输出 phone 列的数据,一次只有一个语句。这样,在输出的时候会报错,造成只会输出第一个语句的假象。

ResultSet rs = st.executeQuery(str);
while (rs.next()) {
ta.append(rs.getString(1)+" ");
ta.append(rs.getString(2)+"\n");
}
connection.close();

selectPhone

解决方法:将str 设置为 str2 语句。

String str1= "select phone from myTable where name = '"+tpname.getText()+"'";
String str2= "select * from myTable where name = '"+tpname.getText()+"'";

2 导出可运行的 jar 包

这一步时,生成的 jar 包,第一次按下按钮时,都会建立程序设定的 myDB 数据库文件夹。(我用的是 Runnable JAR file)

但最开始的时候,没有一点点的反应。我一直以为是因为数据库没有成功导出的问题,第一次接触数据库,一直在查怎么导出带数据库的 Jar 包,无功而返。

第二天再查的时候,给程序每一部分前后都加了语句,如果顺利进行会在内容栏输出对应语句。然后我发现数据库是处于成功链接的状态的!前面几步都是成功的!

那么为什么没有输出?我找到对应的失败地方,加上了输出错误原因的语句。

ta.append(e2.getMessage());

taappend


BUG 2.1: 没有找到对应的表。

原因:打包时没有把建立的表加进去。

解决方案:增加了判断条件,补充建表功能。(造成BUG 2.4)


**BUG 2.2:**System.out.printf() 语句。

解决方案:默默去掉。


BUG 2. 3: 某些按键不能输出对应范围的数据。

解决方案:完善判断逻辑。


**BUG 2.4:**程序只在第一次成功运行。

原因: 我没有考虑 try catch 来建表。而是创建了一个全局变量,但是第二次打开时全局变量会初始化。

解决方案:新增了“初始化”按钮,将初始化功能分离出来。(在第三版改善了这个操作)


最后,程序功能已经基本完善,可以本地保存数据,重复使用。初始化的时候会建立一个 myDB 文件夹和一个 derby.log 日志。删除这两个文件数据就没有咯。

3 打包为 exe

**BUG 3.1:**首先再尝试了一波 exe4j 打包工具,提示 jre 文件被破坏。

原因:在设置条件时候,没有选择“Generate 64-bit executeable"。32位系统直接默认就好,64位系统必须勾上这个选项。

解决方案:勾上这个选项。

exe4jBit

这个页面选择程序之前生成好的 jar 及主类就可以了。(derby.jar在生成的时候已经加在 addressList.jar 了,所以不需要再加了)(这个软件如果没有输入注册码,生成的软件会有提示)

exe4jJar

Jar2Exe 程序也需要选择对应的位数,64位勾上就好了。32位不用。(这个软件相对简单)

jar2exeBit

接下来在没有 Java 环境的64位虚拟机上测试,复制本机的 jre 文件,设置相对路径的环境变量,exe 文件成功打开。


**BUG 3.2:**在没有 Java 环境的 32 位虚拟机上进行相同的测试。exe 文件打不开,提示 Java 环境被破坏。

原因: jre 也分 32 位与 64 位。

解决方案:下载一份32位 jre 替换。成功运行。

用 Inno 做成 exe 安装文件。成功。

exe

最后一个大坑,如何精简 jre?这两个包解压缩后都是180mb,一个几Mb的小程序带这么大的环境吗。查了半天没有解决。只能暂时留着了。没有Java 环境的用户下载一个 jre 压缩包就好了,与 jar 包放在同一个目录下(环境变量好像有点问题=。=,还是推荐 jre ,设置好环境变量)

4 完善

以下内容可对比我几个版本的源码进行参考:

设置全局字体

上面是第二版的界面。默认样式不太好看。然后我都改成宋体了~

public static void main(String args[]) throws SQLException, ClassNotFoundException {	
initGlobalFontSetting(new Font("宋体",Font.PLAIN,18));/*在初始化界面之前加上这句话。*/
addressList frm = new addressList();
frm.setVisible(true);
}
public static void initGlobalFontSetting(Font fnt){   /*网上摘抄方法*/
FontUIResource fontRes = new FontUIResource(fnt);
for(Enumeration<Object> keys = UIManager.getDefaults().keys(); keys.hasMoreElements();){
Object key = keys.nextElement();
Object value = UIManager.get(key);
if(value instanceof FontUIResource)
UIManager.put(key, fontRes);
}
}

设置文本框及滚动条

之前的版本,当数据超过规定的尺寸时,就需要手动拉长软件,十分不方便。于是我加上了 JScrollPane,将水平滚动条设置为隐藏,垂直滚动条设置为自动显示,即当数据超出范围才会显示滚动条。

	ta = new JTextArea(25,41);   //设置文本域尺寸
ta.setLineWrap(true); //自动换行
scrollPane = new JScrollPane(ta); //添加滚动条
scrollPane.setVerticalScrollBarPolicy( //垂直滚动条设置为自动显示
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

还有一个问题:当刷新出的数据超过范围时,滚动条会自动处于最下端,虽然说大部分情况是需要这样的,但是当在浏览一些文档的时候,最好还是从头开始。

解决方案:在超过范围的部分加上这句话就好了。

ta.setCaretPosition(0);

设置组件间距

原因:还是样式问题...有点别扭。右图增加了间距。

解决方案:查询使用的布局文档内的方法。

p = new JPanel(new FlowLayout(FlowLayout.CENTER,10,13));

确认框与逻辑更改

在【初始化】这个模块内,添加了确认框功能,以防用户误删数据。同时缓和了,新建数据表与删除数据之间的矛盾。

原因:当用户第一次使用软件时,数据库还未建立,那么应该是建表语句。如果是用户想删除所以已有数据,那么应该是删除语句。但是他们之间的联系是,新建表内是没有数据的,删除所有数据后的表内也是没有数据的。执行他们的结果是一样的,没有数据的数据表。如果已存在表,那么第一个connect会报错然后退出,执行下面语句。如果未存在表,新建表之后再执行。

解决方案:去掉新建表的方法。精简代码。如下:

if(choice()) { /*初始化模块内代码 */
str = "create table myTable(name varchar(20), phone int )";
connect(str);
str = "delete from myTable";
connect(str);
tfname.setText("");
tfphone.setText("");
ta.setText("\n 初始化成功!");
}
boolean choice() {	//确认框方法
int n = JOptionPane.showConfirmDialog(this,"确认这样做吗?","确认对话框",
JOptionPane.YES_NO_OPTION );
if(n==JOptionPane.YES_OPTION) return true;
return false;
}

同时也用基本方法,将鼠标点击事件的逻辑更改了:以查找为例。

if(obj==search) { 
str ="select * from myTable where name = '"+tfname.getText()+"'";
}
connect(str);

//之前是这么一句话嵌在链接数据库内,用作判断输出数据。
if(!hasName&&!hasPhone||hasName&&hasPhone) str="select * from myTable";

然后改成了这样: 将链接数据库模块与调用分离。

if(obj==search) {   
if(name.length()==0&&phone.length()==0) {
str ="select * from myTable";
} else if(name.length()>0&&phone.length()==0){
str ="select * from myTable where name = '"+name+"'";
} else if(name.length()==0&&phone.length()>0) {
str ="select * from myTable where phone = "+phone;
} else {
str ="select * from myTable where name = '"+name+"' and phone = "+phone;
}
}
connect(str);

剩下的部分也是同一个原理更改。

本来想让鼠标点击事件调用外部类,结果说是线程过多,于是就没有考虑了。

END.

三、总结

做这个作业其实快一个月了,一直卡在数据库链接上,就一直放着。这周上课看到鼠标点击事件 addActionListenner(this) ,addActionListenner(new buttonEvenet()) 两种方法,后者是调用外部类,突然好像明白了什么,一下午就完成基本要求了,总体来讲还是对 JAVA 研究不深。

作业要求之外的事情,卡在打包上,是因为我没有想到捕获异常去查看错误,而是去研究自以为的错误。最后卡在精简 jre 上,进入了四个小时的时间黑洞,我不知道那段时间我做了什么,问题没有解决,时间却飞一样地流逝掉了。做完的时候就应开始记录,而不是再去苛求,导致后面几天的节奏乱掉了。

不过结果还不错,我的第一款软件,尽管没有达到最大效率。

有什么不会的,多查,但是一定要查对方向。捕获异常非常重要。

下面的两篇文章是我在不同阶段,做这个软件时,遇到的问题集锦。

可惜的是,最开始的时候过于投入,提到的问题基本没有截图保存。等实现之后才想到要记录。结果就是,文章不够流畅,跳跃的较快,基本只有要点而没有过渡。遗憾。

具体代码我已经上传至我的 Github【someTools】项目内的【addressList】文件夹内。包含目前三版的原代码和 jar 文件。

Github 地址:https://github.com/evenIfAlsoGo/someTools/tree/master/addressList

附带相关资料:卡在步骤上时的,点睛之笔。

暂时未加入评论功能,请在对应公众号文章下或 GitHub Issues下留言反馈。