Spring对Hibernate的支持

http://subclipse.tigris.org/update

 

精通Spring——Java轻量级架构开发实践 10.4 Spring对Hibernate的支持 
http://book.csdn.net/ 2006-8-15 16:27:00
图书导读 当前章节:10.4 Spring对Hibernate的支持·4.2 工厂模式(Design Pattern:Factory Method)的精髓·4.3 单例模式(Design Pattern:Singleton)·10.3 Spring对IBatis的支持·10.5 小结·18.1 模仿对象·18.2 Spring Mock简介Java EE开源框架培训
蓝点世纪外企高端Java软件工程师培训助你实现月薪 1000-6000元的飞跃!4个月积累1年的经验值
www.fsailing.com/
Web2.0 个人桌面
i桌面,与微软Live争锋 网络桌面新概念
live.csdn.net
Oracle身份管理与萨班斯法案
Oracle身份管理的重要意义在哪里? 获得Oracle 10最新免费资源
ad.cn.doubleclick.ne...

如上文所述,Spring对DAO模式具有概念和风格上的一致性,所以无论使用哪种持久方案(比如JdbcTemplate、SqlMapClientTemplate以及稍后将要介绍的HibernateTemplate)都会感到非常亲切。

下文将通过Spring改写上一章给出的HibernateStockDao,并向其中添加一些额外的功能。

说明:本书中使用的的是Hibernate3,所以也将使用Spring对于Hibernate3支持的那部分功能(当然,Spring对早期版本的Hibernate也提供支持)。

10.4.1  在Spring上下文中配置SessionFactory
通过上文的描述,可以知道,Spring使用JdbcTemplate时必须和特定的数据源进行绑定。而在Hibernate中,数据源是对用户屏蔽的,它使用一个称为“Session”的强劲武器。

Session具有建立或取消对象的持久关联、同步对象状态和数据库以及事务管理等等复杂功能。Session是Hibernate的核心概念,就如同SqlMap与之IBatis一样。Session的创建依赖于Hibernate的SessionFactory,SessionFactory和Session一样,也是Hibernate的核心组件。

和IBatis的整合方式一样,Spring会将Hibernate基本配置文件中的数据源属性抽离到Spring自身的配置文件中,以提供统一的数据源管理和注射。

首先,给出Hibernate的配置文件,如代码10.19所示。

代码10.19  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>

<!-- Database connection settings -->

<!--    <property name="connection.driver_class">-->

<!--      org.postgresql.Driver-->

<!--    </property>-->

<!--    <property name="connection.url">-->

<!--      jdbc:postgresql://1210.0.0.1:5432/hibernate-->

<!--    </property>-->

<!--    <property name="connection.username">postgres</property>-->

<!--    <property name="connection.password">1111</property>-->

 

    <!-- SQL dialect -->

    <property name="dialect">

      org.hibernate.dialect.PostgreSQLDialect

    </property>

    <!-- Echo all executed SQL to stdout -->

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

    <!-- Drop and re-create the database schema on startup -->

    <property name="hbm2ddl.auto">create</property>

 

    <mapping resource="chapter7/hibernate/domain/Category.hbm.xml" />

    <mapping resource="chapter7/hibernate/domain/Product.hbm.xml" />

 

  </session-factory>

</hibernate-configuration>

注意,代码10.19中被注释掉的部分,它将被抽离到了Spring的上下文配置文件,而其余部分则暂时保持不变。

下面给出Spring的配置文件,如代码10.20所示。

代码10.20  dataAccessContext-local.xml

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

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

 

<beans>

  <bean id="propertyConfigurer"

class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <property name="locations">

      <list>

        <value>classpath:jdbc.properties</value>

      </list>

    </property>

  </bean>

 

  <bean id="dataSource"

class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

    <property name="driverClassName" value="${jdbc.driverClassName}"/>

    <property name="url"

    value="jdbc:postgresql://127.0.0.1:5432/hibernate"/>

    <property name="username" value="${jdbc.username}"/>

    <property name="password" value="${jdbc.password}"/>

  </bean>

 

  <bean id="sessionFactory"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="dataSource"/>

    <property name="configLocations">

      <value>chapter7/spring/hibernate/hibernate.cfg.xml</value>

    </property>

