在这里我们以图书管理系统中,某个学生借出一本书这里简单的业务逻辑为例,详细业务过程如下:学生借出一本书,若book表中相应书籍的库存(inventory字段)大于1,则在loan表中插入一条借出记录,并且在book表中相应书籍的库存(inventory字段)减1,然后返回true;否则返回false。 这里只是为了说明持久层的演化过程,因此例子中并没有考虑事务处理和必要的异常数据检查。
在这个模式,数据持久化代码和业务代码混合在一起,代码难以阅读、维护和扩展,很难从代码中分辨出业务逻辑。但是这个模式也有其开发效率高,实现便捷的优点,对于原型系统或者小型的应用系统这些需要快速实现,而后期维护工作量少的系统还是具有一定吸引力的。
大家感受随意感受下下面的代码:
//借阅一本书
public boolean borrow(String studentId, String bookId){
String driver = "com.mysql.jdbc.Driver";
String getInventory = "select inventory from book where bookId = ?";
String updateInventory = "update book set inventory = ? where bookId = ?";
String insertLoan = "insert into loan(studentId, bookId) values(?, ?)";
Connection con = null;
try {
Class.forName(driver);
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_db","username","password");
//获取图书现在的库存
PreparedStatement getInventory_stmt = con.prepareStatement(getInventory);
getInventory_stmt.setString(1, studentId);
ResultSet rs = getInventory_stmt.executeQuery();
int inventory = rs.getInt("inventory");
if(inventory >= 1){
//插入loan数据
PreparedStatement insertLoan_stmt = con.prepareStatement(insertLoan);
insertLoan_stmt.setString(1, stadentId);
insertLoan_stmt.setString(2, bookId);
insertLoan_stmt.executeQuery();
//更新book表是库存
PreparedStatement updateInventory_stmt = con.prepareStatement(updateInventory);
updateInventory_stmt.setInt(1, inventory-1);
updateInventory_stmt.setString(2, bookId);
updateInventory_stmt.executeQuery();
return true;
}else{
return false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭PreparedStatement和Connection
}
return false;
}
上面代码虽然可以实现业务逻辑,但是逻辑代码和数据库处理代码紧密耦合在一起,不利于代码的阅读和维护。同时,如果项目中都使用这种代码,即使在配置文件中统一管理数据库连接地址和用户名密码等信息,若某天要将数据库相关代码改造成使用数据库连接池获取数据库连接,这修改量估计能让开发人员想狗带。 面对上面的问题,可以写一个Util类,用于获取数据库连接。
public class DBUtil{
//获取数据库连接
public static Connection getConnection(){
String driver = "com.mysql.jdbc.Driver";
Connection con = null;
try {
Class.forName(driver);
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_db","username","password");
}catch(Exception e){
e.printStackTrace();
return null;
}
}
//释放数据库连接
public static void releaseConnection(Connection con){
try{
if(con != null){
con.close
}
}catch(Exception e){
e.printStackTrace();
}
}
}
进行了这个改造后,获取Connection的代码可以在DBUtil类中统一管理了,可以解决上面提到的问题,此时要修改数据库连接的获取方式仅需修改DBUtil类相应的getConnection和releaseConnection方法即可。
//借阅一本书
public boolean borrow(String studentId, String bookId){
try {
con = DBUtil.getConnection();
//获取图书现在的库存
...
//插入loan数据
...
//更新book表是库存
...
//释放链接
DBUtil.releaseConnection(con);
}
但是即使是进行了这样的改造,数据库的操作代码和业务代码依然是紧密耦合在一起,代码逻辑依然是混乱难以阅读和维护,此时可以使用DAO模式来解开这种耦合关系
DAO(Data Access Object)模式,实际上是Data Accessor模式和Domain Object模式,Data Accessor模式实现了数据访问和业务逻辑的分离,Domain Object实现了业务数据的对象化封装,将业务数据抽象封装成业务对象,调用端可以直接基于有业务意义的业务对象进行处理,而不是直接处理缺乏业务意义的原始RecordSet数据集。
DAO提供一层数据访问逻辑抽像,为上层调用提供抽象化的数据访问接口,向上层屏蔽了下层JDBC处理的复杂性。业务层只关心业务逻辑即可,不需关心实际的JDBC处理。逻辑清晰的分离开了数据存储和业务逻辑代码。
如下面代码所示,此时的业务逻辑代码已经变得清晰易懂了。
//借阅一本书,业务逻辑代码
public boolean borrow(String studentId, String bookId){
Book book = BookDao.getBook(bookId);
if(book.getInventory() >= 1){
Loan loan = new Loan(studentId, bookId);
book.setInventory(book.getInventory()-1);
BookDao.update(book);
LoanDao.addLoan(loan);
return true;
}
return false;
}
//Data Accessor
public BookDao{
public static Book getBook(String bookId){
...通过JDBC查询数据,并且将数据封装成Book对象
}
public static void updateBook(Book book){
...将book对象组装成update语句,更新book表数据的JDBC处理逻辑
}
}
public LoanDao{
public static addLoan(Loan loan){
...将Loan对象组装成insert语句,插入到loan表的JDBC处理逻辑
}
}
//Domain Object
//book表
public class Book{
private String bookId;
private int inventory;
...其他业务上必要的字段
public void setBookId(String bookId){
this.bookId = bookId;
}
public String getBookId(){
return bookId;
}
public void setInventory(int inventory){
this.inventory = inventory;
}
public int getInventory(){
return inventory;
}
...
}
//loan表
public class Loan{
private String bookId;
private String studentId;
public Loan(String studentId, String bookId){
this.studentId = studentId;
this.bookId = bookId;
}
...getter和setter方法
}
基于这种DAO模式,在项目比较小,持久化处理代码不多的项目中,这种模式不仅代码结构清晰,而且开发效率高。
但是当项目越来越大,业务领域对象数量不断增长时,我们还是需要在DAO类中编写大量的JDBC处理代码,以及需要进行大量的Domain Object和JDBC查询数据集间相互转换工作(如上例子中两个Dao类的方法)。由开发人员来处理这大量的重复工作,不仅繁冗乏味而且容易出错。 得益于开源软件雄厚力量,我们现在可以使用Hibernate、iBatis等第三方ORM框架来帮助我们解决上面提到的问题,这些第三方框架可以将我们从繁冗的JDBC处理和Domain Object 转换工作中解放出来,让开发人员可以专注于系统的业务逻辑中。
以Hibernate为例,Hibernate对底层的JDBC代码进行了一层封装,我们只需定义 Domain Object 并且在配置文件或者通过注解配置Domain Object 和数据库表之间的映射关系,Hibernate 即会为我们自动进行获取数据库连接、执行SQL(使用HQL时还会自动的将 Domain Object 和 SQL 表、字段进行互相转换)、关闭数据库连接等繁冗乏味的工作。使得开发人员可以面向Domain Object进行持久化操作和专注于业务逻辑的实现。当然,除了上面提到的功能,Hibernate还提供了缓存管理,事务管理等其他功能。