我对轻量级(struts+spring+hibernate)j2ee的理解(一)

原创 2007年09月19日 22:48:00
  

我对轻量级j2ee的理解

一、前言

随着javajava开源框架的不断发展,以java web为基础的企业级应用开发技术也越来越完善和成熟。其中用开源框架如(struts,spring,hibernate等)架构开发企业级应用已成为j2ee一种流行趋势---开源、分层、组件、松耦合、orm。开源代表这些框架可以免费使用,你同时能获取它的源代码,扩展框架;分层代表经典的MVC设计模式,以jspview层,可以通过ajax技术异步请求,改善用户对页面的体验,以strutscontroller层,分发请求到相应的action,以spring架构business逻辑层,处理业务逻辑,利用它的依赖注入、AOPDAOORM等特性轻松架构逻辑层,以hibernate为持久化层,完成orm映射,以面向对象的方式操纵数据的存取,减轻烦琐的jdbc编程;组件化、松耦合已成为j2ee开发的核心思想;优秀的orm框架产品更是面向对象程序设计和关系数据库映射的桥梁。以下将用一个demo示例来演示怎样利用struts+spring+hibernate开发轻量级的j2ee企业级应用。

二、示例介绍

   demo完成一个简单的应用片断,即添加商品目录(Category)、添加商品条目(Item),并将他们持久化到数据库中。

三、资源版本

   1.javaj2se1.5以上

   2.strutsstruts1.2

   3.springspring2.0

   4.hibernatehibernate3.2

   5.开发平台:MyEclipse6.0,到MyEclipse上下在MyEclipse_6.0GA_E3.3_FullStackInstaller.exe(其中已经包含eclipse3.3j2ee5),再到网上搜一个注册码。

   6.应用服务器:tomcat6jboss

   7.数据库:mysqloracle10g

