对象持久化

来源:互联网 发布:淘宝运营面试问题 编辑:程序博客网 时间:2024/04/19 23:04
 

什么是持久化?简单地说,持久化就是把数据同步保存到数据库或某些存储设备中。在软件的分层体系结构中,持久化层是与数据库打交道的逻辑层。在数据库中对数据的增加、删除、查找和修改操作由持久化层来处理。最常见的操作是在ASPJSP的页面代码中混杂了很多连接数据库、操作数据库的语句,这实际上是把持久层和展现层耦合到了一起,使得代码的编写和维护都很困难,程序结构非常不清晰。对于大型应用,这个问题尤为突出,因此,对系统结构进行分层,是解决这种耦合问题的前提保障。

Java程序员对使用JDBC进行数据库编程不会感到陌生。实际上,使用JDBC对数据库中的数据进行增加、删除、修改的操作就是持久化的过程。然而,直接使用JDBC作为持久层有一些难以解决的问题。

1.1 JDBC面临的一些问题

常规访问数据库的方法是直接使用JDBC。而在实际的应用中它有一些缺点,详细的解释见下面内容所述。

1.1.1 JDBC简介

JDBC是一种用于执行SQL语句的Java APIJDBC本身是个商标名而不是一个缩写字,然而,JDBC常被认为是代表“Java数据库连接(Java Database Connectivity)”),它由一组用Java编程语言编写的类和接口组成。JDBC为工具/数据库开发人员提供了一个标准的API,使他们能够用纯Java API来编写数据库应用程序。

有了JDBC,向各种关系数据库发送SQL语句就是一件很容易的事。换言之,有了JDBC API,就不必为访问Sybase数据库专门写一个程序,为访问Oracle数据库又专门写一个程序,为访问Informix数据库又写另一个程序等。只需用JDBC API写一个程序即可,它可向相应数据库发送SQL语句。而且,使用Java编程语言编写的应用程序无需去考虑要为不同的平台编写不同的应用程序。将JavaJDBC结合起来将使程序员只需写一遍程序就可让它在任何平台上运行。

简单地说,JDBC可做3件事:

q     与数据库建立连接。

q     发送SQL语句。

q     处理结果。


1.1.2 繁琐的代码问题

看一个插入对象的基本例子,这里增加一个用户对象。

public void addAccount(final Account account) throws DAOException,

AccountAlreadyExistException {

    final Connection  conn = getConnection();

   PreparedStatement pstmt=con.prepareStatement("insert into account values(?,?,?,?,?,?,?,?,?)");

   pstmt.setString(1, account.getUserName());

   pstmt.setInt(2, account.getPassWord());

   pstmt.setString(3, account.getSex());

   pstmt.setString(4, account.getQq());

   pstmt.setString(5, account.getMsn());

   pstmt.setString(6, account.getTel());

   pstmt.setString(7, account.getAddress());

   pstmt.setString(8, account.getHeight());

   pstmt.setString(9, account.getEnglevel());

   pstmt.execute();

   conn.Close();

}

这个操作很简单,就是把增加一个Account对象的对象逻辑转化为一个insert操作,即实现最简单的Account对象到数据库表account的映射。上面是由开发人员手工编写JDBC代码来实现。如果Account对象的属性有上百个,就要写上百行的pstmt.setString()语句,很容易出错。如果有另一个对象Person,也要写相近的代码来实现增加一个Person的操作。

类似的例子和代码在大量的项目中都存在。从1996JDBC出现到目前已经9年,大量的项目开发还在重复地编写这样的代码来实现这些每个项目都需要的基本操作。开发数据访问层的工作量(开发和测试)极大,对开发人员的要求也很高。最不幸的是,在大量的项目开发中都需要重复这种繁复的数据访问层的开发,从而导致大量的人力、物力和财力都浪费在这种人力密集型的数据访问层重复开发中,而数据库操作也恰恰是项目开发中最容易出错和消耗大量时间的部分。如果在项目需要面对不同的数据库,则还需要为每个数据库开发一个专门的数据访问层。

显然这种每个项目都重复但却具备相同模式的代码是一种明显的浪费。这就催生了O/R Mapping技术的产生。ORMObject-Relation Mapping,它的作用是在关系型数据库和对象之间作一个自动映射,这样在具体的操作数据库时,就不需要再与复杂的SQL语句打交道,只要像操作对象即可,O/R Mapping工具会自动地将对象的操作转换为SQL语句操作。这样就只需要关注业务逻辑中的对象架构,而不是底层的重复性SQLJDBC代码。

