现在的位置: 首页 > 综合 > 正文

Eclipse从入门到精通(第2版)

2019年03月29日 ⁄ 综合 ⁄ 共 28447字 ⁄ 字号 评论关闭
文章目录

 

37.2  Hibernate的下载和安装

37.2.1  下载

具体步骤如下:

(1)如图37.2所示,访问Hibernate官方网站http://www.hibernate.org,单击左边菜单的Download超链接,在下载页面中下载Hibernate Core和Hibernate Tools。前者是Hibernate的核心软件包,后者是一个用于辅助Hibernate开发的Eclipse插件。

图37.2  Hibernate的下载页

(2)直正的下载页面会转到著名开源社区sourceforge.net,如图37.3所示。选择下载ZIP格式压缩包hibernate-3.2.2.ga.zip,解压后的目标结构如图37.4所示。

     

图37.3  下载文件选择                图37.4  Hibernate解压后的目标结构

主要目录及文件解释:

  ● hibernate3.jar文件是Hibernate的核心jar包。

  ● lib目录中有一些Hibernate运行需要依赖的第三方jar包,安装时也要用到。

  ● src目录中是Hibernate(hibernate3.jar)的源文件。

  ● etc目录中有一些可以参考的例子文件。

  ● doc目录中包含Hibernate文档。

37.2.2  安装

1.复制jar包

将解压目录中的hibernate3.jar和lib目录下的jar包,全部复制到项目的Web-INF/lib目录,如果提示有同名文件(commons-logging-1.0.4.jar),覆不覆盖则都一样。其他说明如下:

  ● 其实并不需要复制lib目录下的所有jar包,本文只是为了安装上的方便。如果在正式发布程序时,希望只包括真正用到的包,则可以参考解压目录lib中的_README.txt,里面有详细描述。或者参考Hibernate文档,里面也有部分描述。

  ● 注意不要将这些jar包复制到%TOMCAT_HOME%/common/lib目录下,那是Tomcat全局库所在目录,有可能引起包冲突。

  ● 检查一下lib目录中是否有重复包(不同版本),如有,则只保留一个最新版的包,否则很可能会引起类冲突。因此要把antlr-2.7.2.jar删除,保留antlr-2.7.6.jar。

2.创建log4j.properties

Hibernate用log4j包来做日志输出,这就要求项目中创建一个log4j的配置文件log4j.properties,否则有些运行日志就无法看到(不会影响程序运行),另外Eclipse控制台视图会输出如下两条警告信息。

log4j:WARN No appenders could be found for logger (org.apache.catalina.startup.TldConfig).

log4j:WARN Please initialize the log4j system properly.

如果读者熟悉log4j,可以自己创建log4j.properties,定义自己想要的日志配置。如果不熟悉log4j,可以直接将解压目录etc下的log4j.properties,复制到项目的“Java Resourcess:src”下。注意,由于J2EE透视图拒绝直接复制文件到“Java Resourcess:src”下,所以可以转到Java透视图再复制。

37.3  一个简单的Hibernate实例(V005)

37.3.1  创建Hibernate配置文件:hibernate.cfg.xml

在“Java Resourcess:src”下创建一个hibernate.cfg.xml文件如下。

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

         <session-factory>

                   <!-- 数据库 -->

                   <property name="connection.datasource">java:comp/env/jdbc/mysql</property>

                   <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

                   <property name="show_sql">true</property>

         <!-- 打开Hibernate的session自动管理机制 -->

         <property name="current_session_context_class">thread</property>

                   <!-- 把所有*.hbm.xml文件注册在这里 -->

                   <mapping resource="cn/com/chengang/sms/model/model.hbm.xml"/>

         </session-factory>

</hibernate-configuration>

配置说明:

  ● connection.datasource设定所用的连接池。

  ● dialect告诉Hibernate使用哪种SQL数据库方言(dialect)。不同数据库的SQL语法都有一些差异,Hibernate会根据设置的方言来适应这些差异。想知道其他数据的方言名称,可以利用Eclipse的代码提示功能,在Java程序中输入“org.hibernate.dialect.”然后按“Alt+/”快捷键。

  ● show_sql设定在控制台是否显示Hibernate生成的SQL语句,开发期间设为true,便于调试。

  ● model.hbm.xml是一个XML映射文件,这个文件创建在model目录下(内容将在以后给出)。注意,这里用的是相对路径,cn字串前面是没有“/”的。

  ● hibernate.cfg.xml还有一种hibernate.properties的写法,在Hibernate的解压目录etc可找到它的例子。两种写法选一种即可,本文选前一种。

  ● 某些属性对Hibernate的性能影响很大,比如batch_size项设置成0和30,性能相差会有4倍以上。属性会有一个默认值,但如果所开发的项目需要作性能优化,则可根据实际情况来重新设置。如果想了解hibernate.cfg.xml中更多的属性设置,可以参考Hibernate文档的“表3.3  Hibernate配置属性”,那里有属性的说明和建议值,本文不再复述。

