论坛首页 Java企业应用论坛

spring+hibernate多层web开发eclipse下的开发模板

浏览 16699 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2004-12-18  
本人吐血奉献,内容包括:[list]
    1. xdoclet生成hbm配置文件和sql语句。
     2. xdoclet生成spring的配置文件applicationContext.
     3. 容器管理事务并解决延迟加载问题.
  4. 解决国际化和中文问题.
   5. bo、dao、business service、controller、view(jstl或jsp),一共五层结构。
  6. 表单绑定、表单验证。能绑到bo的尽量使用bo来绑定,不能绑到bo就要自己做command(类似于struts 的ActionForm)。
  7. Dao测试用例的设计技巧。Dao测试用例的设计要做到(我总结的经验,对错否还望指正):[list]
    (1) 独立性: 测试使用的数据记录由测试程序自己生成.
      (2) 可移植性:通过CVS check out到另外机器也能通过。--公司领导在其机器上check out 后运行测试看到绿色也开心。
(3) 对数据库无入侵性:测试程序生成的数据记录最后也由自己删除。
(4) 测试完全性:尽力保证测完所有方法且当然希望是通过的,要做到这点有点难,特别是涉及多表查询时,所以我只说是“尽力”。
[/list:u]
Service层的测试用例也应该这样设计的,但有时需要初始化的数据量太大,最后要删除的数据也太多,我感觉得不偿失,大家可量力而行。
   8. 使用Hibernate映射解决树形数据结构的例子--Cat.java,在这里面也包含使用version乐观锁定的xdoclet tag的书写格式。
   9. 使用继承策略简化Dao的编写.这点很重要,使用继承,有些dao接口和接口实现里面的方法是空的。
[/list:u]
--------------------------------
^_^以下说明写地有点乱,我有时间再改。^_^
--------------------------------

环境说明:

  spring+hibernate是轻量级的解决方案,在任何j2ee容器都能运行.
  我使用的mysql4+tomcat5,配置文件也是针对mysql的.为了让初学者能很快上手,也建议你使用mysql4+tomcat5,这样你就不用修改里面配置啦!

项目说明:

[list]1.  该项目是一个hibernate+spring+xdoclet的eclipse3++myeclipse下的配置模板:
    从bo、dao interface、dao implement、daoTest、bussiness interface、bussiness implement、bussiness test、command(类似于ActionForm)、Controller(类似于Action)、Validator(类似于struts的validator)、jsp&jstl view都包含,自己慢慢看吧。2.  hibernate的配置文件、sql语句生成使用xdoclet,spring bean的配置文件也用xdoclet生成,完全自动化呀,很酷!
3.  这是一个小型宠物管理,猫下面有孩子猫,所以是一个用ibernate实现树形的猫的数据结构,很强的hibernate啊![/list:u]

运行说明:

  安装eclipse3+myeclipse(配套eclipse3的版本的myeclipse)先,在eclipse里面建立一个名为pet的工程,把这个工程拷贝粘贴覆盖过去,再刷新工程绝对ok!
