使用MyEclipse集成SSH和DWR(二)整合Spring和Hibernate

来源:互联网 发布:重启linux服务器关闭 编辑:程序博客网 时间:2024/05/22 03:47

 开发环境:
  1. JDK 1.5
  2. Tomcat 6.0
  3. Spring 2.5
  4. Struts 1.3
  5. Hibernate 3.2
  6. DWR 2.0
  7. MyEclipse 6.5
  8. SQL Server 2005
本系列文章PDF格式及示例源代码请到此处下载

Spring整合Hibernate的价值在于Spring为Hibernate增加了以下内容:
   * Session management:Spring为Hibernate的session提供了有效、容易和安全的控制
   * Resource management:Spring控制Hibernate的SessionFactories,JDBC datasources及其它相关资源
   * Integrated transaction management:完整的事务管理
   * Exception wrapping:异常的包装

1. 利用Spring IoC容器创建SessionFactory

可以使用org.springframework.orm.hibernate3.LocalSessionFactoryBean创建SessionFactory实例, 共有以下二种方式:
1) 【最佳方案】直接使用Hibernate配置文件hibernate.cfg.xml
  Hibernate配置文件hibernate.cfg.xml如下:

  1. <?xml version='1.0' encoding='UTF-8'?>
  2. <!DOCTYPE hibernate-configuration PUBLIC
  3.           "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  4.          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  5. <hibernate-configuration>
  6.     <session-factory>
  7.         <property name="myeclipse.connection.profile">
  8.                 com.microsoft.sqlserver.jdbc.SQLServerDriver
  9.         </property>
  10.         <property name="connection.url">
  11.                 jdbc:sqlserver://localhost:1433;databaseName=SSH
  12.         </property>
  13.         <property name="connection.username">sa</property>
  14.         <property name="connection.password"></property>
  15.         <property name="connection.driver_class">
  16.                 com.microsoft.sqlserver.jdbc.SQLServerDriver
  17.         </property>
  18.         <property name="dialect">
  19.                 org.hibernate.dialect.SQLServerDialect
  20.         </property>
  21.         <mapping resource="cn/qdqn/ssh/entity/UserInfo.hbm.xml" />
  22.     </session-factory>
  23. </hibernate-configuration>

   Spring配置文件中SessionFactory初始化配置方法:

  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  2.         <property name="configLocation"
  3.             value="classpath:hibernate.cfg.xml">
  4.         </property>
  5. </bean>

2) 在Spring配置文件中整合所有Hibernate配置参数

  1. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  2.         <property name="driverClassName" 
  3.                           value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
  4.         <property name="url" 
  5.                           value="jdbc:sqlserver://localhost:1433;databaseName=SSH"/>
  6.         <property name="username" value="sa"/>
  7.         <property name="password" value=""/>
  8. </bean>
  9. <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  10.         <property name="dataSource" ref="dataSource">
  11.         </property>
  12.         <property name="mappingResources">
  13.             <list>
  14.                <value>cn/qdqn/ssh/entity/UserInfo.hbm.xml</value>
  15.             </list>
  16.         </property>
  17.         <property name="hibernateProperties">
  18.             <props>
  19.                 <prop key="hibernate.dialect">
  20.                      org.hibernate.dialect.SQLServerDialect
  21.                 </prop>
  22.                 <prop key="show_sql">true</prop>
  23.             </props>
  24.         </property>
  25. </bean>

  注意:使用MyEclipse集成SSH时,org.apache.commons.dbcp.BasicDataSource所在的包commons-dbcp-1.2.2.jar不会默认加载,另外还需加载commons-pool-1.4.jar,两者均可在Apache网站commons项目下找到。否则运行程序会出现以下异常:

  1. java.lang.NoClassDefFoundError: org/apache/commons/pool/impl/GenericObjectPool
  2. at java.lang.Class.getDeclaredConstructors0(Native Method)
  3. at java.lang.Class.privateGetDeclaredConstructors(Unknown Source)
  4. at java.lang.Class.getConstructor0(Unknown Source)
  5. at java.lang.Class.getDeclaredConstructor(Unknown Source)
  6. ……

2. Hibernate DAO开发