37.3.2  创建XML映射文件:model.hbm.xml

Hibernate之所以能够智能地判断实体类和数据表之间的对应关系,就是因为有XML映射文件。本小节先在cn.com.chengang.sms.model包下创建一个名为model.hbm.xml的XML映射文件,其内容如下。

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <class name="cn.com.chengang.sms.model.Grade" table="grade">

                   <id name="id">

                            <generator class="identity"/>

                   </id>

        <property name="name"/>

    </class>

</hibernate-mapping>

配置说明:

  ● model.hbm.xml可以任意命名及放置于其他目录下,当然hibernate.cfg.xml文件也要做相应修改。笔者建议将它和它所对应的实体类放在一个包下,并用包名做文件名。

  ● <class>项定义了实体类和数据表之间的关系:name是实体类(用类全名),table是对应的数据表(表名不分大小写)。可以省略掉table属性,这时默认表名和实体类同名。在model.hbm.xml文件中可以设置多个<class>项,笔者建议将一个包中的所有实体类都集中在一个*.hbm.xml文件中。

  ● <id>项定义了主键id字段所用的键值生成方法,identity是一种MSSQL、DB2、MySQL通用的主键值生成方法(Oracle不能用identity,可换成sequence)。要了解更多内容,可以查阅Hibernate文档。

  ● <property>子项定义了实体类和表字段的关联。本例只设置了定义类字段的name属性,还有一个column属性是定义数据库表字段名的,本例没有设置。Hibernate正是通过这里的设置建立起实体类和数据库表之间的字段对应关系。本例没有设置column,则Hibernate会默认为它和name属性同名。假如,想将Grade实体类的name字段对应于数据库表的grade_name字段,并将表字段的长度定义成16、不允许空值,则可以按照如下设置:

<property name="name">

      <column name="grade_name" length="16" not-null="true"/>

</property>

  ● <property>体现Hibernate的友好性:它可以设置得很详细,也可以很简洁,当设置简洁时,Hibernate会采用默认值。要了解更多关于<property>设置的内容,可以参阅Hibernate文档。

37.3.3  创建HibernateUtil类

HibernateUtil类用于得到一个SessionFactory,而SessionFactory可以得到Session。Session是Hibernate中最重要和使用最频繁的一个对象,实体对象都是通过它来和数据库交互。这个Session和JSP的Session不同,但是有一点类似于JDBC的Connection,即Session比Connection包含的内容更多,功能范围更广。在Hibernate的编程中将不会再使用Connection,而是通过Session来和数据库交互。

HibernateUtil类的内容如下:

package cn.com.chengang.sms.db;

import org.hibernate.SessionFactory;

import org.hibernate.cfg.Configuration;

public class HibernateUtil {

         private static final SessionFactory sessionFactory;

         static {

                   try {

                            // 实例化一个SessionFactory对象

                            sessionFactory = new Configuration().configure().buildSessionFactory();

                   } catch (Throwable ex) {

                            System.err.println("Initial SessionFactory creation failed." + ex);

                            throw new ExceptionInInitializerError(ex);

                   }

         }

         public static SessionFactory getSessionFactory() {

                   return sessionFactory;

         }

}

程序说明:

  ● HibernateUtil可以任意取名。它是一个静态方法类,即类中的方法都是静态方法。

  ● SessionFactory是一个静态变量,它由static {…}静态代码块来初始化一个实例。注意,static{…}代码块比较特殊,它既不是方法也不是变量。生成一个SessionFactory对象很耗费时间和资源,所以在这里整个Web系统共用一个SessionFactory。而生成一个Session对象的代价很小,在编程中千万不要把Session写成单例模式来进行实例共享,对Session的使用原则是用完就关闭,而且要尽量早关闭。

  ● 对于旧版本的Hibernate,此类还有将Session保存/剥离到当前线程中的两个方法。但现在已经不需要了,因为这里在新版本的hibernate.cfg.xml中用current_session _context_class属性打开了Hibernate的Session自动管理机制。

37.3.4  创建GradeManager类

GradeManager类似于DbOperate,它主要提供数据库操作方法。在这里编写了向Grade表插入一条记录的方法,以及取出Grade表中id值大于2的所有记录的方法。代码如下:

package cn.com.chengang.sms.db;

public class GradeManager {

         public void insertGrade() throws HibernateException {

                   Session session = HibernateUtil.getSessionFactory().getCurrentSession();

                   session.beginTransaction();

                   Grade grade = new Grade();// 生成一个年级对象

                   grade.setName("高四");

                   session.save(grade); // 将这个对象保存到数据库

                   session.getTransaction().commit();

         }