[list]1.  修改根目录下的hibernate.properties文件,本人用mysql,如果你也用mysql,改userName和password就可以了--默认的配置是userName=root,password为空,假如你安装mysql没有设置root的密码,那就什么都不用改。
2.  下载spring、hibernate2.x、xdoclet2.1、ant、mysql_jdbc_driver(我用这个driver:mysql-connector-java-3.0.14-production-bin.jar),把它们lib目录下所有的jar文件都放入到WebRoot/WEB-INF/lib目录下--初学者这样最省事,哪些jar是需要的也别管。
3.  运行src/build.xml,然后右键点击工程pet,刷新,生成src/org/ggyy/bo/*.hbm.xml文件;再运行src/build.xml,再刷新工程,生成src/sql.ddl文件(这是eclipse3.0+myeclipse下的毛病,不能自动刷新,其它ide我没试过,我只喜欢eclipse!^_^).
4.  运行org.ggyy.util.DataBaseTask(这是我写的一个类),读取src/sql.ddl文件,然后往mysql数据库里面(使用mysql内置的test数据库)发送sql语句.
5.  运行springBuild.xml(也是ant脚本,eclipse不能自动刷新,我干脆把每个target分开!这样运行ant,郁闷ing),生成spring的配置文件WebRoot/WEB-INF/applicationContext.xml. 额外的bean声明写在src/spring-beans.xml,由spring的xdoclet将其合并到aplicationContext.xml里面去--自己看看就明白了.
6. 发布到tomcat5,启动tomcat服务器,浏览器键入:http://localhost:8080/pet/db/listCat.sf
[/list:u]

国际化和中文问题解决说明:

[list]
1.  messages_zh_CN.properties文件必须使用JDK提供的转码工具native2ascii.exe进行转换:
    native2ascii messages_zh_CN.properties msg.txt
  把生成的目标文件msg.txt拷贝粘贴替换掉Messages_zh_CN.properties里面的内容,这样就不会乱码了.
2.  中文问题我是这样解决的:
   (1) src/spring-beans.xml有如下声明(spring-beans.xml实际上经sprng的xdcolet处理最终合并到applicationContext.xml里面):
 
      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		 <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
		 <property name="url"><value>jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=GB2312</value></property>
		 <property name="username"><value>root</value></property>
		 <property name="password"><value></value></property>
	   </bean>
	   

  这里使用mysql里面的test数据库,请注意url中的useUnicode=true&characterEncoding=GB2312.这是mysql特有的,其它数据库环境我没试过,使用其它数据库的朋友要注意.
   (2) WebRoot/WEB-INF/web.xml有编码的fillter声明,也是gb2312,自己看吧.
   (3) 每一个jsp头文件, <%@ page contentType="text/html; charset=gb2312"%>,当然也gb2312也!
3.其它语言我没试过.
[/list:u]

用容器管理事务来解决Hibernate的延迟加载问题:

[list]
  1.在spring里面解决延迟加载的问题很简单,只要给方法配置一个事务,有事务上下文,就有HibernateSession上下文,就不存在延迟加载的问题.这个问题用AOP来解决可能好一些,可惜这样的拦截器我不会^_^.
  2.容器管理的事务只在Dao层和Service层进行管理(在Controller层也管理事务我感觉很变态),由于没有使用OpenSessionInView 的filtter,在view层就有延迟加载的问题.为了避免这种现象,在Service或Dao层事先就把view层所需要的数据传递给Controller层,再由Controller层传给view层;同时约定,除非Controller层已经明确地把从表的数据加载,否则在view层不要试图取得从表类的数据!
[/list:u]
   发表时间:2004-12-18  

使用基类实现Dao说明


bo包中:

Entity.java.
   所有bo类的基类,只有一个属性id,这样你就可以统一控制主键的生成策略.
package org.ggyy.bo;
public class Entity {
    private long id = -1;
    /**
     * @hibernate.id 
     * generator-class="native" 
     * unsaved-value="-1"
     */
    public long getId(); {
        return id;
    }
    public void setId(long i); {
        id = i;
    }
    public boolean equals(Object arg0); {
        return this.getId(); == ((Entity); arg0);.getId();;
    }
}

Cat.java.
   bo  的一个例子,派生自Entity。使用version的锁定.
 package org.ggyy.bo;
/**
 * 树形数据结构的例子:树猫,有parent和children,且映射到同一个字段:fk_parent_id
 */
/**
 * @hibernate.class table="tbl_cat" dynamic-update="true" dynamic-insert="true"
 *                  optimistic-lock="version"
 */
public class Cat extends Entity {
    private Set children = new HashSet();;
    private Cat parent;
    private Owner owner;
    private String name;
    private Integer version;
    /**
     * @hibernate.many-to-one 
     *   column="fk_owner_id" 
     *   class="org.ggyy.bo.Owner"
     *   cascade="save-update"
     */
    public Owner getOwner(); {
        return owner;
    }
    public void setOwner(Owner owner); {
        this.owner = owner;
    }
    /**
     * @hibernate.version
     */
    public Integer getVersion(); {
        return version;
    }
    public void setVersion(Integer version); {
        this.version = version;
    }
    /**
     * @hibernate.set 
     *    cascade="all"
     *    inverse="true"
     *    lazy="true"
     * @hibernate.collection-key 
     *    column="fk_parent_id"
     * @hibernate.collection-one-to-many 
     *    class="org.ggyy.bo.Cat"
     */
    public Set getChildren(); {
        return children;
    }

