数据库接口及实现类的设计
本节讲解项目中数据持久层的设计。数据持久层是指一个项目中专门负责将数据持久化保存的业务层,初学者可以将其简单地理解为 “专门负责增删改查的功能模块”。
本系统采用设计模式中的接口模式来实现持久层的设计,通过接口确定业务范围,再由实现类来实现具体细节,这样做的好处是同一套业务可以有多种实现方式。例如:同样是读取所有员工的打卡记录,既可以从主数据库中读取,也可以从备份数据库中读取;可以从 MySQL 数据库中读取,也可以从 SQLite 数据库中读取等。想要实现这样看似复杂的功能,实际上只要针对接口写不同的实现类即可。
数据库接口
com.mr.clock.dao 包下的 DAO.java 为数据库接口。要注意 DAO 是一个接口(interface)而不是一个类(class)。
DAO 是 Data Access Object(数据访问对象)的缩写,它定义了程序有哪些访问数据库的行为,并将这些行为封装成了抽象方法。程序究竟要如何访问数据库?访问哪种数据库?如何编写 SQL 语句?这些具体的问题需要交由具体的实现类来解决。DAO 是行为的制定者,而不是执行者。DAO 接口定义的抽象方法及其说明如下:
public interface DAO {
}
基于 MySQL 数据库的接口实现类
24.7.1节的 DAO 接口已经设计好了抽象方法,本节就来讲解如何编写一个基于 MySQL 数据库的接口实现类。
com.mr.clock.dao 包下的 DAOMysqlImpl.java 为基于 MySQL 的数据库接口实现类,该类通过 implements 关键字实现了 DAO 接口,因此该类需要实现 DAO 接口所有的抽象方法。DAOMysqlImpl 类的声明代码如下:
public class DAOMysqlImpl implements DAO
因为很多抽象方法的实现代码都很相似,所以本节将只讲解具有代表性的实现方法。
查询
getEmp(int id) 方法是通过员工编号查询员工详细信息的方法,方法参数是被查询员工的编号。
getEmp(int id) 方法首先会设计一个待执行的 SQL 语句,这是一个 select 指令,SQL 语句中的 “?” 是占位符,稍后会为其赋值;然后通过 JDBCUtil 数据库工具类获取数据库连接,并创建执行 SQL 语句的接口,将方法参数传入的 id 值赋值给 SQL 语句中的占位符,这样就获得了一个完整的 SQL 语句;最后执行 SQL 语句,如果有查询结果,就根据结果中的 name 字段值和 code 字段值创建一个员工对象,并返回,查不到结果就返回 null。别忘了在异常处理之后要及时关闭结果集和执行 SQL 语句的接口。getEmp(int id) 方法的具体代码如下:
@Override
public Employee getEmp(int id) {
}
添加
addEmp(Employee e) 方法是向数据库中添加新员工信息的方法,方法参数是新员工对象。
addEmp(Employee e) 方法首先会设计一个待执行的SQL语句,这是一个 insert 指令;然后创建数据库连接、执行 SQL 语句的接口,并将参数中的员工信息填充到 SQL 语句中;最后执行 SQL 语句,完成添加。因为 insert 指令不涉及查询结果,所以不使用 ResultSet 对象。addEmp(Employee e) 方法的具体代码如下:
@Override
public void addEmp(Employee e) {
}
删除
deleteEmp(Integer id) 方法是删除数据库中某个员工的所有信息的方法,方法参数是被删除员工的编号。
deleteEmp(Integer id) 方法首先会设计一个待执行的SQL语句,这是一个 delete 指令;然后创建数据库连接、执行 SQL 语句的接口,并将参数中的员工编号填充到 SQL 语句中;最后执行 SQL 语句,完成删除操作。同样,因为 delete 指令不涉及查询结果,所以不使用 ResultSet 对象。deleteEmp(Integer id) 方法的具体代码如下:
@Override
public void deleteEmp(Integer id) {
}
修改
updateWorkTime(WorkTime time) 方法是更新数据库中的作息时间数据的方法,方法参数是更新之后的作息时间。
updateWorkTime(WorkTime time) 方法首先会设计一个待执行的 SQL 语句,这是一个 update 指令;然后创建数据库连接、执行 SQL 语句的接口,并将参数中的员工编号填充到 SQL 语句中;最后执行SQL语句,完成更新操作。同样,因为 update 指令不涉及查询结果,所以不使用 ResultSet 对象。updateWorkTime(WorkTime time) 方法的具体代码如下:
@Override
public void updateWorkTime(WorkTime time) {
}
复杂的查询实现
getAllClockInRecord() 方法用来获取所有员工的打卡记录,因为这个数据量是不断变化的,返回结果是一个二维数组,所以需要一些特殊手段对查询结果进行处理。
因为打卡记录的数量不确定,所以先创建一个长度可变的 HashSet 哈希集合来保存所有查询结果。集合中的元素是一个一维数组,0 索引保存打卡的员工编号,1 索引保存打卡的具体时间。当所有结果都读取完毕之后,根据集合的元素个数创建二维数组,再将每一个元素赋值给二维数组,这样就得到了一个与数据库中 t_lock_in_record 表中数据完全相同的二维数组。getAllClockInRecord() 方法的具体代码如下:
@Override
public String[][] getAllClockInRecord() {
}
数据库接口工厂类
工厂类是接口模式的一个重要组成部分,工厂类专门负责创建数据库接口实现类对象。这样做的好处是前端业务代码在调用数据库接口时,调用者可以不知道也不关心自己使用的接口对象是由哪个类实现的。一个项目中可能有几个甚至十几个接口实现类,但具体采用哪种实现类,工厂类具有决定权。
com.mr.clock.dao 包下的 DAOFactory.java 为数据库接口工厂类,其具体代码如下:
public class DAOFactory {
public static DAO getDAO() {
return new DAOMysqlImpl(); // 返回基于 MySQL 数据库的实现类对象
}
}
从上述代码中可以看到工厂类的代码非常少。本系统的工厂类返回的是基于 MySQL 数据库的实现类,开发者如果想要切换成其他数据库,只需在 getDAO() 方法中将返回的对象改为基于其他数据库的实现类即可,前端业务代码无须做任何修改。