         public List<Grade> getGrades() throws HibernateException {

                   Session session = HibernateUtil.getSessionFactory().getCurrentSession();

                   session.beginTransaction();

                   // 创建一个条件查询语句

                   String hql = "from Grade as g where g.id > :id";

                   Query query = session.createQuery(hql); // 创建查询对象

                   query.setInteger("id", 2); // 设置查询参数

                   List<Grade> result = query.list(); // 从数据库取出数据,并自动封装到List集合中

                   //也可以三句合为一句:session.createQuery(hql).setInteger("id", 2).list();

                   session.getTransaction().commit();// 提交

                   return result;// 返回数据集

         }

         public void close(){

                   HibernateUtil.getSessionFactory().close();

         }

}

程序说明:

  ● 在Session中,每个数据库操作都是在一个事务(Transaction)中进行的,这样可以隔离开不同的操作。Hibernate的事务是JDBC事务的更高层次的抽象,它提供了更好的灵活性和适应性。

  ● 无论session.getTransaction().commit()提交,或session.getTransaction().rollback()回滚都会自动关闭session。

  ● 以前都是将实体对象的字段一个个拆散并组合成SQL语句,或者从数据库取出数据后将字段值一个个封装到实体对象。现在用了Hibernate,就再也不必这么处理数据了,这是Hibernate有魅力的一面。

  ● getGrades方法中用到的hql字串不是JDBC的SQL语句,而是Hibernate自有的HQL语句。关于HQL的更多内容,可查阅Hibernate文档。

37.3.5  创建hibernateTest.jsp

hibernateTest.jsp使用GradeManager类来获得数据,并将数据显示在页面上。代码如下:

<%@ page contentType="text/html; charset=utf8"%>

<%@ page import="cn.com.chengang.sms.db.GradeManager"%>

<%@ page import="cn.com.chengang.sms.model.Grade"%>

<%

         GradeManager mgr = new GradeManager();

         //插入一个年级对象

         mgr.insertGrade();

         //取出所有年级对象,并显示在页面上

         for (Grade g : mgr.getGrades())

                   out.print(g.getId() + "  " + g.getName() + "</br>");

         mgr.close();

%>

37.3.6  总结及实践建议

以上5步完成了一个简单的Hibernate实例,用浏览器运行hibernateTest.jsp的效果如  图37.5所示。

图37.5  hibernateTest.jsp的运行效果

本节用从底层到高层的次序来完成了一个实例的讲解,它虽然简单,但也反映了一个典型Hibernate程序的编写框架:

  ● HibernateUtil一经完成之后即可系统通用,以后很少改动。

  ● XML映射文件是项目前期要做的最重要的工作,在项目开发后期就很少会改动它。对初学者来说,编写XML映射文件也是难点所在。

  ● GradeManager是数据库操作类,这相当于以前的DbOperate类的功能。但它不再直接操作数据库,而是通过Hibernate去访问,所以在GradeManager类中看不到涉及JDBC的代码。

  ● 最后,就是位于最高层的JSP文件hibernateTest.jsp,这个文件主要使用GradeManager类来操作数据库。

实践建议:

  ● Lomboz支持XML映射文件的热修改,当XML映射文件改动之后,Lomboz会将它重新装入。不过这可能需要一两秒钟时间,具体时间要视读者所用电脑性能   而定。

  ● 在Java编程中要时刻注意区分大小写,这是编程中出错较多的原因。

37.4  继续深入使用Hibernate(V006)

在Hibernate中最主要的就是写XML映射文件和HQL查询语句。HQL和SQL相似,有过SQL经验的人可以很轻松地掌握HQL,所以Hibernate的难点集中在XML映射文件的编写上。为了让读者从实例中迅速掌握Hibernate的核心知识,本节将用Hibernate的知识来继续改写原有的用户登录程序。

37.4.1  修改XML映射文件

在本步将把年级、班级、课程和用户4个实体类和表的映射关系设置清楚,这其中涉及多对一关系、一对多关系、继承式实体类(用户类)等知识,关于用户类的设计方案及代码,可参阅26.3节。

给出XML映射文件model.hbm.xml的完整内容如下:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

         <!--年级-->

    <class name="cn.com.chengang.sms.model.Grade">

                   <id name="id">

                            <generator class="identity"/>

                   </id>

        <property name="name"/>

    </class>

        

         <!--班级-->

    <class name="cn.com.chengang.sms.model.SchoolClass">

                   <id name="id">

                            <generator class="identity"/>

                   </id>

        <property name="name"/>

                   <many-to-one name="grade" class="cn.com.chengang.sms.model.Grade"

column="grade_id" update="false" insert="false" />

    </class>

        

         <!--课程-->

    <class name="cn.com.chengang.sms.model.Course">

                   <id name="id">

                            <generator class="identity"/>

                   </id>

        <property name="name"/>

    </class>

        

         <!--用户-->

    <class name="cn.com.chengang.sms.model.IUser" discriminator-value="I">

                   <id name="id">

                            <generator class="identity"/>

                   </id>

                   <discriminator column="type" type="character"/>

         <property name="userId"/>

                   <property name="password"/>

                   <property name="name"/>

                   <property name="latestOnline"/>

                  

                   <!--用户.学生-->

                   <subclass name="cn.com.chengang.sms.model.Student" discriminator-value="B">

                            <many-to-one name="schoolclass"