<!--    <property name="mappingResources">-->

<!--      <list>-->

<!--        <value>chapter7/hibernate/domain/Category.hbm.xml</value>-->

<!--        <value>chapter7/hibernate/domain/Product.hbm.xml</value>-->

<!--      </list>-->

<!--    </property>-->

<!--    <property name="hibernateProperties">-->

<!--      <props>-->

<!--        <prop key="hibernate.dialect">

                 org.hibernate.dialect.PostgreSQLDialect

               </prop>-->

<!--        <prop key="show_sql">true</prop>-->

<!--        <prop key="hbm2ddl.auto">create</prop>-->

<!--      </props>-->

<!--    </property>-->

  </bean>

 

</beans>

可以发现,从代码10.19抽离出的数据源,现在换成了Apache的DBCP连接池。当然,也可以换作其他实现,比如从一个JNDI上获取的数据源:

  <bean id="dataSource"

  class="org.springframework.jndi.JndiObjectFactoryBean">

    <property name="jndiName" value="java:comp/env/jdbc/myds"/>

  </bean>

Spring提供了LocalSessionFactoryBean来创建本地的Hibernate SessionFactory,这就类似于上节中介绍的SqlMapClientFactoryBean(它用以创建IBatis的SqlMap)。当然也可以创建绑定在JNDI上的SessionFactory,不过这通常只在EJB环境下使用。

注意:代码10.20中被注释掉的部分,如果不使用LocalSessionFactoryBean的configLocations属性读取Hibernate的原生配置文件,可由Spring的LocalSessionFactoryBean负责配置Hibernate,它和hibernate.cfg.xml的作用完全一致,这时候就不需要Hibernate的原生配置了。

10.4.2  重建Hibernate进货DAO伪实现
有了以上对Hibernate SessionFactory的正确配置后,下文将重建一个Hibernate StockDao(曾在第九章9.7节给出过),其中主要展示了HinbernateTemplate和HibernateCallback(这两个类也是Spring给出的,它们用于简化Hibernate的操作)的使用方法。

首先为上一章代码9.31的HibernateStockDao抽象出一个接口,并且增设一个查询方法,如代码10.21所示。

代码10.21  StockDao.java

 

package chapter10.spring.hibernate;

 

import java.util.List;

import chapter10.hibernate.domain.Category;

 

public interface StockDao {

  public void createCategoryCascade(Category category);

  public void deleteCategoryCascade(Category category);

  /**

   * 通过Category,查找Product列表

   */

  public List findProductByCategory(Category category);

}

接着撰写一个StockDao的伪实现,它暴露了一个HibernateTemplate属性,用以接受注射,如代码10.22所示。

代码10.22  HibernateStockDao.java

package chapter10.spring.hibernate;

 

import java.util.List;

import org.springframework.orm.hibernate3.HibernateTemplate;

import chapter10.hibernate.domain.Category;

 

public class HibernateStockDao implements StockDao {

 

  private HibernateTemplate template;

 

  public void setTemplate(HibernateTemplate template) {

    this.template = template;

  }

 

  public void createCategoryCascade(Category category) {

  }

 

  public void deleteCategoryCascade(Category category) {

  }

 

  public List findProductByCategory(final Category category) {

    return null;

  }

}

10.4.3  TDD又来了:规划测试案例
有了以上这套DAO伪实现之后,先不急着撰写DAO的具体实现,回想一下第三章介绍的测试驱动开发(TDD),这里不妨再次运用它。

首先是规划测试,沿用上一章代码9.32的测试案例并改写,使其中的StockDao和JdbcTemplate接受IoC注射(这里引入JdbcTemplate目的只是为了方便测试),如代码10.23所示。

代码10.23  HibernateStockDaoTest.java

 

package chapter10.spring.hibernate;

 

import java.util.List;

 

import junit.framework.TestCase;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.jdbc.core.JdbcTemplate;

 

import chapter10.hibernate.domain.Category;

import chapter10.hibernate.domain.Product;

 

public class HibernateStockDaoTest extends TestCase {

 

  private StockDao stockDao;

  private JdbcTemplate jdbcTemplate;

  private Product product1;

  private Product product2;

  private Category category;

 

  protected void setUp() throws Exception {

      ApplicationContext ctx = new ClassPathXmlApplicationContext(

       new String[]{"ch10/spring/hibernate/dataAccessContext-local.xml"});

 

    stockDao = (StockDao)ctx.getBean("stockDao");

    jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");

 

    category = new Category("RABBIT");

    category.setName("Rabbit");

    category.setDescn("Desciption of Rabbit");

 

    product1 = new Product("RABBIT-01");

    product2 = new Product("RABBIT-02");

    product1.setName("WhiteRabbit");

    product1.setDescn("Description of WhiteRabbit");

    product2.setName("BlackRabbit");

    product2.setDescn("Description of BlackRabbit");

 

    category.addProducts(product1);

    category.addProducts(product2);

  }

 

  public void testCreateCategory() {

    stockDao.createCategoryCascade(category);

    assertEquals(3, getCategoryAndProductSizeManually());

  }

 

  public void testDeleteCategory() {

    if(getCategoryAndProductSizeManually()==0) {

      stockDao.createCategoryCascade(category);

    }

    stockDao.deleteCategoryCascade(category);

    assertEquals(0, getCategoryAndProductSizeManually());

  }

 

  public void testRetrieveProductByCategory() {

    if(getCategoryAndProductSizeManually()==0) {

      stockDao.createCategoryCascade(category);

    }

    List productList = stockDao.retrieveProductBy(category);

    assertEquals(2, productList.size());

  }

 

  private int getCategoryAndProductSizeManually() {

    int categorySize = jdbcTemplate.queryForList(

      "SELECT * FROM CATEGORY WHERE CATID='RABBIT'").size();

    int productSize = jdbcTemplate.queryForList(

        "SELECT * FROM PRODUCT " +

        "WHERE PRODUCTID='RABBIT-01' OR PRODUCTID='RABBIT-02'").size();

    return (categorySize + productSize);

  }

 

 

}

预期地,该测试案例运行后将全部失败,这正是TDD第一步的效果,如图10.2所示。

 

图10.2  Spring结合Hibernate首次测试完败

10.4.4  TDD又来了:完善基础设施
检查后发现,错误的原因是没有在Spring配置文件中定义stockDao。

向代码10.20中追加三个定义:stockDao、HibernateTemplate、jdbcTemplate,如下:

  <bean id="jdbcTemplate"

 class="org.springframework.jdbc.core.JdbcTemplate">

    <constructor-arg ref="dataSource"></constructor-arg>

  </bean>

  <bean id="hibernateTemplate"

  class="org.springframework.orm.hibernate3.HibernateTemplate">

    <constructor-arg ref="sessionFactory"/>

  </bean>

  <bean id="stockDao"

  class="chapter10.spring.hibernate.HibernateStockDao">

    <property name="template" ref="hibernateTemplate"/>

  </bean>

再次运行测试案例,可以看到,基础设施已经搭建成功,现在依然存在的错误是未实现HibernateStockDao。

10.4.5  添加HibernateTemplate和HibernateCallback实现,交付测试
下面的任务就是使用HibernateTemplate和HibernateCallback来完成DAO的具体实现,如代码10.24所示。

代码10.24  HibernateStockDao.java

package chapter10.spring.hibernate;

 

import java.util.List;

 

import org.hibernate.HibernateException;

import org.hibernate.Session;

import org.springframework.orm.hibernate3.HibernateCallback;

import org.springframework.orm.hibernate3.HibernateTemplate;

 

import chapter10.hibernate.domain.Category;

 

public class HibernateStockDao implements StockDao {

 

  private HibernateTemplate template;

 

  public void setTemplate(HibernateTemplate template) {

    this.template = template;

  }

 

  public void createCategoryCascade(Category category) {

    template.save(category);

  }

 

  public void deleteCategoryCascade(Category category) {

    template.delete(category);

  }

 

/**

       * 使用HQL进行对象级别的查询

       */