四、开发持久化层(hibernate

   Hibernate的功能主要是

(1)    利用xml映射文件完POJO到数据库的映射。

(2)    利用hibernate.cfg.xml配置文件创建SessionFactory,将java对象存储到数据库中,并将数据库中的数据以java对象的形式取回。

 

Hiernate开发步骤:

(1)    开发持久化对象(persistent Object)。本示例中有两个domain object—CategoryItemHibernate中被持久化的对象为简单的javaBean,拥有settergetter方法,持久化对象中没有耦合hibernate的任何API。它们的UML图示如下:

 

Category

Long id

String name

Category partenCategory

Set childCategories

Set items

Item

Long : id

String : name

Set : parentCategories

     多对多双向关联

     一对多双向关联

它们在数据库中的表结构如下:

Category

ID

name

parent_id

1

体育用品

null

2

NIKE

1

3

安踏

1

4

休闲体育用品

1

 

 

 

 

 

 

 

 

 

Items

ID

name

decription

price

discount

picture

inTime

1

nike跑鞋

质量好

680.00

80

……

2007-08-09

2

nike球衣

good

320.00

75

3

运动包

12.000

90

Category_Item(链接表)

category_id

item_id

2

1

2

2

4

3

2

3

 

 

 

 

 

 

 

 

Category.java的源代码:

package com.sample.domain;

 

import java.io.Serializable;

import java.util.Set;

import java.util.HashSet;

 

public class Category implements Serializable{

       public static final long serialVersionUID=-1814239825517340645L;

       private Long id;

       private String name;

       private Set<Category> childCategories=new HashSet<Category>();

       private Category parentCategory;

       private Set<Item> items=new HashSet<Item>();

      

      

       public Category(){}

      

       public Long getId() {

              return id;

       }

       public void setId(Long id) {

              this.id = id;

       }

       public String getName() {

              return name;

       }

       public void setName(String name) {

              this.name = name;

       }

       public Set<Category> getChildCategories() {

              return childCategories;

       }

       public void setChildCategories(Set<Category> childCategories) {

              this.childCategories = childCategories;

       }

       public Category getParentCategory() {

              return parentCategory;

       }

 

       public void setParentCategory(Category parentCategory) {

              this.parentCategory = parentCategory;

       }

 

       public Set<Item> getItems() {

              return items;

       }

 

       public void setItems(Set<Item> items) {

              this.items = items;

       }

 

       /**

        * add category

        */

      

       public void addCategory(Category category){

              if(category==null){

                     throw new IllegalArgumentException("category is null!");

              }

              if(category.getParentCategory()!=null){

                     category.getParentCategory().childCategories.remove(category);

                     category.setParentCategory(null);

              }

              this.childCategories.add(category);

              category.setParentCategory(this);

       }

      

       /**

        * add item

        */

      

       public void addItem(Item item){

              if(item==null){

                     throw new IllegalArgumentException("Item is null");

              }

              this.items.add(item);

              item.getParentCategories().add(this);

       }

      

       /**

        * overrides java.lang.Object.equals

        */

       public boolean equals(Object o){

              if(this==o)return true;

              if(!(o instanceof Category))return false;

              Category other=(Category)o;

              if(other.getName().equals(this.name) && other.getParentCategory().equals(this.getParentCategory())){

                     return true;

              }

              return false;

       }

      

       /**

        * overrides java.lang.Object.hashCode

        */

       public int hashCode(){

              int result=0;

              result=this.name.hashCode();

              result+=29*result+this.getParentCategory().hashCode();

              return result;

              }

}

 

其中编写持久化对象的几点要求:

1 每个持久化类需要一个无参数的构造器,hibernate会用Constructor.newInstance()来实例化它们,生成该对象的代理类,帮助持久化对象映射到数据库中。如: public Category(){},至少为package级别。

(2)    需要一个标识属性(identifier property)(可选),它映射数据库表中的主键字段,为代理主键,它最好不代表业务逻辑,如:private Long id;最好用Long类型。

(3)    为需要被持久化的属性添加settergetter方法,遵循javaBean的写法,hibernate能够访问任何级别的方法。

(4)    最好不要将持久化类声明为final,或一个方法为final,这样你将失去生成hibernate代理类的好处,如lazy loading

(5)    如果需要在网络间传递你的持久化类,你必须继承java.io.Serializable

(6)    如果要将持久化类放入Collection中,或者比较两个持久化类是否代表数据库中的同一条记录,必须改写java.lang.equalsjava.lang.hashCode。(建议每个持久化类都改写这两个方法)如下面的程序表示如果Category对象的name和它的parentCategory相同的话,这两个对象相同,即表示同一条记录:

       /**

        * overrides java.lang.Object.equals

        */

 

       public boolean equals(Object o){

              if(this==o)return true;

              if(!(o instanceof Category))return false;

              Category other=(Category)o;

              if(other.getName().equals(this.name) && other.getParentCategory().equals(this.getParentCategory())){

                     return true;

              }

              return false;

       }

      

       /**

        * overrides java.lang.Object.hashCode

        */

       public int hashCode(){

              int result=0;

              result=this.name.hashCode();

              result+=29*result+this.getParentCategory().hashCode();

              return result;

              }

}