class="cn.com.chengang.sms.model.SchoolClass" column="schoolclass_id"/>

                   </subclass>

                  

                   <!--用户.老师-->

                   <subclass name="cn.com.chengang.sms.model.Teacher" discriminator-value="A">

                            <set name="courses" table="iuser_course" inverse="true" lazy="true">

                                     <key column="iuser_id"/>

                                     <many-to-many class="cn.com.chengang.sms.model.Course"

column="course_id"/>

                            </set>

                   </subclass>

    </class>             

</hibernate-mapping>

代码说明:

(1)初学者在设置XML映射文件时常常被一对多、多对一等关系搞得很迷糊,感觉这些关系的设置很难捉摸。这里给出一个重要的口诀,即“以实体类的字段为依据来配置XML映射文件:类的字段有则映射有、类的字段无则映射无”。

(2)年级实体类的设置中,把原来的table="grade"去掉了,没有table属性,则默认为表和类同名。这也是当初创建表时为什么用类名做表名的原因:一是名称相同方便记忆,二是配置XML映射文件时也方便简洁。

(3)班级实体类的第三个字段“grade”是年级实体类的类型,一个年级有多个班级,所以班级对年级是多对一的关系。设置多对一关系的字段不再用<property>项,而是用<many-to-one>项来设定,如下:

<many-to-one name="grade" class="cn.com.chengang.sms.model.Grade" column="grade_id"

update="false" insert="false" />

其中name属性是班级实体类中的字段名grade;class属性是grade字段的类型(全类名);column属性对应班级数据表的字段名。

update、insert两项属性的默认值是true,本处设为false。如果设成true,则Hibernate在更新班级实体对象所对应的数据库数据的同时,也会自动更新年级表数据。但一般都不会通过班级实体对象来自动更新年级表,因为年级表基本是不会动的,这种更新不仅没什么用处,反而影响效率。注意,设为false之后,并不是指不能更新年级表,而是指不通过班级实体来自动更新年级表。

有些读者在这里也许会问:“班级对年级是多对一关系,反过来,年级对班级就是一对多关系。为什么只在设置班级实体时,指定了<many-to-one>关系,而在设置年级实体时,没有反过来指定<one-to-many>关系呢?”

年级对班级是一对多关系,没错。但正如前面所说的口诀,年级实体类中并没有指向班级的字段,所以在XML映射中就不设置它的一对多关系。假设在年级实体类中增加一个字段“private List schoolClasses”,用来存放年级下的所有班级,这时就应该用<one-to-many>来设置这种一对多关系了,否则班级记录就不会自动通过Hibernate被抓取到schoolClasses字段中。

(4)用户类是典型的继承式实体类,它的XML映射设置如下:

  ● <class>项定义接口IUser时多加了一个discriminator-value="I"。

  ● <discriminator column="type" type="character"/>是指增加一个名为type的character型字段,这个字段用来区分不同的子类。

  ● 在设置IUser时定义好共同字段。

  ● 在<subclass>项定义各子类的独有字段,其中discriminator-value属性要求各不相同,此值将存入表的type字段中。

关于这种继承式实体设置的更多内容,可参阅Hibernate文档的“第9章 继承映射”,此文档中共有3种方案,本文选择的是“所有用户类合用一个表”的第一种方案。在本书第26章设计用户表时,也提到了这3种方案,大家可以参照阅读,以便加深理解。

(5)学生类有一个班级字段,学生和班级是多对一关系,所以也用了<many-to-one>来设置此字段。

(6)老师类的设置,代码如下:

<set name="courses" table="iuser_course" inverse="true" lazy="true">

         <key column="iuser_id"/>

         <many-to-many class="cn.com.chengang.sms.model.Course" column="course_id"/>