    public void setChildren(Set children); {
        this.children = children;
    }

    /**
     * @hibernate.many-to-one 
     *    column="fk_parent_id" 
     *    class="org.ggyy.bo.Cat"
     *    cascade="save-update"
     */
    public Cat getParent(); {
        return parent;
    }

    public void setParent(Cat parent); {
        this.parent = parent;
    }
   /**
    * @hibernate.property
    */
    public String getName(); {
        return name;
    }
    public void setName(String name); {
        this.name = name;
    }
}


Dao包中:

IEntityDao.java.
    这个Dao接口是所有Dao接口的父接口.其它Dao接口只要继承这个接口,增加、删除、修改、load的方法就不用写了,只需要关注属于自己的finder(或filtter)方法就可以了.
    增加和修改操作合为一个方法:public void store(Entity entity)。它实际调用saveOrUpdate的方法.
    你也可以加入更多的共有方法,这样要看你的项目的需要和你自己对所建的系统的理解和抽象能力.
package org.ggyy.dao;
public interface IEntityDao {
    public Entity load(String id); throws DataAccessException;

    public void store(Entity entity); throws DataAccessException;

    public void delete(String id); throws DataAccessException;

    public void delete(Entity entity); throws DataAccessException;

}

ICatDao.java.
    这个Dao只需要很少的方法,因为基类已经有了增删改load的方法了.
package org.ggyy.dao;
public interface ICatDao extends IEntityDao {
    /**
     * 树形遍历
   */
    public void treeVisitCat(String catId, IVisit visit);
            throws DataAccessException;
    /**
     * 查找所有孩子,要遍历树,使用HQL很难解决的^_^
   */
    public List findAllChildren(String  catId); throws DataAccessException;
   /**
     *查找直接子节点,直接使用HQL。
     */
    public List findDirectChildren(String catId); throws DataAccessException;
    public List findRootCats(); throws DataAccessException;

}


IVisit.java
   遍历接口,看一下CaoDaoImpl中的findAllChildren的方法就知道如何使用了这个接口了.
package org.ggyy.dao;
public interface IVisit {
    public void visit(Cat c);;
}


Dao的实现:

EntityDaoImpl.java.这是一个抽象类,由于事先不知道bo的class,所以推后由每一个Dao实现来完成.
abstract public class EntityDaoImpl extends HibernateDaoSupport implements
        IEntityDao {
   /**
     *抽象方法,是留子类实现的。
     */
   abstract protected Class getEntityClass();;
    public Entity load(String id); throws DataAccessException {
        return (Entity); this.getHibernateTemplate();.load(this.getEntityClass();,
                new Long(id););;
    }
    public void store(Entity entity); throws DataAccessException {
        this.getHibernateTemplate();.saveOrUpdate(entity);;
    }
    public void delete(Entity entity); throws DataAccessException {
        this.getHibernateTemplate();.delete(entity);;
    }
    public void delete(String  id); throws DataAccessException {
        Entity entity = (Entity); this.getHibernateTemplate();.load(
                this.getEntityClass();, new Long(id););;
        this.getHibernateTemplate();.delete(entity);;
    }
    }

CatDaoImpl.java.
     继承于EntityDaoImpl.
     我给treeVisitCat和findAllChildren这两个方法配置了一个readonly的事务,并不是因为这个方法需要事务,而是为了解决延迟加载的问题.
     在本系统里,所有的一对多都是cascade="all" inverse="true" lazy="true"; 所有的的多对一都是cascade="save-update".主表类不负责从表类的加载,也不负责维护主从关系,这样的做我感觉性能是最好的.但由于lazy="true",下面这个方法的cat.iterator()再往下运行就出错了(是因为延迟加载的问题).但给它配置一个事务就不同了,有了事务上下文(我也不知道是不是这样称呼),就有HibernateSession的上下文,这样就不会有延迟加载的问题啦.
     这样的做法有点无耻,为了弥补性能的损失,我只好把Transaction做成readonly.
     对于这种query,使用OpenSessionInView是行,但这样HibernateSession不好控制. 我想过了,最好方法是使用AOP给它配置一个HibernateSession的上下文,也就是使用AOP来拦截,可惜我不会^_^.