1.1.3 多表连接问题

比如一个学校,在数据库中建立多张表,分别命名为班级表、学生基本信息表、学生成绩表、学生选课表、老师基本信息表和老师教授班级表,如果有一个条件查询,需要得知学生基本信息、成绩和选课信息,则要在SQL语句中用连接查询3个表。这无可非议。关键这种复杂的SQL语句相当难以维护。当彻底删除一个学生资料时,需要把数据库中有关此学生的资料都删除,在此需要根据学生id把学生基本信息表、学生成绩表、学生选课表中的学生资料都删除,程序员必须知道关于学生的表有哪几张,并且对这几张表进行操作。工作原理是比较简单的,但真正实施起来,编写和维护代码都非常不便。

1.1.4 表间级联问题

仍然使用1.1.3小节中学校的例子来讲解。相关对象通俗一点讲,学校是爷爷,班级是父,学生是子。如果要删除一个班级,直接使用JDBC的话,这种操作对于程序员来说极其繁琐。需要按照以下的操作进行:

1)首先在班级信息表中找到班级,取得班级id,然后删除此班级的基本信息。

2)根据班级id删除此班级所有学生的基本信息。

3)删除每个学生所对应的资料(例如成绩表、选课表等)。

4)如果还有级联,则递归地把级联信息删除。

使用JDBC非常熟练的程序员,也不得不花费很多精力在这种级联关系的数据库操作上,而真正需要关心的业务逻辑在精力花费中的比重就降低了。这不得不说是JDBC让人头痛的地方。

1.1.5 层与层之间的耦合严重

不管是面向过程/结构的开发方法还是面向对象的开发方法,几乎所有的项目和产品的软件开发中都必须涉及关系数据库的数据存取更新等问题。和关系数据库打交道是所有项目产品开发中首先需要面对的,目前的开发技术需要为每个项目开发一个数据访问层(Data Access Layer)来作为商业逻辑代码的基础,如图1-1所示。

1-1 系统的分层结构

JDBC实际上是数据访问层,通过它访问和操作数据层的数据库。然而,作为数据访问层的编程人员,必须要很熟悉SQL语句,并且对底层的数据表了如指掌,例如哪个表中有什么字段以及字段的意义等程序员都必须知道。这样的话,经常的做法是,程序员本身兼职DBA。因为此时数据访问层和数据层是密不可分的。

使用ORM技术则不会存在这个问题。数据层完全被隐藏了,暴露在程序员面前的只是一些JavaBean对象,在程序中通过JavaBean的属性来间接操作数据表中的字段。当然这种操作的转换是由ORM产品来完成的,对于程序员来说,只是简单地做了一些getXXXsetXXX方法就可以完成对关系数据表的操作。

程序员不用知道什么时候数据库从MySQL移到DB2,或是移到SQL Server 2000,也不用知道数据库名是什么,表名是什么,字段是什么,对于他来说这没有关系。甚至程序员可以不用知道SQL语句,可以不懂数据库的任何知识。只需要在适当的时候,调用JavaBeansetXXXgetXXX方法,就可以取得和改变数据表字段的值;对于程序员来说,JavaBean就是存储信息的实体,对JavaBean的操作,就是对信息实体的操作(实际上是数据表)。

如此一来,设计人员只要把系统框架搭出来,把一些JavaBean交给程序员去实现业务逻辑即好。这样的结构比程序员调用JDBC去操作数据库要清晰优雅得多。

1.1.6 性能问题

对于insert操作,普通JDBC程序员这样来写:

pstmt = conn.prepareStatement("insert into user_info values(?,?)");

for (int i=0; i< list.length; i++) {

pstmt.setString(1,(User)list.get(i).getName());

pstmt.setString(2, (User)list.get(i).getPwd());

pstmt.executeUpdate();

}

如果批量插入n条记录,那么就是n次向数据库发送SQL语句。而ORM技术的Hibernate则是采用了JDBC 2.0batch功能。

for (int i=0; i<list.length; i++) {

pstmt.setString(1,(User)list.get(i).getName());

pstmt.setString(2, (User)list.get(i).getPwd());

pstmt.addBatch();

}

pstmt.executeBatch();

只是1次向数据库发送SQL,性能高下判若分明。update操作与此类似。

select操作可以使用JCS缓冲查询结果,重复查询比JDBC肯定要快很多;分页操作可以使用数据库的分页SQL,或者使用JDBC2.0scroll result

