JSP考勤系统(二) JDBC连接MySQL数据库

  在前面的MFC程序那一块,我们已经介绍了基于ODBC和ADO技术如何操作MySQL数据库,这一部分我们介绍另外一种更加方便快捷的,能够实现相同功能的方式,那就是通过JDBC访问数据库。相比之下,这种方式要比之前的ADO方便得多,较之前面难度也有所下降。

ODBC vs JDBC

  这两种方式都是访问数据库的常用的编程接口,ODBC(Open Database Connection)是”微””软”的东西,只能在其操作系统上跑,移植性可想而知;JDBC(Java Database Connection)则是Oracle的东西,是Java大家庭的一员。大家都知道,Java正式可移植性的典范,所以,JDBC的可移植性是显然的。至于为什么,通过下面的实现流程,你应该会有所体会。此外,可以通过比较ADO的实现来窥见其便捷性。

JDBC操作数据库流程


  上述是项目中有关数据库操作的类示图。其中system.entity中的两个类为两个实体对象,UserInfo用于记录用户信息(如下),目前只保存用户的用户名、密码以及邮箱。其数据来自于用户通过注册存入数据库的用户信息数据。

1
2
3
4
5
6
7
8
9
/**
* 用户个人信息实体类
*/
public class UserInfo {
private String username;
private String password;
private String email;
.....
}

  另外一个是用户信息实体类RFIDCard(如下),用于存放用户个人信息以及包括UID、卡片类型、创建时间等卡片信息。

1
2
3
4
5
6
7
8
9
10
/**
* 用户信息实体类,包括用户个人信息以及卡片信息
*/
public class RFIDCard {
private String cardId;
private String type;
private UserInfo user;
private Date createTime;
.....
}

  通过将数据封装成实体类,将有利于数据的传递。本文给出的只是一个参考,读者可以根据功能需求自行实现。
  system.database包中,则定义了一套对数据库操作的接口(如下图)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 数据库辅助类CURD接口定义
*/
public interface DBHelper {
/**
* 插入新用户
* @return 是否插入成功
*/
public abstract boolean saveRFIDCard(RFIDCard card);

/**
* 根据cardId删除用户
* @return 删除的记录数
*/
public abstract boolean deleteRFIDCardByCardId(String cardId);

/**
* 更新用户信息 通过card.cardId进行唯一标识
* @return 是否更新成功
*/
public abstract boolean updateRFIDCard(RFIDCard card);

/**
* 根据卡号获取用户信息 包括卡片信息及用户个人信息
* @return 用户信息
*/
public abstract RFIDCard SearchRFIDCardByCardId(String CardId);

/**
* 获取所有的用户信息 包括卡片信息及用户个人信息
* @return RFIDCard信息列表
*/
public abstract List<RFIDCard> getAllRFIDCards();
}

  包括对数据库的增删改查操作。另外则是具体实现了上述一套接口的RFIDCardDBHelper数据库辅助类,按照JDBC访问数据库的流程对上述接口进行实现。在讲到具体每个接口的实现之前,我们有必要先看看目前的数据库状况。下面是创建该数据库的ddl。注意的一点是,这里保存的password为经过MD5加密后的密码字符串,为char(32)。

1
2
3
4
5
6
7
create table tb_RFIDCard(
CardId varchar(20) primary key,
username varchar(20),
password char(32),
createTime datetime,
CardType varchar(20)
);

  JDBC访问数据库主要有以下5个步骤:

1. 注册驱动
2. 通过驱动管理器获取连接
3. 获得语句对象
4. 传递语句对象,执行并获取结果
5. 关闭结果集或语句对象或连接等资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class RFIDCardDBHelper implements DBHelper {
// 驱动全局类名字符串
private static final String DRIVER = "com.mysql.jdbc.Driver";
// 数据库名
private static final String DBNAME = "rfid";
// 表名
private static final String TBNAME = "tb_rfidcard";
// JDBC-MySQL连接字符串
private static final String CONNSTR = "jdbc:mysql://localhost:3306/" + DBNAME;

private Connection conn = null;

public RFIDCardDBHelper(){
// 注册驱动
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 通过驱动管理器获取连接
try {
this.conn = DriverManager.getConnection(CONNSTR, "root", "chenshj35");
} catch (SQLException e) {
e.printStackTrace();
}
}

...
// 垃圾回收前释放连接
@Override
protected void finalize() throws Throwable {
if(null != this.conn)
this.conn.close();
super.finalize();
}
}

  在RFIDCardDBHelper辅助类的实现中,我们定义一个私有的连接对象,在构造函数中完成驱动注册,并获取到连接对象。最后,因为数据库连接的数目是有限的,我们必须在对象回收释放前将连接关闭。至于驱动字符串和连接字符串,则是按照规范设置。