(7)    两个类有关联关系时(一个类拥有另一个类的reference,最好在程序中主动建立类之间的引用关系,双方都建立引用。如添加一个子目录:

       /**

        * add category

        */

      

       public void addCategory(Category category){

              if(category==null){

                     throw new IllegalArgumentException("category is null!");

              }

              if(category.getParentCategory()!=null){

                     category.getParentCategory().childCategories.remove(category);

                     category.setParentCategory(null);

              }

       //建立两个对象的关联关系

              this.childCategories.add(category);

              category.setParentCategory(this);

       }

(2)   编写该类的hibernate映射文件。映射的文件名为----类名+hbm.xml,最好一个类对应一个映射文件。如Category.hbm.xml

 

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping package="com.sample.domain">

   <class name="Category" table="Category">

      <!-- 标识属性映射 -->

      <id name="id" column="ID" type="long">

         <!-- 根据数据库来选择不同的生成策略 -->

         <generator class="native"></generator>

      </id>

      <!-- 简单属性映射 -->

      <property name="name" type="string" ></property>

      <!-- 多对一映射,采用外键关联 -->

      <many-to-one name="parentCategory" column="parent_id" class="Category" lazy="false"></many-to-one>

      <!-- 一对多映射,默认为延迟加载,子端为关系维护端 ,级联关系-->

      <set name="childCategories" inverse="true" cascade="all,delete-orphan">

         <key column="parent_id"></key>

         <one-to-many class="Category"/>

      </set>

      <!-- 多对多映射,有连接表,此端为被维护端 -->

      <set name="items" inverse="true" table="Category_Item" inverse=”true”>

         <key column="Category_id"></key>

     <many-to-manycolumn="Item_id"class="Item"></many-to-many>

      </set>

     

   </class>

</hibernate-mapping>

下面我们来详细分析一下这个映射文件,学习hibernate的最大的障碍就在于编写hibernate映射文件,如果你搞懂了它的映射文件的写法和技巧,那你的hibernate就学完了一半了,剩下的就只是怎样存取java对象了。

1.首先每个hibernate3映射文件dtd为:

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

2.它的根元素为<hibernate-mapping></hibernate-mapping>

可以在它里面声明一个全局包,这样在下面的文件中可以使用类名,不必写完整它的包名,相反,如果没有设定,在文件中就必须写包含包名的类名:

<hibernate-mapping package="com.sample.domain">

3.根元素下面可以包括一个或多个<class></class>元素,最好一个映射文件映射一个类。Class元素中包含多个子元素。

<class name="Category" table="Category">其中name对应java持久化类的类名,如果根元素没有设定package属性,必须写带包名的类名,如com.sample.Categorytable对应数据库中的表名。

 

4.<id>元素。Class元素的第一个子元素必须为id元素,它映射标识符属性。

<!-- 标识属性映射 -->

      <id name="id" column="ID" type="long">

         <!-- 根据数据库来选择不同的生成策略 -->

         <generator class="native"></generator>

      </id>

其中name对应java持久化类的id属性,column对应数据库中相应的字段名,type为映射属性值的类型,现将java类型和对应的hibernate映射类型以级在数据库中生成的类型列出,以供参考。

  1. 映射类型          java 类型                     标准 sql 类型   
  2.   
  3. integer         int or Integer            INTEGER   
  4. long            long or java.lang.Long    BIGINT   
  5. short           short or java.lang.Short  SMALLINT   
  6. float           float or java.lang.Float  FLOAT   
  7. double          double or java.lang.Double DOUBLE   
  8. big_decimal     java.math.BigDecimal       NUMERIC   
  9. character       java.lang.String           CHAR(1)   
  10. string          java.lang.String           VARCHAR   
  11. byte            byte or java.lang.Byte     TINYINT   
  12. boolean         boolean or java.lang.Boolean BIT   
  13. yes_no          boolean or java.lang.Boolean CHAR(1)('Y' or 'N')   
  14. true_false      boolean or java.lang.Boolean CHAR(1)('Y' or 'N')   
  15. date            java.util.Date or java.sql.Date  DATE   
  16. time            java.util.Date or java.sql.Time  TIME   
  17. timestamp       java.util.Date or java.sql.TimeStamp TIMESTAMP   
  18. calendar        java.util.Calendar            TIMESTAMP   
  19. calendar_date   java.util.Calendar            DATE   
  20.   
  21. binary          byte[]                      VARBINARY( or BLOB)   
  22. text            java.lang.String            CLOB   
  23. serializable    java.io.Serializable        VARBINARY (or BLOB)   
  24. clob            java.sql.Clob               CLOB   
  25. blob            java.sql.Blob               BLOB   
  26.   
  27. class           java.lang.Class             VARCHAR   
  28. locale          java.util.Locale            VARCHAR   
  29. timezone        java.util.TimeZone          VARCHAR   
  30. currency        java.util.Currency          VARCHAR   

<generator class="native"></generator>generator表示标符的生成策略,它也有很多种选择,它要根据底层数据库的类型来选择不同的生成策略,如果你选择class=”native”,表示hibernate会为你根据不同数据库来选择不同的生成策略,如mysql数据库可以选择”increment””indentity”,如果为oracle数据库,可以选择”sequence”

5.property属性,它映射java对象的一般属性,如基本类型、String类型等。

     <!-- 简单属性映射 -->

      <property name="name" type="string" ></property>

其中name对应java对象的属性名,type为属性的映射类型,如果没有指定数据库的字段名column属性,hibernate会利用反射机制以java对象属性名来生成相同的数据库字段名。

6.对象之间的关系映射。

自然界中广义的对象不是孤立存在的,它们之间存在着各种各样的关系。比如一个用户可以有多个订单,每个订单可以有多个订单项,目录下有多个子商品,每个商品可以归为多个目录下………归结起来主要有三大关系:

1)一对多关系(多对一关系)