</set>

  ● 老师类和课程是典型的多对多关系:一个老师可以教多门课程,一门课程也可以由多名老师来教。

  ● 因为老师类中的课程字段被定义成一个Set集合,所以用<set>项来定义,除此之外还有<list>、<map>、<array>等。

  ● name="courses"对应于老师类字段courses。

  ● 多对多需要一个新表(仅两字段)来保存两者之间的关联,table="iuser_course"就是这个新表的表名。

  ● inverse="true",设置反转。因为多对多有两端,当两端同时都做了修改时,Hibernate需要根据inverse项的设置来判断应该依据哪一端来做更新操作。如果两端都同时设为inverse="true",或同时省略inverse,极可能导致更新冲突,所以一般是任选一端(且仅一端)来设置inverse="true"。

  ● lazy="true"(默认false),设置延迟(也称懒加载)。
    延迟是Hibernate中非常重要的概念,主要在设置多对多、一对多关系中使用。比如,要显示一个仅有老师名称的列表,如果没有设置课程字段延迟获取,那么Hibernate会将老师对应的课程记录一并从数据库取出,这样就有点浪费了;如果设置了延迟,那么Hibernate会在真正用到课程记录时,才会去数据库中取,这样就显得智能一些。
    但延迟有缺点:①它要求在使用完延迟型数据(如课程数据)之前,Session不能关闭。②如果一个页面肯定要显示所有课程记录,这时不延迟一次取完,要比延迟分批取的效率高得多。
    所以使用延迟要平衡利弊,根据实际情况作出选择。以本例来说明,如果老师对应的课程记录特别多,也就是说一次性取完相应课程记录的代价很大,那么应该选择延迟。但本例中,课程表极小(对于中学来说,也就10门课程左右),每个老师对应的课程数很少(一个老师一般只教一门课程,除非是一人全包的乡村小学老师),这时完全可以不用延迟。本例使用延迟仅仅是为了演示。
    如果已经设置了延迟,但在某些特殊情况下,又需要提前关闭Session,这时就要提前把所有延迟加载数据一起取出。实现代码如下:

Hibernate.initialize(user.getCourses()); //强行加载延迟字段的数据

session.close(); //关闭session

Set<Course> set=user.getCourses();//在session关闭之后依然可以取得延迟数据

37.4.2  数据库操作类的实现

Hibernate的一个优点就是抽象于数据库,能够适应多数据库,所以过去数据层的设计也就没有必要存在了。把MysqlOperate、OracleOperate、SqlServerOperate、AbstractDbOperate、ConnectManager和SmsFactory 6个类都删除掉,还有SMS类中的无用常量也可以删除掉,仅留CURRENT_USER常量。然后将DbOperate由接口类改成普通类,其具体代码如下:

package cn.com.chengang.sms.db;

import java.util.Collections;

import java.util.List;

import org.hibernate.Query;

import org.hibernate.Session;

import cn.com.chengang.sms.model.IUser;

public class DbOperate {

         // 根据用户名得到用户对象,如返回null则表示此用户不存在

         public IUser getUser(String userId) throws RuntimeException {

                   Session session = HibernateUtil.getSessionFactory().getCurrentSession();

                   IUser user = null;

                   try {

                            session.beginTransaction();

                            user = getUser(session, userId);

                            session.getTransaction().commit();

                   } catch (Exception e) {

                            session.getTransaction().rollback();

                            throw new RuntimeException(e);

                   }

                   return user;

         }

         public IUser getUser(Session session, String userId) {

                   Query q=session.createQuery("from "+IUser.class.getName()+ " where userId=:userId");

                   q.setParameter("userId", userId);

                   List list = q.list();

                   if (list.isEmpty())

                            return null;

                   else

                            return (IUser) list.get(0);

         }

         // 根据分页信息对象QueryInfo得到应用的用户记录

         // 要求QueryInfo中已有currentPage和pageSize的数据

         public List<IUser> getUsers(QueryInfo qi) throws RuntimeException {

                   Session session = HibernateUtil.getSessionFactory().getCurrentSession();

                   List<IUser> list = null;

                   try {

                            session.beginTransaction();

                            list = getUsers(session, qi);

                            session.getTransaction().commit();

                   } catch (Exception e) {

                            session.getTransaction().rollback();

                            throw new RuntimeException(e);

                   }

                   return list;

         }

         public List<IUser> getUsers(Session session, QueryInfo qi) {

                   // 得到总记录数

                   String hql = "select count(*) from " + IUser.class.getName();

                   qi.rsCount = ((Long) session.createQuery(hql).iterate().next()).intValue();

                   if (qi.rsCount == 0)// 等于0表示没有记录

                            return Collections.emptyList();

                   // 算出总页数

                   if (qi.rsCount % qi.pageSize == 0)

                            qi.pageCount = qi.rsCount / qi.pageSize;

                   else

                            qi.pageCount = (qi.rsCount / qi.pageSize) + 1;

                   // 算出起始位置= (当前页号-1)*每页记录数

                   int start = (qi.currentPage - 1) * qi.pageSize;

                   Query q = session.createQuery("from " + IUser.class.getName());

                   q.setFirstResult(start);

                   q.setMaxResults(qi.pageSize);

                   return (List<IUser>)q.list();

         }

}