另外,Hibernate总是尽量延迟向数据发送SQL,它会先把SQL语句缓冲在Session的缓冲区中,最后在flush时一次性的向数据库发送。

总体来说,当使用Hibernate时,相当于有一个JDBC高手来帮你优化JDBC调用,那点封装了一层的损失可以忽略不计。


1.2 使用ORM

在时下的软件开发中,ORM这个词组越来越多地被人们提起。前面已经介绍了,ORM是对象到关系的映射,它把对表直接进行的操作变成对持久化类的属性和方法的直接操作,ORM层作为分层体系中的持久层。

面向对象的开发(Object-Oriented)在近年已广为普及,成为软件开发最基本的方法。开发语言也出现了专门为面向对象开发的编程语言,像目前主流的JavaC#都是基于面对象开发设计的编程语言。实践证明,面向对象是一种能有效提高开发质量、易于管理和符合人的思维模式的开发方法。

在面向对象的开发中,数据访问层最基本的数据库操作包括插入对象、删除对象、更新对象和查询对象。几乎所有的项目开发都需要首先需要实现这些原子操作。而这些操作在几乎所有的项目开发中都很类似,都是把插入对象、删除对象、更新对象和查询对象的对象级别的操作转化为底层关系数据支持的insertdeleteupdateselectSQL操作。然而,如果在程序中直接调用JDBC来实现这种转化的操作,程序员不得不面对大量繁杂的SQL代码,不能专注于业务逻辑的编程,而且容易出错;将来对系统进行维护时,面对长串的SQL语句,就算是程序员自己写的代码,也要花费一定时间才能了解这些语句的来龙去脉。若使用ORM来作为数据访问层,问题就会迎刃而解。

下面使用ORM技术来增加一个用户对象Account,这里使用开源的Hibernate

public void addAccount(final Account account) throws DAOException,

ccountAlreadyExistException {

  Connection conn = DataSource.GetConnection();

  sessionFactory  sf= new

Configuration().configure().buildSessionFactory();

Session s= sf.openSession();

Transaction t = s.beginTransaction();

s.save(account);

t.commit();

}

对比后可以体会到代码量的巨大差别。而这个还只是一个最简单的插入单个对象的情况。在插入多个对象,特别是插入多个关联对象的情况下,更加可以看到ORM的优势。在保存对象时,可以将该对象所引用的所有对象自动都保存到相应的数据表中,而不需要每个对象都做保存的操作。

Session s= sf.openSession();

Transaction t = s.beginTransaction();

Banji ban=new Banji();

Student stu1=new Student("张三","3","89");

Student stu2=new Student("李四","12","74");

Student stu3=new Student("赵五","5","92");

Set students=ban.getStudents();

students.add(stu1);

students.add(stu2);

students.add(stu3);

s.save(ban);                                                   //班级中的学生被级联添加了

t.commit();

如果采用传统的手工SQL JDBC方式编码,代码量可以多出近5倍。加上测试节省的时间,采用ORM技术可以极大地提高开发效率和开发时间,同时开发质量也更容易得到 保证。

1.3 软件分层体系结构

在分层体系模式中,最常见、最简单也最有效的就是MVCModelViewControl)模式。MVC模式强行把输入输出、数据实体和控制器相分离。

q     Control相当于整个系统的大脑,它负责思考数据如何取得、如何处理以及数据流向何处。

q     数据实体可以是数据库,也可以是经过ORM实现了的数据对象,数据实体仅在控制器进行调用和处理,然后在展现层显示所携带的数据信息。

q     MVC中的展现层功能很单一,它只是负责将得到的数据进行显示,或是收集数据(用户填写的表单)传送给控制器进行处理。展现层是比较“笨”的,它不能完成一些“聪明”的思考工作。比较高级的逻辑处理由控制器来完成。这意味着展现层不可以实现权限控制(例如每页面顶部加入代码验证某用户是否有查看此页面的权限),不可以实现业务逻辑操作(例如用户取款100元,它不可以在用户余额里减去100,而应当把这100元的数据传给控制器,由控制器来完成扣款的操作)。

几年前,当ASP大行其道时(如果读者有过ASP编程经验就会了解,虽然有了COM技术,但常见的ASP代码仍然是所有操作都写在一个页面代码中,无分层可言。使用JSP当然也可以这样,不过在JSP中分层显得要容易一些),系统结构经常是在应用程序中直接访问数据库,将持久化操作、业务操作和展现数据混合在一个页面代码中,结构如图1-2所示。

在这种模式中,应用程序做了一些“聪明”的控制工作。在MVC模式中,这些“聪明”的工作都将由Controler来处理。View层只是负责输入、输出数据,View只是一个没有思想的只负责数据显示或传递的工具,而操纵它、具有思想的就是MVC中的大脑——Controler。如果使用MVC实现同样的系统,可以如图1-3所示来进行系统分层。

MVC的主要思想是用一个或多个Servlet作为控制器(在J2EE开发中),请求由Servlet接收后,经Servlet处理后再发送给JSP页面。可以这样理解:Servlet接收一些原始的数据,进行操作后将得到的成果送给JSP页面显示(而在JSP+JavaBean模式中,JSP接收原始数据,请求JavaBean处理后,JSP自己再把它显示出来)。在Servlet作为控制器时,每个Servlet通常只完成一小部分功能,但多个Servlet组合起来就可以完成复杂的功能,而且这样重用性很好(副作用是会导致回应请求的时间加长)。在此模式中,JavaBean只是充当数据实体对象,它定义了对象的属性和方法,它也是“笨”的,仅仅是数据的容器,不知道有任何的控制方法。整个模式体系完全由Controler一手操控,可以这么说:Controler负责从JavaBean取数据,然后Controler再把取到的数据发给JSP页面进行显示。

                         

1-2 应用程序层直接访问底层数据库                  1-3 MVC模式的系统分层

事实上,如图1-3所示的分层结构中的控制器层还可以更细化一点,拆分为业务逻辑层和数据持久化层。如果就如图1-3所示的结构,以JDBC编程为例,在控制器层必须写很多的JDBC句来完成底层数据的同步操作,而且不同的数据库有不同的特性。如果系统移植到其他的数据库上,控制器层的一些代码可能需要重写。有一点也很重要,就是在控制器层混杂了业务逻辑操作的代码。例如,用户取款100元,在控制器层则进行了如下操作:

1)用SQL语句取得用户账号信息。

2)判断用户账户是否有100元可取,以及此账号是否已冻结。

3)在内存中进行操作,将用户余额减去100元。

4)用SQL语句将新的用户余额更新到数据库。

1-4 加入了持久层的控制器

上面第(2)和第(3)步是业务逻辑操作,与数据库无关。对程序员来说,不只要精通具体的业务,而且对JDBC编程也要相当熟悉,实际上这就增加了程序员学习的成本。就像是工厂里的汽车流水线,一个工人不只要会喷漆,还要精通焊接,这并没有什么不可,但从效率/成本的标准来讲,这是不值得的。因此,既然此时的控制器做了一些比较多的事,可以将这些事情提出来,放到另一个层中去,这样系统的维护性和可重用性都大大提高了,降低的是层之间的耦合性。系统结构如图1-4所示。

持久化层封装了底层的数据操作细节,为业务逻辑层提供了面向对象的API,持久层的意义是:

q     代码重用性高,能够完成所有的数据访问操作。

q     具有相对的独立性,底层数据库改变,只需修改持久层代码,只要对上提供的API不变,则上层的业务逻辑层可以不用修改。

Hibernate作为一个中间件,实现的就是持久层功能。用户不必关心其内部是如何实现的,只要知道Hibenrate提供了哪些API,在需要时进行调用即可。


1.4   

模型就是模拟实现,是抽象。抽象带来的好处是能够反映模型中元素之间的关系,清晰把握大局。如果不惜代价,最好的模拟实现是造出真正的目标实物,然后根据实际情况对目标实物进行修改。这种模拟实现代价昂贵,而且周期很长,因此在实际生活中极少采用。这样才会有了一些造价低廉的模型来模拟实体的行为,比如盖楼,先由工程师画出图纸,进行研究确保无误后才会施工;如果图纸有问题,则重新设计一张即可,虽然有成本,但比起盖好楼再拆掉重盖,成本就小得多了。

在软件设计阶段,使用域模型来模拟真实的实体对象,域模型是面向对象的,它由以下的内容组成:

q     状态和行为(和普通的JavaBean一样)。

q     域对象之间的关系。

构成域对象的基本元素是域对象(Domain Object),它可以分为实体域对象和过程域对象。

1.4.1 实体域对象