1) 使用Hibernate原生API实现DAO
   A. 使用原生API实现DAO


  1. public class UserInfoDAORaw implements IUserInfoDAO {
  2.     private SessionFactory sessionFactory;
  3.     public SessionFactory getSessionFactory() {
  4.         return sessionFactory;
  5.     }
  6.     public void setSessionFactory(SessionFactory sessionFactory) {
  7.         this.sessionFactory = sessionFactory;
  8.     }
  9.     public List findAll() {
  10.         return this.sessionFactory.getCurrentSession()
  11.                                                                .createQuery("from UserInfo").list();
  12.     }
  13. 部分代码省略……
  14. }

   B. 在applicationContext.xml配置原生DAO Bean 

  1. <bean id="userInfoDAORaw" class="cn.qdqn.ssh.dao.UserInfoDAORaw">
  2.         <property name="sessionFactory">
  3.             <ref bean="sessionFactory" />
  4.         </property>
  5. </bean>

   C. 运行测试

  1. ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
  2. UserInfoDAORaw dao=(UserInfoDAORaw)ctx.getBean("userInfoDAORaw");
  3. List<UserInfo> list=dao.findAll();
  4. for(UserInfo info : list){
  5.         System.out.println(info.getUserName()+"-"+info.getUserPwd());
  6. }

  结论:使用Hibernate原生API实现DAO可以做到Hibernate和Spring完全分离,缺点是无法利用Spring封装Hibernate所提供的额外功能。


2)【最佳方案】使用Spring框架所提供的HibernateDaoSupport类实现DAO
   A. 使用MyEclipse反向工程生成Spring 整合Hibernate 的DAO,该DAO继承自Spring的org.springframework.orm.hibernate3.support.HibernateDaoSupport 

  1. public class UserInfoDAO extends HibernateDaoSupport {
  2.     private static final Log log = LogFactory.getLog(UserInfoDAO.class);
  3.     // property constants
  4.     public static final String USER_NAME = "userName";
  5.     public static final String USER_PWD = "userPwd";
  6.     public void save(UserInfo transientInstance) {
  7.         log.debug("saving UserInfo instance");
  8.         try {
  9.             getHibernateTemplate().save(transientInstance);
  10.             log.debug("save successful");
  11.         } catch (RuntimeException re) {
  12.             log.error("save failed", re);
  13.             throw re;
  14.         }
  15.     }
  16. 部分代码省略……
  17. }

   B. 在applicationContext.xml配置DAO Bean 

  1. <bean id="userInfoDAO" class="cn.qdqn.ssh.dao.UserInfoDAO">
  2.         <property name="sessionFactory">
  3.             <ref bean="sessionFactory" />
  4.         </property>
  5. </bean>

   C. 运行测试

  1. ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
  2. UserInfoDAORaw dao=(UserInfoDAORaw)ctx.getBean("userInfoDAO");
  3. List<UserInfo> list=dao.findAll();
  4. for(UserInfo info : list){
  5. System.out.println(info.getUserName()+"-"+info.getUserPwd());
  6. }

  注意:HibernateDaoSupport通过getHibernateTemplate()方法得到HibernateTemplate实例进行保存、删除等操作,但是HibernateTemplate默认不进行事务处理,而在Hibernate中这些操作必须在事务下执行才能得到正确的结果,因此必须使用Spring声明式事务管理。


3. 使用Spring声明式事务管理

1) 使用Spring 1.x 的事务代理类进行事务管理
   A. 在applicationContext.xml中声明事务管理器,注入sessionFactory属性

  1. <bean id="transactionManager"
  2.     class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  3.         <property name="sessionFactory">
  4.             <ref local="sessionFactory" />
  5.         </property>
  6. </bean>

   B. 在applicationContext.xml中使用Spring AOP代理方式实现声明式事务

  1. <bean id="userInfoDAOProxy" class=
  2.                                                         "org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  3.         <!--必须为true时CGLIB才不用强制编写DAO接口-->
  4.         <property name="proxyTargetClass">
  5.             <value>true</value>
  6.         </property>
  7.         <property name="transactionManager">
  8.             <ref bean="transactionManager"/>
  9.         </property>
  10.         <property name="target">
  11.             <ref bean="userInfoDAO"/>
  12.         </property>
  13.         <property name="transactionAttributes">
  14.             <props>
  15.                 <prop key="*">PROPAGATION_REQUIRED</prop>
  16.             </props>
  17.         </property>
  18. </bean>

   C. 通过代理Bean获取DAO Bean,进行数据库操作

  1. ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
  2. UserInfoDAO dao=(UserInfoDAO)ctx.getBean("userInfoDAOProxy");
  3. UserInfo user=new UserInfo();
  4. user.setUserName("比尔盖茨");
  5. user.setUserPwd("windows");
  6. dao.save(user);

  问题1:运行程序会报以下异常:

  1. java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit(IILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V
  2.     at net.sf.cglib.core.ClassEmitter.begin_class(ClassEmitter.java:77)
  3.     at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:173)
  4.     at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
  5.     at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
  6.     at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:145)
  7.     at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:117)
  8.     at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:108)
  9.     at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:104)
  10.     at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
  11. …………

解决方法:原因是Spring与Hibernate所使用的asm版本冲突,删除asm.2.2.3.jar即可。
  问题2:对每个业务逻辑Bean或DAO Bean都要设置事务代理Bean将是一个非常庞大的工作量!
  改进方法: 可以通过定义“基类”来解决重复性编码!如:

  1. <bean id="baseDAOProxy" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  2.         <property name="proxyTargetClass">
  3.             <value>true</value>
  4.         </property>
  5.         <property name="transactionManager">
  6.             <ref bean="transactionManager"/>
  7.         </property>
  8.         <property name="transactionAttributes">
  9.             <props>
  10.                 <prop key="*">PROPAGATION_REQUIRED</prop>
  11.             </props>
  12.         </property>
  13. </bean>
  14.     
  15. <bean id="userInfoDAOProxy" parent="baseDAOProxy">
  16.         <property name="target">
  17.             <ref bean="userInfoDAO"/>
  18.         </property>
  19. </bean>

  结论:采用Spring 1.x配置事务要额外配置一个代理对象,原来Bean的获取方式也要修改,因此,也是一种“侵入式”的解决方案,虽然没有侵入到Bean程序代码中。
2) 使用Spring 2.x 的aop 和tx 声明式配置进行事务管理
   A. 在applicationContext.xml中添加aop和tx名称空间

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2.     xmlns:aop="http://www.springframework.org/schema/aop"
  3.     xmlns:tx="http://www.springframework.org/schema/tx"
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5.     xsi:schemaLocation="
  6.         http://www.springframework.org/schema/beans 
  7.         http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  8.         http://www.springframework.org/schema/tx
  9.         http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
  10.         http://www.springframework.org/schema/aop
  11.         http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
  12.  …………
  13. </beans>

   B. 在applicationContext.xml中声明事务管理器,注入sessionFactory属性

  1. <bean id="transactionManager"
  2. class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  3.         <property name="sessionFactory">
  4.             <ref local="sessionFactory" />
  5.         </property>
  6. </bean>

   C. 通过 <tx:advice>定义事务通知

  1. <tx:advice id="txAdvice" transaction-manager="transactionManager">
  2.         <tx:attributes>
  3.             <tx:method name="add*" propagation="REQUIRED"/>
  4.             <tx:method name="del*" propagation="REQUIRED"/>
  5.             <tx:method name="update*" propagation="REQUIRED"/>
  6.             <tx:method name="do*" propagation="REQUIRED"/>
  7.             <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
  8.         </tx:attributes>
  9. </tx:advice>

   D. 将事务通知advice和切面pointcut组合起来

  1. <aop:config>
  2.         <aop:pointcut id="daoMethods" expression="execution(* cn.qdqn.ssh.dao.*.*(..))"/>
  3.         <aop:advisor advice-ref="txAdvice" pointcut-ref="daoMethods"/>
  4. </aop:config>

   E. 两种应用测试:
   a) 对于Java Application,直接获取DAO Bean,进行数据库操作

  1. ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
  2. UserInfoDAO dao=(UserInfoDAO)ctx.getBean("userInfoDAO");
  3. UserInfo user=new UserInfo();
  4. user.setUserName("比尔盖茨");
  5. user.setUserPwd("windows");
  6. dao.save(user);

  问题:运行程序会报以下异常

  1. Exception in thread "main" java.lang.ClassCastException: $Proxy1
  2.                  at cn.qdqn.ssh.test.AddUserInfo.main(AddUserInfo.java:18)

  解决方法:此时唯有JDK 基于接口的代理将起作用,因此每个BO或DAO类必须要有对应的Interface,可以使用MyEclipse的重构功能生成BO或DAO类的接口定义,将获取的BO或DAO Bean放在相应接口对象的引用中即可。代码修改如下:

  1. ApplicationContext ctx=
  2. new ClassPathXmlApplicationContext("applicationContext.xml");
  3. IUserInfoDAO dao=(IUserInfoDAO)ctx.getBean("userInfoDAO");
  4. UserInfo user=new UserInfo();
  5. user.setUserName("比尔盖茨");
  6. user.setUserPwd("windows");
  7. dao.save(user);

   b) 对于Web Application,在Struts Action定义BO或DAO,通过Spring在action-servlet.xml中进行注入

  1. public class AddAction extends Action {
  2.     private UserInfoDAO userInfoDAO;
  3.     public UserInfoDAO getUserInfoDAO() {
  4.         return userInfoDAO;
  5.     }
  6.     public void setUserInfoDAO(UserInfoDAO userInfoDAO) {
  7.         this.userInfoDAO = userInfoDAO;
  8.     }
  9. …………
  10. }
  1. <bean name="/add" class="cn.qdqn.ssh.struts.action.AddAction">
  2.       <property name="userInfoDAO">
  3.         <ref bean="userInfoDAO"/>
  4.       </property>
  5. </bean>

        问题:启动Tomcat会报以下异常

  1. org.springframework.beans.factory.BeanCreationException: Error creating bean with name '/add' defined in ServletContext resource [/WEB-INF/action-servlet.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
  2. PropertyAccessException 1: org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy1] to required type [cn.qdqn.ssh.dao.UserInfoDAO] for property 'userInfoDAO'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [$Proxy1] to required type [cn.qdqn.ssh.dao.UserInfoDAO] for property 'userInfoDAO': no matching editors or conversion strategy found
  3. Caused by: 
  4. org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessException details (1) are:
  5. PropertyAccessException 1:
  6. org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy1] to required type [cn.qdqn.ssh.dao.UserInfoDAO] for property 'userInfoDAO'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [$Proxy1] to required type [cn.qdqn.ssh.dao.UserInfoDAO] for property 'userInfoDAO': no matching editors or conversion strategy found
  7.  …………

        解决方法:同Java Application所遇错误相类似,只需将Struts Action定义的等待被注入的BO或DAO替换为其相应的Interface形式即可纠正该错误。如下代码:

  1. public class AddAction extends Action {
  2.     private IUserInfoDAO userInfoDAO;
  3.     public IUserInfoDAO getUserInfoDAO() {
  4.         return userInfoDAO;
  5.     }
  6.     public void setUserInfoDAO(IUserInfoDAO userInfoDAO) {
  7.         this.userInfoDAO = userInfoDAO;
  8.     }
  9. …………
  10. }