程序说明:

  ● 因为用户的老师类的课程字段采用了延迟,在前面已经讨论过,在获取延迟字段的数据之前不能关闭Session,所以取得用户的方法分成了两种:带Session参数的和不带的。如果不会用到课程数据,就用不带Session的方法,方便一些。

  ● 在第二种方法中的IUser.class.getName()得到的是IUser的全类名“cn.com.chengang. sms.model.IUser”,在HQL查询中,不用在查询之前加“select *”字串,而且表名是用类全名来代替,这也体现了Hibernate面向对象的风格。

  ● 第三种方法是分页式取数据的方法,q.setFirstResult(start)是设置起始记录位置,q.setMaxResults(qi.pageSize)是设置本次要取的记录数。

  ● 在这里不仅捕获了Exception异常,并且在处理完异常后,再次将该异常包装成RuntimeException异常抛出。这是一种较标准的写法,是为了在类外程序中使用该方法时,能够再次捕获异常,并作一些处理。如果要做得更完善,则可以专门继承RuntimeException创建一个子异常类给DbOperate类用。

37.4.3  修改使用DbOperate类的程序

1.LogonAction类

此类代码基本不变,仅将原来取数据的两行代码

DbOperate db = SmsFactory.getDbOperate();

IUser user = db.getUser(userId);

更改为:

IUser user=new DbOperate().getUser(userId);

2.对显示用户列表userList.jsp文件修改示意如下(主要改动用粗体字标示)

<%@ page contentType="text/html; charset=utf8"%>

<%@ page import="cn.com.chengang.sms.model.*"%>

<%@ page import="cn.com.chengang.sms.db.*"%>

<%@ page import="java.util.*"%>

<%@ page import="org.hibernate.Session"%>

<%@ include file="../checkLogon.jsp"%>

<HTML>

<HEAD><TITLE>用户列表</TITLE></HEAD>

<BODY><table>

         <tr><td>ID</td><td>用户名</td><td>密码</td><td>姓名</td><td>班级</td><td>课程</td><td>最后登录时间</td></tr>

        

<%

QueryInfo qi=new QueryInfo();

qi.currentPage=1; //显示第一页

qi.pageSize=100; //每页100条记录

Session hsession = HibernateUtil.getSessionFactory().getCurrentSession();

hsession.beginTransaction();

try{

         List<IUser> list = new DbOperate().getUsers(hsession,qi);

         for (IUser user:list) {%>    

                   <tr>

                            <td><%=user.getId()%></td>

                            <td><%=user.getUserId()%></td>

                            <td><%=user.getPassword()%></td>

                            <td><%=user.getName()%></td>

                            <td>

                            <%if (user instanceof Student) {

                                     SchoolClass s = ((Student) user).getSchoolclass();

                                     if (s != null)

                                               out.print(s.getName());

                            }%>

                            </td>

                            <td>

                            <%if (user instanceof Teacher) {

                                     Set<Course> set = ((Teacher) user).getCourses();

                                     StringBuilder sb=new StringBuilder();

                                     for (Iterator it = set.iterator(); it.hasNext();) {

                                               Course course = (Course) it.next();

                                               sb.append(course.getName());

                                               if (it.hasNext())

                                                        sb.append(", ");

                                     }

                                     out.print(sb.toString());

                            }%>

                            </td>

                            <td><%=user.getLatestOnline()%></td>

                   </tr>

         <%

         }

         hsession.getTransaction().commit();

} catch (Exception e) {

         hsession.getTransaction().rollback();

         throw new RuntimeException(e);

}

%>

</table></BODY></HTML>

程序说明:

  ● 在文件头增加了一个<%@ page import来引用hibernate包。

  ● 将Session对象取名hsession是为了避免和JSP的默认对象Session产生同名冲突。

  ● hsession必须要在取数据循环结束后才能关闭,因为老师类的课程字段用的是延迟获取数据的方式。

37.5  实现用户的修改、删除功能(V007)

37.5.1  界面效果及功能说明

本节将同时需要Struts和Hibernate的知识,实例完成后的用户列表页面如图37.6所示。

  ● 单击“删除”超链接后,不提问,直接从数据库中删除记录,并再次返回用户列表页面。

  ● 单击图37.6中的“修改”超链接后,打开如图37.7所示的页面。

  ● 单击图37.7中的“修改”按钮后,再次返回用户列表页面,此时页面显示新值。

 

图37.6  用户列表页面                 图37.7  修改页面

37.5.2  在DbOperate类增加方法

在DbOperate类分别加入删除数据、插入更新数据的方法,每种方法都提供带Session参数和不带Session参数的两种。saveOrUpdate同时具有插入和更新的功能,从这一点也可以体现Hibernate的高度智能化,的确大大简化了操作数据表的工作。除了本例通过一个查询HQL来删除,还有一种常用的传入实体类的删除方法:“session.delete(Object obj)”,Hibernate会根据实体类型去相应的表中将记录删除。

加入到DbOperate类的代码如下:

         // 用HQL语法来删除数据,参数hql是一个查询字符串

         public void delete(String hql) throws RuntimeException {

                   Session session = HibernateUtil.getSessionFactory().openSession();

                   try {

                            session.beginTransaction();

                            delete(session, hql);

                            session.getTransaction().commit();

                            System.err.println("try "+session.isOpen()+"_"+session.isConnected());

                   } catch (Exception e) {

                            session.getTransaction().rollback();

                            System.err.println("Exception "+session.isOpen()+"_"+session.isConnected());

                            throw new RuntimeException(e);

                   }finally{

                            session.close();

                            System.err.println("finally "+session.isOpen()+"_"+session.isConnected());

                   }

         }

         public void delete(Session session, String hql) {

                   session.createQuery(hql).executeUpdate();

         }

         // 插入或更新实体对象所对应的记录

         public void saveOrUpdate(Object obj) throws RuntimeException {

                   Session session = HibernateUtil.getSessionFactory().getCurrentSession();

                   try {

                            session.beginTransaction();

                            saveOrUpdate(session, obj);

                            session.getTransaction().commit();

                   } catch (Exception e) {

                            session.getTransaction().rollback();

                            throw new RuntimeException(e);

                   }

         }

         public void saveOrUpdate(Session session, Object obj) {

                   session.saveOrUpdate(obj);

         }

37.5.3  在用户列表userList.jsp文件增加两个超链接

在原有的userList.jsp的每条记录后面加入“修改”、“删除”超链接,代码如下:

<td><A HREF="/myweb/user/userAction.do?method=showUser&userId=<%=user.getUserId()%>"

>修改</A></td>

<td><A HREF="/myweb/user/userAction.do?method=removeUser&id=<%=user.getId()%>"

>删除</A></td>

(1)以用户chen为例,给出相应的IE地址显示如下:

修改的IE地址显示:

http://localhost:8080/myweb/user/userAction.do?method=showUser&userId=chen

删除的IE地址显示:

http://localhost:8080/myweb/user/userAction.do?method=removeUser&id=1

(2)在这里用的UserAction稍后创建,它继承自Struts的DispatchAction类,并有3种方法removeUser、showUser和modifyUser分别对应于删除、显示和修改。

(3)要修改用户,首先得把它的值显示出来,所以method参数值为showUser。修改使用userId参数(用户名),删除使用id参数(自动递增主键)。当然,也可以改为通过userId来删除记录。

(4)特别强调:

  ● 超链接的HREF属性和表单标签(<html:form)的action属性在地址定位上有区别,表单action定位用/user/action.do,而超链接HREF定位用/myweb/user/action.do或action.do。这是比较容易忽略的一点。

  ● method参数的值必须是UserAction中的一个方法名。

  ● userId、id两个参数必须是UserAction对应的UserForm类中的字段。

37.5.4  在Struts配置文件struts-config.xml中增加一个action定义

将下面的XML块,加入到<action-mappings>…</action-mappings>之间。

<action path="/user/userAction" type="cn.com.chengang.sms.user.UserAction"

 name="userForm" scope="request" validate="true"

input="/user/userList.jsp" parameter="method">

         <forward name="modifyUserView" path="/user/modifyUser.jsp"/>

         <forward name="modifySuccess" path="/user/userList.jsp"/>

</action>

代码说明:

  ● <action>项的path属性定义了UserAction。

  ● parameter属性是DispatchAction型Action所必需的。

  ● name="userForm",此项设置说明UserAction也是用userForm来封装表单数据,当然,userForm还要加入一些字段,在后面会给出其代码。

  ● 这里设置了两个转发:一个是修改页面的modifyUserView;一个是修改成功后的返回页面modifySuccess。

37.5.5  修改UserForm类

UserForm类需要做如下3处修改:

(1)由于修改页面和登录页面合用UserForm类,所以要向类中再多加入修改页面用到的3个字段(同时还有相应的Setter/Getter方法),代码如下:

private Long id;  //数据库ID

private String name; //姓名

private Date latestOnline;//最后登录时间

//-----Setter/Getter方法,省略----

(2)因为UserAction继承自DispatchAction类,这种类型的Action要求UserForm继承ValidatorActionForm,否则无法使用36.5.2节方法2中的验证方式。将UserForm的父类改为ValidatorActionForm。代码如下:

public class UserForm extends ValidatorActionForm {……

(3)最后还必须将UserForm中的validate方法删除,否则修改用户页面无法被打开。

从这里可以看到Struts程序的代码复用率很高,共用一个UserForm,这不仅减少了创建ActionForm的个数,还能共用验证机制。Struts允许将系统中所有表单用到的字段合在一个ActionForm中,但不推荐这样做。也有人认为应该一个JSP表单对应一个ActionForm,但这种方案的代码复用率太低。

建议将同类型表单所用到的字段合在一个ActionForm中,就像UserForm一样。这样既不会产生太多ActionForm,又有很好的代码复用。当然,这里面没有绝对的法则,读者应该根据自己实际的开发情况来选择。

37.5.6  创建UserAction类

创建UserAction类的代码如下:

package cn.com.chengang.sms.user;

public class UserAction extends DispatchAction {

         private final static String USER = "modiUser";

         // 删除用户的Action