2)一对一关系

3)多对多关系

它们可以是单向(只有一方知道另一方)或双向(双方都知道对方)关系。

下面来分别看一下映射文件中的各种关联关系:

(1)   多对一映射

<!-- 多对一映射,采用外键关联 -->

      <many-to-one name="parentCategory" column="parent_id" class="Category" lazy="false"></many-to-one>

Java程序片断为:

       private Category parentCategory;

   public Category getParentCategory() {

              return parentCategory;

       }

 

       public void setParentCategory(Category parentCategory) {

              this.parentCategory = parentCategory;

       }

映射文件中以<many-to-one>元素来映射多对一关系。name对应java对象中的属性parentCategorycolumn对应数据库中外键参照字段,取名为parent_idclassone那端对应的javaCategory(本应用中为自身多对一双向映射)Lazy=”false”表示该类实例化后会立即加载和它关联的one端对象parentCategory,即如果程序中调用getParentCategory()将返回Category的引用。如果不设定这个属性,并且对象已经脱离session的管理(detached)时,调用getParentCategory()将返回null。如果对象在session的管理中(persistent),返回Category的引用,这就是延迟加载,我们马上会谈到hibernate的这个特性。

(3)    一对多映射

  <!-- 一对多映射,默认为延迟加载,子端为关系维护端 ,级联关系-->

      <set name="childCategories" inverse="true" cascade="all,delete-orphan">

         <key column="parent_id"></key>

         <one-to-many class="Category"/>

      </set>

对应的java代码片断为:

private Set<Category> childCategories=new HashSet<Category>();

public Set<Category> getChildCategories() {

              return childCategories;

       }

       public void setChildCategories(Set<Category> childCategories) {

              this.childCategories = childCategories;

       }

one-to-many映射时,因为many一方会保存在java集合里,所以还要映射java的集合属性,java集合中包含ListSetMap。在hibernate里,只能将集合声明为接口类型,如:private Set<Category> childCategories=new HashSet<Category>();而不能用HashSet<Category> childCategories=new HashSet<Category>();另外在映射集合类型时,因为它是接口,所以在映射前必须用它的子类实例化。

    Set元素中name对应java对象中集合属性的名字,inverse=”true”表示关联关系的所有者是many端,即在数据库中拥有外键的表对应的java类,为什么要设置inverse属性呢?这是由于面向对象程序设计和关系数据库的不匹配导致的,在java程序中两个对象靠拥有对方的引用建立关联关系,而在数据库中两张表的关联关系是靠在一张表中建立foreign key 来关联另一张表的主键,从而建立起关联关系。如:alter table a add index index_001 parent_id add constraint index_001 foreign key(parent_id) references table_b(ID)。它如果像java那样在两张表中都用外键建立和对方的关联,那就违反了数据库的规范。如果在映射文件中没有明确指明在哪端建立外键关联,Hiberante就无法知道在哪端建立外键关联,所以设置了inverse这个属性,如果这个关系的inversetrue,表示在它的对方建立外键来关联它们的关系,例如:AB的一对多关系中,如果Ainversetrue,则在B方对应的数据库表中设置外键来建立双方的关系。alter table B add index index_001 A_id add constraint index_001 foreign key(A_id) references A(ID)。下面是摘自Apress出版的书《Beginning Hibernate From Novice to Professional》:

Table 4-1.Marking the Owner of an Association

Type of Association Options

One-to-one Either end can be made the owner, but one (and only one) of them should be—if you don’t specify this, you will end up with a circular dependency.

One-to-many The many end must be made the owner of the association.

Many-to-one This is the same as the one-to-many relationship, viewed from the opposite perspective, so the same rule applies—the many end must be made the

owner of the association.

Many-to-many Either end of the association can be made the owner.

If this all seems rather confusing, just remember that association ownership is concerned exclusively with the management of the foreign keys in the database, and things should become clearer as you use Hibernate further.

我们再来看cascade属性,它表示是否采用级联操作。比如在典型的父子关系中,如果要求父类被删除时,他的子类也应该级联删除。所以对在这种情况下可以用cascade属性来保证这种级联操作,否则会违反数据关系的一致性。Cascade有多种选择值,如create merge delete save-update evict replicate lock refreshall等。本示例中”alldelete-orphan”all表示支持所有的级联操作,delete-orphan表示你可以简单的从父类的集合中移除一个子类引用,hibernate会帮你自动从数据库中删除这个子类的数据,你不需要进行额外的操作。例如:Category.getChildCategories().remove(childCategory);

另外还有一个重要的属性lazy,在hibernate的映射文件中默认的lazy值为true,表示实例化一个对象A时,不会立即实例化它所关联的对象B,如果对象A存在于session的管理中(persistent Object状态),当用到对象B时,hibernate会自动实例化对象B,并把对象B的引用传递给对象A.。比如:

Transaction tx;

tx=Session.beignTransaction();

Category category=(Category)session.get(Category.class,1);

Tx.commit;

Set<Category> childs=category.getChildCategories();

此时childs中的元素为0

如果这样,位于session管理中时:

Transaction tx;

tx=Session.beignTransaction();

Category category=(Category)session.get(Category.class,1);

Set<Category> childs=category.getChildCategories();

tx.commit();

此时childs的元素已被加载,可以使用了。

 

因为集合中元素是存储在另外一个关联表中的,所以要为每个集合映射添加一个key属性,它的column属性指定这个关联表中的外键列名,它自动关联父类的ID列。

集合中的元素可以是基本java类型、String型、和对象引用型。

当为前两种时,用<element>元素映射,比如是String型:

 

<set name="childCategories" inverse="true" cascade="all,delete-orphan">

         <key column="parent_id"></key>

         <element column="childname" type=”string”/>

      </set>

element column指定存储元素的列名,type指定元素的hiberante映射类型。

当存储的元素为对象的引用时,且没有关联关系时,用class属性指定element元素的类型。如:<element class=”Category” />

当有关联关系时,有两种情况:one-to-manymany-to-many。将element元素换成如下:

One-to-many:表示父类和父类集合中的元素为一对多关系

<one-to-many class=”Category” />

Many-to-many: 表示父类和父类集合中的元素为多对多关系

<many-to-many column=”Item_id” class=”Item” />

(4)     多对多关系映射

  <!-- 多对多映射,有连接表,此端为被维护端 -->

      <set name="items" inverse="true" table="Category_Item" inverse=”true”>

         <key column="Category_id"></key>

     <many-to-many column="Item_id" class="Item"></many-to-many>

      </set>

(5)   一对一关系映射(one-to-one)

 例如每一个人对应一张身份证,人和身份证就一对一的关系。一对一映射有两中方式:1.一张表的主键参照另一张表的主键。

          2.一张表用外键参照另一张表的主键,外键为unique

下面我们用第2种方式来建立人和身份证的双向一对一关系:

它们的表结构如下:

  person

ID

name

sex

birthday

1

tom

 

 

 idCard

ID

idnumber

person_id

1

4556123701245

1

 

Person.java

package com.sample.domain;

 

import java.io.Serializable;

import java.util.Date;

 

public class Person implements Serializable{

    public static final long serialVersionUID=-1814239825517340645L;

    private Long id;

    private String name;

    private boolean sex;

    private Date birthday;

    private IDCard idCard;

   

    public Person(){}

 

    public Long getId() {

       return id;

    }

 

    public void setId(Long id) {

       this.id = id;

    }

 

    public String getName() {

       return name;

    }

 

    public void setName(String name) {

       this.name = name;

    }

 

    public boolean isSex() {

       return sex;

    }

 

    public void setSex(boolean sex) {

       this.sex = sex;

    }

 

    public Date getBirthday() {

       return birthday;

    }

 

    public void setBirthday(Date birthday) {

       this.birthday = birthday;

    }

 

    public IDCard getIdCard() {

       return idCard;

    }

 

    public void setIdCard(IDCard idCard) {

       this.idCard = idCard;

    }

   

   

}

IDCard.java

package com.sample.domain;

 

import java.io.Serializable;

 

public class IDCard implements Serializable{

    public static final long serialVersionUID=-1814239825517340645L;

    private Long id;

    private String number;

    private Person person;

   

    public IDCard(){}

 

    public Long getId() {

       return id;

    }

 

    public void setId(Long id) {

       this.id = id;

    }

 

    public String getNumber() {

       return number;

    }

 

    public void setNumber(String number) {

        this.number = number;

    }

 

    public Person getPerson() {

       return person;

    }

 

    public void setPerson(Person person) {

       this.person = person;

    }

   

}

它们的映射文件如下:

Person.hbm.xml

<hibernate-mapping package="com.sample.domain">

    <class name="Person" table="person">

       <id name="id" type="long" column="ID">

          <generator class="native"></generator>

       </id>

       <property name="name" column="name" type="string"></property>

       <property name="sex" column="sex" type="boolean"></property>

       <property name="birthday" column="birthday" type="date"></property>

       <!-- 一对一关系映射 -->

 <one-to-one name="idCard class="IDCard" property-ref="person">

</one-to-one>

</class></hibernate-mapping>

IDCard.hbm.xml

<hibernate-mapping package="com.sample.domain">

   <class name="IDCard" table="idCard">

      <id name="id" type="long" column="ID">

         <generator class="native"></generator>

      </id>

      <property name="number" type="string" column="idnumber" unique="true"></property>

     <!-- 一对一关系映射 -->

      <many-to-one name="person" class="Person" column="person_id" unique="true"></many-to-one>

   </class>

</hibernate-mapping>

到此,我们已经完成了java持久化类的设计和它相应的映射文件,那么持久化类是怎样被持久化到数据库的呢?持久化类自己不会完成持久化动作,它需要依靠另外的一种对象完成持久化操作,这类对象叫做Data Access Object------DAO。下面我们来完成DAO层的设计。(待续........)

J2EE Web应用的轻量级解决方案:SSH(Spring+Struts+Hibernate)解析

SSH(Spring+Struts+Hibernate)解析 一个spring2.5+hibernate3.2+struts2.0组合框架,使用spring的 IoC来管理应用的 所有b...

J2EE框架(Struts&Hibernate&Spring)的理解

SSH: Struts(表示层)+Spring(业务层)+Hibernate(持久层) Struts: Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求。 在MVC框架中,Stru...

J2EE框架(Struts,Hibernate,Spring)的理解

SSH: Struts(表示层)+Spring(业务层)+Hibernate(持久层) Struts: Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求。 在MVC框架中,Stru...

基于 struts+spring+ibatis 的轻量级 J2EE 开发

JpetStore 4.0 是 ibatis 的最新示例程序,基于 Struts MVC 框架(注:非传统 Struts 开发模式),以 ibatis 作为持久化层。该示例程序设计优雅,层次清晰,可以...
  • duci2
  • duci2
  • 2012年04月22日 13:26
  • 550
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:我对轻量级(struts+spring+hibernate)j2ee的理解(一)
举报原因:
原因补充:

(最多只允许输入30个字)