  public List retrieveProductBy(final Category category) {

    final String catid = category.getCatid();

    return (List)template.execute(new HibernateCallback() {

      public Object doInHibernate(Session session)

        throws HibernateException {

        String category = "chapter10.hibernate.domain.Category";

        List products =

          session.createQuery("select category.products from "+

              category+" category where category.catid='"+catid+"'").list();

        return products;

      }

    });

  }

}

HibernateTemplate提供了许多便利的方法,并且在代码中不需要处理包括Session的打开关闭,事务的开启,异常地捕捉等等操作。并且,该template也是线程安全的。通过HibernateCallback接口的回调,就可以和Hibernate的Session打交道了。

上例中,通过回调,可以在doInHibernate()方法内执行更多的处理,比如调用原生Sesssion上的方法。不必担心,在所有处理结束以后,HibernateTemplate仍会进行确保正确的Session打开和关闭以及事务的自动参与。其实对于简单的一步操作,如save、delete、load、find等,HibernateTemplate可以很好地替换这种繁琐的回调方法。比如上例中的retrieveProductBy()方法又可以改写如下:

  public List retrieveProductBy(final Category category) {

    String categoryAlias = "chapter10.hibernate.domain.Category";

    return template.find("select category.products from "+

        categoryAlias+" category where category.catid=?",

        category.getCatid());

  }

撰写完毕,运行早已准备好的测试案例,效果如图10.3所示。

 

图10.3  Spring结合Hibernate案例测试通过

除了以上对HibernateTemplate的使用方法,Spring还提供了一个便利的HibernateDaoSupport超类,这和用以支持JDBC的JdbcDaoSupport类以及用以支持IBatis的SqlMapClientDaoSupport类概念是一致的,改动的方法也大相径庭。

限于篇幅,这里不再列出,在随书源码中可以找到运用这个支持类的实现,它们分别是chapter10.spring.hibernate..HibernateStockDao2和dataAccessContext-support-local.xml。

10.4.6  声明式管理Hibernate本地事务
Spring提供了一种统一的IoC方式来管理Hibernate事务(本地或者分布式事务)。从Spring接手hibernate.cfg.xml(Hibernate的基本配置文件)起,Hibernate事务便轻易交由Spring拖管了。

说明:在上一章介绍IBatis和DAO的时候,曾经针对事务和DAO的关系简单的进行了探讨。通常DAO的粒度应该都是比较细的,即它们只是一些单步的CRUD操作,所以就需要引入一个业务对象来包裹DAO,这样,就可以在业务对象的基础上,提供更粗粒度的事务划分了(比如跨越多个DAO的方法调用进行事务管理)。

为了能对DAO进行更粗粒度的事务控制,需要为其增加一个业务对象。下面给出了该业务对象的接口和实现,如代码10.25~10.26所示。

代码10.25  StockFacade.java

 

package chapter10.spring.hibernate;

 

import chapter10.hibernate.domain.Category;

 

public interface StockFacade {

  public void business1(Category category);

  public void someOtherBusiness();

}

 

代码10.26  BusinessFacadeImpl.java

 

public class BusinessFacadeImpl implements StockFacade {

  private StockDao stockDao;

  public void setStockDao(StockDao stockDao) {

    this.stockDao = stockDao;

  }

  public void business1(Category category) {

    stockDao.createCategoryCascade(category);

    stockDao.retrieveProductBy(category);

    stockDao.deleteCategoryCascade(category);

  }

  public void someOtherBusiness() {

    //other implemention

  }

}

接着给出关于事务策略的配置,其中使用了Spring针对Hibernate3给出的HibernateTransactionManager,它提供了Hibernate的本地事务管理策略,如代码10.27所示。

代码10.27  transaction-context.xml

 

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

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

 

<beans>

  <bean id="transactionManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager">①

    <property name="sessionFactory" >

      <ref bean="sessionFactory" />

    </property>

  </bean>

 

  <bean id="business"