3) 【最佳方案】使用Spring 2.x 的@Transactional标注进行事务管理
   A. 在BO或DAO类中添加事务标注@Transactional

  1. import org.springframework.transaction.annotation.Transactional;
  2. @Transactional
  3. public class UserInfoDAO extends HibernateDaoSupport {
  4.     private static final Log log = LogFactory.getLog(UserInfoDAO.class);
  5.     public static final String USER_NAME = "userName";
  6.     public static final String USER_PWD = "userPwd";
  7.     public void save(UserInfo transientInstance) {
  8.         log.debug("saving UserInfo instance");
  9.         try {
  10.             getHibernateTemplate().save(transientInstance);
  11.             log.debug("save successful");
  12.         } catch (RuntimeException re) {
  13.             log.error("save failed", re);
  14.             throw re;
  15.         }
  16.     }
  17. 部分代码省略……
  18. }

   B. 在applicationContext.xml中添加transactionManager和 <tx:annotation-driven>

  1. <bean id="transactionManager"
  2.             class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  3.         <property name="sessionFactory">
  4.             <ref local="sessionFactory" />
  5.         </property>
  6. </bean>
  7. <tx:annotation-driven transaction-manager="transactionManager" 
  8.                                        proxy-target-class="true"/>

  注意:proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。 
   C. 测试运行,一切正常

  1. ApplicationContext ctx=
  2. new ClassPathXmlApplicationContext("applicationContext.xml");
  3. UserInfoDAO dao=(UserInfoDAO)ctx.getBean("userInfoDAOProxy");
  4. UserInfo user=new UserInfo();
  5. user.setUserName("比尔盖茨");
  6. user.setUserPwd("windows");
  7. dao.save(user);
原创粉丝点击