/**
 * @spring.bean id ="catDaoTarget"
 * @spring.property name="sessionFactory" ref="sessionFactory"
 */
public class CatDaoImpl extends EntityDaoImpl implements ICatDao {
    /**
       * 需要readonly的事务,是为了解决Hibernate的延迟加载问题.
       */
    public List findAllChildren(String catId); throws DataAccessException {
        final List children = new ArrayList();;
        this.treeVisitCat(catId, new IVisit(); {
            public void visit(Cat c); {
                children.add(c);;
            }
        });;
        return children;
    }
      /**
         * 需要readonly的事务,是为了解决Hibernate的延迟加载问题.
         */
       public void treeVisitCat(String catId, final IVisit visit);
            throws DataAccessException {
        Cat catt = (Cat); getHibernateTemplate();
                .load(Cat.class, new Long(catId););;
        Stack s = new Stack();;
        s.push(catt);;
        while (s.empty(); == false); {
            Cat c = (Cat); s.pop();;
            visit.visit(c);;
            Set children = c.getChildren();;
            if (children != null && !children.isEmpty();); {
                Iterator ci = children.iterator();;
                while (ci.hasNext();); {
                    Cat cc = (Cat); ci.next();;
                    s.push(cc);;
                }
            }
        }
    }
    protected Class getEntityClass(); {
        return Cat.class;
    }
    public List findDirectChildren(String catId); throws DataAccessException {
        return this.getHibernateTemplate();.find(
                "select cat from Cat as cat where cat.parent.id=?",
                new Long(catId););;
    }
    public List findRootCats(); throws DataAccessException {
        return this.getHibernateTemplate();.find(
                "select cat from Cat as cat where cat.parent=null");;
    }
}


IOwnerDao.java和OwnerDaoImpl.java

可以看出它们基本上等于空,OwnerDaoImpl只有一个方法:
   protected Class getEntityClass() {
        return Owner.class;
    }
假如基类IEntityDao设计得很好的话,子类的很多方法很多方法都可以省了。
package org.ggyy.dao;
/**
 * @author jiangyubao
 *OwnerDao的接口
 */
public interface IOwnerDao extends IEntityDao{

}

package org.ggyy.dao.hibernate;
import org.ggyy.bo.Owner;
import org.ggyy.dao.IOwnerDao;
/**OwnerDao的实现
 * @author jiangyubao
 * @spring.bean id="ownerDao"
 * @spring.property name="sessionFactory" ref="sessionFactory"
 */
public class OwnerDaoImpl extends EntityDaoImpl implements IOwnerDao {
    protected Class getEntityClass(); {
        return Owner.class;
    }
}
0 请登录后投票
   发表时间:2004-12-19  
谢谢楼主的共享,请问能不能给出比较详尽的ApplicationContext.xml文件内容?
0 请登录后投票
   发表时间:2004-12-20  

这个文件其实是使用ant 运行 一下那个src/springBuild.xml文件就可以生成的了。运行方法:选中src/springBuild.xml,右键,选择run->ant build

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

<!DOCTYPE beans PUBLIC
    "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">

<beans
  default-autowire="no"
  default-lazy-init="false"
  default-dependency-check="none"