         public ActionForward removeUser(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response) throws Exception {

                   UserForm actionForm = (UserForm) form;

                   Long id = actionForm.getId();

                   String hql = "delete " + IUser.class.getName() + " where id=" + id;

                   new DbOperate().delete(hql);

                   return (mapping.getInputForward());

         }

         // 修改用户的Action

         public ActionForward modifyUser(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response) throws Exception {

                   UserForm actionForm = (UserForm) form;

                   IUser user = (IUser) request.getSession().getAttribute(USER);

                   request.getSession().removeAttribute(USER);

                   user.setUserId(actionForm.getUserId());

                   user.setPassword(actionForm.getPassword());

                   user.setName(actionForm.getName());

                   new DbOperate().saveOrUpdate(user);// 更新数据库

                   return (mapping.findForward("modifySuccess"));

         }

         // 显示一个用户的Action

         public ActionForward showUser(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response) throws Exception {

                   UserForm actionForm = (UserForm) form;

                   String userId = actionForm.getUserId();

                   IUser user = new DbOperate().getUser(userId);

                   actionForm.setUserId(user.getUserId());

                   actionForm.setPassword(user.getPassword());

                   actionForm.setName(user.getName());

                   actionForm.setLatestOnline(user.getLatestOnline());

                   request.getSession().setAttribute(USER, user);// 保存用户状态

                   return (mapping.findForward("modifyUserView"));

         }

}

程序说明:

(1)在显示用户的方法showUser中,先取得用户对象,然后再将要显示在界面上的值转给UserForm。在修改用户的页面中,UserForm的字段值会自动显示在相应的文本框中。

(2)在程序中定义了一个字符串常量USER。如果要经常用到某个字符串,最好定义成常量,否则容易因为书写错误而产生难以发现的BUG。

(3)可以通过new DbOperate().saveOrUpdate(user)来直接更新user代表的数据记录,但这要求让user对象从showUser中取出后,到modifyUser中还能用。本例采用HttpSession来维持user的状态。

HttpSession是保存在服务器端的,通常HttpSession是30分钟左右失效(这是可设置的),HttpSession本身所占内存并不大,但在此期间,因为它保持着对user对象的引用,这使得user对象及user引用的其他对象都无法被JVM垃圾回收器回收。所以在使用完HttpSession之后,要记得用removeAttribute(USER)方法将HttpSession对user的引用清除。

对于user对象状态的保持,可能还会有以下几种行不通的想法:

  ● 用request来保持状态。
分析:这是不行的,user状态可从showUser方法保持到modifyUser.jsp页面,但在modifyUser.jsp页面提交修改后,转到modifyUser方法中时,user对象已失效。

  ● 在UserForm创建一个user字段。
分析:user状态一样无法保持到modifyUser方法中,当提交表单转到modifyUser时,所有在表单中没有被设置的UserForm字段值都会被清空。

  ● 在UserAction类创建一个user字段来保存user对象。
分析:这是错误的。Struts的机制是:UserAction在整个系统只有一个实例,因此UserAction类的user字段会被所有用户线程共用,这将导致很严重的BUG。

37.5.7  创建modifyUser.jsp

modifyUser.jsp的作用是用文本框显示用户数据,当修改完成后,单击“修改”按钮即可将这些修改提交到数据库中。代码如下:

<%@ page contentType="text/html; charset=utf8"%>

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>

<HTML><HEAD><TITLE>修改用户</TITLE>

<META http-equiv=Content-Type content="text/html; charset=utf8">

</HEAD><BODY>

<html:form action="/user/userAction.do?method=modifyUser" focus="userId" onsubmit="return validateUserForm(this)">

<table width="100%">

         <tr><td>

                   用户名:<html:text property="userId" maxlength="10" />

                   <font color="red"><html:errors property="userId"/></font>

         </td></tr>

         <tr><td>

                   密 码:<html:text property="password" maxlength="20"/>

                   <font color="red"><html:errors property="password"/></font>

         </td></tr>

         <tr><td>

                   姓 名:<html:text property="name" maxlength="20"/>

                   <font color="red"><html:errors property="name"/></font>

         </td></tr>

         <tr><td>

                   最后登录时间:

        <bean:write name="userForm" property="latestOnline"/>

         </td></tr>

         <tr><td>

        <html:submit>修改</html:submit><html:reset>重填</html:reset>

    </td></tr>  

</table>

</html:form>

<html:javascript dynamicJavascript="true" staticJavascript="true" formName="userForm"/>

</BODY></HTML>

程序说明:

  ● 在页头加入了html和bean标签的声明,并在最后登录时间值的输出时使用了<bean:write >标签。在<bean>标签中的name属性引用了userForm对象,userForm对象是Struts在JSP页面中隐式生成的。

  ● 此页面和logon.jsp是非常相似的,特别是对值进行验证机制两者完全一样。

抱歉!评论已关闭.