这是最容易理解的对象形式。常见的实体对象有两种:主动域对象(EJB)和JavaBean对象(也称为POJOPlain Old Java Object)。JavaBean对象是主动域对象的子集。实体域对象代表真实世界中的物质实体,如人、时间、地点、事件等。在一个学校中可以把学校、班级、课程、学生都作为单独的实体对象,它仅仅是数据的载体,不包括业务逻辑方法(如果是JavaBean对象)。在实体对象中需要设置一个惟一的对象id来标识此对象(不是必须),以使程序能区别不同的对象。

JavaBean对象包括一些属性和相应的get(set)方法。例如,一个学生的实体对象的代码可以如下:

package model;

public class Student {

   private String id;                           //标识id

   private String cardId;                   //学号

   private String name;                            //学生姓名

 

   public String getName() {

       return name;

   }

   public String getCardId() {

       return cardId;

   }

   public void setId(String id) {

       this.id = id;

   }

   public void setName(String stuName) {

       this.name = stuName;

   }

   public void setCardId(String cardId) {

       this.cardId = cardId;

   }

   public String getId() {

       return id;

   }

}

主动域对象在JavaBean对象的基础上封装了此JavaBean访问数据库的方法。例如,Student对象的实体对象如果是主动对象,则它的代码可能如下:

package model;

public class Student {

   private String id;                           //标识id

   private String cardId;                   //学号

   private String name;                            //学生姓名

   //省略get(set)和显示的构造方法

   /*--创建学生对象,在数据库中插入属性值--*/

        public void create(){

                  //DatabaseUtil是辅助类,目的是通过JDBCAPI取得相应的数据库连接

                  Connection conn=DataBaseUtil.getConnection();

                  PrepareStatement pstmt=null;

                  try{

                  //JDBC的自动提交事务设为false,意味着操作结束时必须手动提交事务才能生效

                  conn.setAutoCommit(false)

                  //将显示构造函数中取得的属性值插入数据

                  pstmt=conn.prepareStatement("insert into student values (?,?,?)");

                  pstmt.setString(1,id);

                  pstmt.setString(2,cardId);

                  pstmt.setString(3,name);

                  pstmt.execute();

                  conn.commit();

                  }catch(Exception e)            //如果出现例外,则回滚操作

                  {conn.rollback();}

                  finally{

                    try{

                           ptmt.close();

                           conn.close();

                    }catch(Exception e){

                           e.printStackTrace();

                    }

          }

        }

        public void delStu(){……}                   //删除学生对象

        public void updateStu(){……}            //修改学生对象

        public void getStuById(){……}           //从数据库中根据学生id加载一个学生对象

 

        public List getStuByName(){……}    //从数据库中根据学生姓名加载一个学生对象集合

}

可以看出,主动域对象封装了它自身与数据库的持久化操作,这样,过程域对象就可以脱离相应的数据库操作,专心致力于业务逻辑。然而主动域对象的缺点也是明显的,在对象的实现中依然使用的是JDBCAPI,这样JDBC的问题就会显露。而且这样的对象移植性比较差,不同的数据库对SQL语句有不同的支持。在以前版本的MySQL数据库就不支持子查询,如果在基于Oracle的数据库中写了子查询,移植到MySQL上就会出错(现在新版本的MySQL已经支持子查询了)。

1.4.2 过程域对象

过程域对象代表应用中的业务逻辑操作,或者也可以把持久层的数据操作放入过程域中。这样,过程域对象就不只有业务逻辑,还有对象的数据持久操作(平时所熟悉的xxxHelper类、xxxUtil类等就是如此)。在JSP中的JSP+JavaBean模式中,控制类JavaBean充当的就是过程域的对象,此时过程域的作用如图1-5所示。

如果把过程域中的数据持久化操作抽取出来,放到实体对象中,实体对象就不只是数据载体了,它还负责将本身对象的属性同步持久到数据库中。

如果把过程域中的数据持久化操作抽出来,作为单独的一个层,专门负责数据的访问和持久化,则这一层可以使用JDBC来完成,但是使用ORM产品会优雅和方便得多,结构如图1-6所示。

               

1-5 JSP+JavaBean模式中过程域的作用         1-6 抽出了数据持久操作的MVC结构

将持久化操作从过程域对象或实体对象中抽取出来另作一层,这种结构层与层之间耦合低,移植性高。例如,如果数据库改变,则只需修改持久层的相应代码即可,过程域和实体域对象不用做任何修改。在一个项目中,这样的分层体系也适合于分工合作,如有人写持久层代码,向控制器层提供接口,另有人根据持久层提供的接口写控制器代码。

想了解更多java相关知识,请加入53587861高级java群,验证:阿春.共同探讨未来的新技术.

原创粉丝点击