class="chapter10.spring.hibernate.BusinessFacadeImpl">

    <property name="stockDao">

      <ref bean="stockDao"/>

    </property>

  </bean>

 

  <bean id="businessProxy"

    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <property name="transactionManager">

      <ref bean="transactionManager" />

    </property>

    <property name="target">

      <ref bean="business" />

    </property>

    <property name="transactionAttributes">

      <props>

        <!-- 运行在当前事务范围内,如果当前没有启动事务,那么创建一个新的事务-->

        <prop key="business*">PROPAGATION_REQUIRED</prop>

        <!-- 运行在当前事务范围内,如果当前没有启动事务,那么抛出异常-->

        <prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>

      </props>

    </property>

  </bean>

 

</beans>

 

代码10.28  HibernateTransactionUsageTest.java

 

package chapter10.spring.hibernate;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import chapter10.hibernate.domain.Category;

import junit.framework.TestCase;

 

public class HibernateTransactionUsageTest extends TestCase {

  private StockFacade stockBusiness;

  protected void setUp() throws Exception {

    String path = "ch10/spring/hibernate/";

    ApplicationContext ctx = new ClassPathXmlApplicationContext(

         new String[]{path+"dataAccessContext-support-local.xml",

             path+"transaction-context.xml"});

    stockBusiness = (StockFacade)ctx.getBean("businessProxy");

  }

  public void testTransctionUsage() {

    Category category = new Category("RABBIT");

    category.setName("Rabbit");

    category.setDescn("Desciption of Rabbit");

    stockBusiness.business1(category);

  }

}

10.4.7  声明式管理Hibernate分布式事务
通过Spring,还可以很方便地切换至另一种事务管理策略。比如需要提供分布式事务管理策略时,只要替换一下配置即可,如代码10.29所示。

代码10.29  appContext-jta.xml

<beans>

  <bean id="transactionManager"

class="org.springframework.transaction.jta.JtaTransactionManager">

    <property name="sessionFactory" >

      <ref bean="sessionFactory" />

    </property>

  </bean>

 

  <bean id="myDataSource1"

class="org.springframework.jndi.JndiObjectFactoryBean">

      <property name="jndiName">

          <value>java:comp/env/jdbc/myds1</value>

      </property>

  </bean>

  <bean id="myDataSource2"

class="org.springframework.jndi.JndiObjectFactoryBean">

      <property name="jndiName">

          <value>java:comp/env/jdbc/myds2</value>

      </property>

  </bean>

 

  <bean id="sessionFactory1"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="myDataSource1"/>

    <property name="configLocations">

      <value>hibernate.cfg1.xml</value>

    </property>

  </bean>

  <bean id="sessionFactory2"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="myDataSource2"/>

    <property name="configLocations">

      <value>hibernate.cfg2.xml</value>

    </property>

  </bean>

 

  <bean id="dao1"

  class="daopackage1.DaoImpl">

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

  </bean>

  <bean id="dao2"

  class="daopackage2.DaoImp2">

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

  </bean>

</beans>

 

  <bean id="business" class="businesspackage.BusinessFacadeImpl">

    <property name="dao1">

      <ref bean="dao1"/>

    </property>

    <property name="dao2">

      <ref bean="dao2"/>

    </property>

  </bean>

  <bean id="businessProxy"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <property name="transactionManager">

      <ref bean="transactionManager" />

    </property>

    <property name="target">

      <ref bean="business" />

    </property>

    <property name="transactionAttributes">

      <props>

        <prop key="business*">PROPAGATION_REQUIRED</prop>

        <prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>

      </props>

    </property>

  </bean>

</beans>

可以看到,对于横跨多个Hibernate SessionFacotry的分布式事务,只需简单地将JtaTransactionManager和LocalSessionFactoryBean的定义结合起来就可以了,其中每个DAO通过bean属性得到各自的SessionFactory引用。

说明:如果所有底层数据源都是支持事务的容器,那么只需要对一个业务对象应用JtaTransactionManager策略,该对象就可以横跨多个DAO和多个Session Factory来划分事务了。使用Spring的最大好处就是,可通过配置来声明式地管理事务,无需对应用代码作任何改动。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值