注意: 在使用JDBC驱动之前,我们需要把提供这一系列API的第三方库导入到工程中(点击获取 访问密码 2395)。右键工程->Build Path->Configure Build Path,在弹出窗口中Java Build Path->Libraries->Add External JARs把下载的mysql-connector-java-5.1.26-bin.jar包导入即可。
  获取连接之后,我们就可以进行有关增删该查的相关操作,saveRFIDCard(RFIDCard card)把传递过来的用户信息插入数据库,并返回插入成功与否信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public boolean saveRFIDCard(RFIDCard card) {
int count = 0;
if(null != this.conn){
PreparedStatement prestmt = null;
try {
// 获取语句对象 MD5()函数对密码进行加密
String sql = "insert into "+ TBNAME +
"(CardId, username, password, createTime, CardType) values(?, ?, MD5(?), ?, ?)";
prestmt = this.conn.prepareStatement(sql);

prestmt.setString(1, card.getCardId());
prestmt.setString(2, card.getUser().getUsername());
prestmt.setString(3, card.getUser().getPassword());
prestmt.setDate(4, (java.sql.Date) card.getCreateTime());
prestmt.setString(5, card.getType());

// 传递语句对象,并返回结果
count = prestmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally{
if(null != prestmt){
try {
prestmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

}
return (count == 1);
}

  由于insert操作传递的参数较多,我们采用PreparedStatement语句对象。首先根据insert语句创建sql语句,用?进行占位,并在后续通过语句对象的setString(index,value)设置参数。在设置参数之前,我们通过prepareStatement(sql)创建与sql语句对应的语句对象。需要注意的一点是,有关mysql这个包的函数,他们的index是从1开始计算的,这与我们常识里的从0开始有点冲突
  创建好语句对象后,我们就可以执行该语句以获取结果。插入操作属于DML语句,调用executeUpdate方法执行(Update并不表示值使用与update操作,对于delete、insert等DML语句同样适用)。在下面例子我们会看到,对应dql查询语句,我们是通过executeQuery函数来执行。二者的返回结果也不一样,dml语句返回受影响的函数,dql则返回查询结果集。
  此外,还需要对可能发生的异常进行捕获,通过finally在退出函数前保证语句对象得到释放。通过这个函数的介绍,我们已经能够实现其他功能的函数,基本的流程都是上述提及的5个步骤,关键点在于判断sql语句是dml语句还是dql语句,从而对返回值进行区别。同时,注意此过程中异常的处理以及资源的释放问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public boolean deleteRFIDCardByCardId(String cardId) {
int count = 0;
if(null != this.conn){
Statement stmt = null;
try {
// 获取语句对象
String sql = "delete from "+ TBNAME +
"where CardId=" + cardId;
stmt = this.conn.createStatement();

// 传递语句对象,并返回结果
count = stmt.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
} finally{
if(null != stmt){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

}
return (count==1);
}

  接下来是deleteRFIDCardByCardId(String cardId)删除操作函数,和增加操作相似,都是dml语句,实现上有点不同的是,这里使用普通的Statement语句对象,需要注意的就是这次是通过createStatement获取语句对象,而不是PreparedStatement使用的prepareStatement函数,这一点需要大家注意。
  那么问题来了,这两个语句对象有什么区别?怎么用呢?PreparedStatement对sql语句进行预编译,前提是多条sql语句除了数据有区别外,结果完全一致,这样的话,对于批量操作就很有优势。PreparedStatement通过预编译,虽然在创建语句对象的时候耗时更多,但是,其在后续的一系列相同结构的sql语句的耗时上则是大幅减少,这样的话,对于批量操作,总的耗时就会比普通的Statement少得多。另外一个使用PreparedStatement的情况就是有点类似这里的情况,需要设置的参数比较多,那么通过?这个占位符,使得参数的传递更加方便快捷直观。修改操作函数updateRFIDCard(RFIDCard card)的实现几乎与删除函数完全一致(如下)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public boolean updateRFIDCard(RFIDCard card) {
int count = 0;
if(null != this.conn){
PreparedStatement prestmt = null;
try {
// 获取语句对象
String sql = "update "+ TBNAME +
" set username=?, password=MD5(?), createTime=?, CardType=?";
prestmt = this.conn.prepareStatement(sql);

prestmt.setString(1, card.getUser().getUsername());
prestmt.setString(2, card.getUser().getPassword());
prestmt.setDate(3, (java.sql.Date) card.getCreateTime());
prestmt.setString(4, card.getType());

// 传递语句对象,并返回结果
count = prestmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally{
if(null != prestmt){
try {
prestmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

}
return (count >= 1);
}

  最后,便是查询操作,属于另外一种dql范畴。因为功能需求,这里定义了两种查询操作,一种是根据UID获取用户信息SearchRFIDCardByCardId(String cardId),另一种则是遍历整个数据库,获取当前数据库的所有记录List getAllRFIDCards()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public RFIDCard SearchRFIDCardByCardId(String cardId) {
RFIDCard card = null;
if(null != this.conn){
Statement stmt = null;
ResultSet result = null;
try {
// 获取语句对象
stmt = this.conn.createStatement();
String sql = "select * from "+ TBNAME + " where CardId=" + cardId;

// 传递语句对象,并返回结果
result = stmt.executeQuery(sql);
if(result.next()){
String CardType = result.getString("CardType");
String username = result.getString("username");
String password = result.getString("password");
String email = "";
Date createTime = result.getDate("createTime");

card = new RFIDCard(cardId, CardType,
username, password, email, createTime);
}

} catch (SQLException e) {
e.printStackTrace();
} finally{
if(null != stmt){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if(null != result){
try {
result.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
return card;
}

  这里引入了结果集ResultSet保存执行结果。可以看到上述提及dql语句与dml语句的区别,是通过executeQuery(sql)执行sql语句,返回值是一个指向结果集开头的游标,我们需要通过next方法将其移到第一条记录上。假如不存在下一跳记录,next函数会返回假。将游标移到记录行上之后,我们便可以通过getString(“字段名”)或者getString(index)获取记录中不同字段的数据。同样的,index还是从1开始计数。这里使用前一种方法,也建议大家使用第一种方法,因为通过index获取数据,当表的结果发生变化时(插入新字段),下标便需要进行改变,前者则不需要。
  另外,需要注意的是,除了释放语句对象,结果集同样需要进行资源释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public List<RFIDCard> getAllRFIDCards() {
List<RFIDCard> cards = null;

if(null != this.conn){
Statement stmt = null;
ResultSet result = null;
try {
// 获取语句对象
stmt = this.conn.createStatement();
String sql = "select * from "+ TBNAME;

// 传递语句对象,并返回结果
result = stmt.executeQuery(sql);
boolean first = true;
RFIDCard card = null;
while(result.next()){
if(first){
cards = new ArrayList<RFIDCard>();
}
first = false;

String cardId = result.getString("CardId");
String CardType = result.getString("CardType");
String username = result.getString("username");
String password = result.getString("password");
String email = "";
Date createTime = result.getDate("createTime");

card = new RFIDCard(cardId, CardType,
username, password, email, createTime);
cards.add(card);
}
} catch (SQLException e) {
e.printStackTrace();
} finally{
if(null != stmt){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if(null != result){
try {
result.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

}
return cards;
}

  List getAllRFIDCards()借鉴SearchRFIDCardByCardId(String cardId),修改的地方主要是通过while循环,将结果集的每条记录依次插入到保存所有记录的列表中。我们需要考虑到的是,当我们的数据库记录数足够大,我们不可能在一个页面中把所有记录显示出来的时候,我们就需要考虑分页。所有,List getAllRFIDCards()可以进一步改进,改进成List getAllRFIDCards(int from, int to)来获取数据库查询所有记录中从from到to这个边界内的记录,方便后面View显示界面实现分页。这时候sql查询语句应该变成“select * from “+ TBNAME + “ limit ”+ from +”, “ + to;
  最后,也许有的时候你会不知不觉在sql语句字符串后面加了个”;”,因为我们在MySQL命令行里都是这么干的,打完一条语句,都需要加上”;”。其实,不管在ADO还是本文介绍的JDBC,这个”;”都是没必要的。因为正如你觉得,这个”;”只是MySQL命令行里作为结束符的东西,真正执行的sql语句也只是”;”前面那部分命令。有时,还可能因为加上这个”;”而导致执行出错。

总结

  希望通过本文的介绍,可以让你对如何实现JDBC访问数据库以及这部分内容在我们整个项目中的位置有更清晰的认识。跟ADO一样,我们将一系列的数据库操作放到一个辅助类中,这样,我们在需要的时候,只需要将辅助类实例化,然后便能调用提供的接口实现我们需要的功能。
  看到这里,是不是觉得流程清晰了呢?那么就开始动手吧,把这部分功能的实现也在自己的项目中实现跑通,动手做一做,也许你收获的更多。

[如对本文有什么建议或者错漏的地方,非常欢迎您通过评论指出,衷心感谢!]
文章目录
  1. 1. ODBC vs JDBC
  2. 2. JDBC操作数据库流程
  3. 3. 总结