>

  <bean
      id="addCatFC"
      class="org.ggyy.web.ctrl.AddCatFC"
  >

    <property name="catDao">
      <ref bean="catDao"/>
    </property>
    <property name="validator">
      <ref bean="catFVLD"/>
    </property>
    <property name="formView">
      <value>catForm.jsp</value>
    </property>
    <property name="sessionForm">
      <value>true</value>
    </property>
  </bean>

  <bean
      id="catFVLD"
      class="org.ggyy.web.vld.CatFVLD"
  >

  </bean>

  <bean
      id="managerServiceTarget"
      class="org.ggyy.service.spring.ManagerServiceImpl"
  >

    <property name="catDao">
      <ref bean="catDao"/>
    </property>
  </bean>

  <bean
      id="catDaoTarget"
      class="org.ggyy.dao.hibernate.CatDaoImpl"
  >

    <property name="sessionFactory">
      <ref bean="sessionFactory"/>
    </property>
  </bean>

  <bean
      id="ownerDao"
      class="org.ggyy.dao.hibernate.OwnerDaoImpl"
  >

    <property name="sessionFactory">
      <ref bean="sessionFactory"/>
    </property>
  </bean>

  <bean
      id="editCatFC"
      class="org.ggyy.web.ctrl.EditCatFC"
  >

    <property name="catDao">
      <ref bean="catDao"/>
    </property>
    <property name="formView">
      <value>catForm.jsp</value>
    </property>
    <property name="sessionForm">
      <value>true</value>
    </property>
    <property name="validator">
      <ref bean="catFVLD"/>
    </property>
  </bean>

      <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
                <property name="basename"><value>messages</value></property>
       </bean>
       <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
		<property name="url"><value>jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=GB2312</value></property>
		<property name="username"><value>root</value></property>
		<property name="password"><value></value></property>
	   </bean>
       <bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
                <property name="dataSource"><ref local="dataSource"/></property>
                <property name="mappingResources">
                        <list>
                                 <value>org/ggyy/bo/Cat.hbm.xml</value>
                                 <value>org/ggyy/bo/Owner.hbm.xml</value>
                         </list>
                </property>
                <property name="hibernateProperties">
                        <props>
                                <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
                                <prop key="hibernate.show_sql">true</prop>
                        </props>
                </property>
        </bean>
        <bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
		        <property name="dataSource"><ref local="dataSource"/></property>                
                <property name="sessionFactory"><ref local="sessionFactory"/></property>
        </bean>
        <!-- user1 define bean here -->
        <bean id="catDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                <property name="transactionManager"><ref local="transactionManager"/></property>
                <property name="target"><ref local="catDaoTarget"/></property>
                <property name="transactionAttributes">
                        <props>
                                <prop key="findAllChildren">PROPAGATION_REQUIRED,readOnly</prop>
                                <prop key="treeVisitCat">PROPAGATION_REQUIRED,readOnly</prop>
                        </props>
                </property>
         </bean>
          <!-- user2 define bean here -->
         <bean id="managerService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                <property name="transactionManager"><ref local="transactionManager"/></property>
                <property name="target"><ref local="managerServiceTarget"/></property>
                <property name="transactionAttributes">
                        <props>
                                <prop key="manCat">PROPAGATION_REQUIRED</prop>          
                        </props>
                </property>
         </bean>
         <bean id="catControllerMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
			 <property name="mappings">
				<props>
					<prop key="/db/listCat.sf">listCatHandler</prop>
					<prop key="/db/deleteCat.sf">deleteCatHandler</prop>
				</props>
			</property>
	     </bean>

</beans>
0 请登录后投票
   发表时间:2004-12-24  
其实类似的模板也可以参考Appfuse,作者也写了一个AppGen工具,在1.6.1版本中就已经解决了国际化的问题,在Ant命令中直接执行了native2ascii,感觉挺方便的。

我已经把Appfuse的帮助的主要开发部分翻译完了,请参见:blog.csdn.net/ltf_ty.
0 请登录后投票
   发表时间:2004-12-26  
ltf_ty 写道
其实类似的模板也可以参考Appfuse,作者也写了一个AppGen工具,在1.6.1版本中就已经解决了国际化的问题,在Ant命令中直接执行了native2ascii,感觉挺方便的。

我已经把Appfuse的帮助的主要开发部分翻译完了,请参见:blog.csdn.net/ltf_ty.


读了一遍,感觉翻译的很不错。不过有点遗憾的是,作者似乎在Service哪里写的太少了点,他说他写的Service的作用是为了将表现层和数据库层解耦,但我不是很理解他的写法,他不过是用Service调用了一边DAO层的方法,怎么能做到和表现层解耦? 他没有解释的很明确,不知道你翻译完了之后是怎么理解的。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics