Spring Hibernate Template解析
在Spring中,如果选用Hibernate作为持久层框架,往往需要在beans.xml中配置好SessionFactory,然后将SessionFactory注入到对应的DAO类。当我们使用SessionFactory来进行CRUD,配合对应的异常处理,会使得真正有用的业务逻辑代码显得微不足道。而且,除了那部分业务逻辑,创建Session、开启事务、处理异常、关闭资源这一系列代码在大多数场景下都是重复的。为了解决这个问题,Spring引入Hibernate Template让我们专注于业务逻辑代码。
问题
举个简单栗子,当前我们需要将User保存到数据库中,并且在保存用户的同时,在日志表中插入一条记录。因此,我们引入Spring中针对Hibernate的声明式事务管理,并在Service层方法上添加事务。需要两个DAO分别负责User和Log,此处忽略Transaction的配置,将重心放在Hibernate Template。
在beans.xml中配置好Component,DataSource,SessionFactory和TransactionManager,UserDAOImpl负责将User插入到数据库中。
@Component(value = "userDAO")
public class UserDAOImpl implements UserDAO {
private SessionFactory sessionFactory;
public void save(User user) {
try {
Session session = sessionFactory.getCurrentSession();
session.save(user);
} catch (HibernateException e) {
e.printStackTrace();
}
}
public SessionFactory getSessionFactory() {
return sessionFactory;
}
@Resource(name = "mySessionFactory")
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
LogDAOImpl将一条日志插入到数据库。
@Component(value = "logDAO")
public class LogDAOImpl implements LogDAO {
private SessionFactory sessionFactory;
public void save(Log log) {
try {
Session session = sessionFactory.getCurrentSession();
session.save(log);
} catch (HibernateException e) {
e.printStackTrace();
}
}
public SessionFactory getSessionFactory() {
return sessionFactory;
}
@Resource(name = "mySessionFactory")
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
注意到在UserDAOImpl 和LogDAOImpl 的save方法中,真正和业务逻辑相关的代码只有session.save()。Hibernate Template的作用就是将这部分重复的代码抽出来,作为一个模板,我们在使用Hibernate Template时只需关注具体业务。
配置Hibernate Template
配置Hibernate Template只需引入HibernateTemplate,并将SessionFactory注入到HibernateTemplate即可。此处为了说明的连续性,将DataSource,SessionFactory和HibernateTemplate的配置信息都附上。
<?xml version="1.0" encoding="UTF-8"?>
<!--===============================Spring整合Hibernate=============================-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:jdbc.properties</value>
</property>
</bean>
<!--配置数据源-->
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--设置Hibernate的SessionFactory-->
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="myDataSource"></property>
<!--设置Hibernate的配置信息-->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<!--告诉容器去扫描哪些包里的实体类-->
<property name="packagesToScan">
<list>
<value>entity</value>
</list>
</property>
</bean>
<!--========================设置HibernateTemplate=======================-->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="mySessionFactory"></property>
</bean>
</beans>
将HibernateTemplate注入到UserDAOImpl和LogDAOImpl,代码精简了好多。
@Component(value = "userDAO")
public class UserDAOImpl implements UserDAO {
private HibernateTemplate hibernateTemplate;
public void save(User user) {
hibernateTemplate.save(user);
}
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
@Resource(name = "hibernateTemplate")
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
}
@Component(value = "logDAO")
public class LogDAOImpl implements LogDAO {
private HibernateTemplate hibernateTemplate;
public void save(Log log) {
hibernateTemplate.save(log);
}
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
@Resource(name = "hibernateTemplate")
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
}
浅析Hibernate Template
那么Hibernate Template内部到底是怎么实现的?查看HibernateTemplate的源码,其中的save方法如下。
public Serializable save(final Object entity) throws DataAccessException {
return (Serializable)this.executeWithNativeSession(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
HibernateTemplate.this.checkWriteOperationAllowed(session);
return session.save(entity);
}
});
}
save方法抛出了DataAccessException,实际上,Spring将所有的异常都封装成了DataAccessException 。DataAccessException 继承了NestedRuntimeException,而NestedRuntimeException又继承了RuntimeException。所以在HibernateTemplate中抛出的任何异常都会导致事务的回滚。
在save方法中,调用了executeWithNativeSession方法,方法的参数是一个匿名内部类,该匿名内部类实现了HibernateCallback接口。executeWithNativeSession方法正是调用了匿名内部类的doInHibernate方法,并向其传递了当前的Session对象,然后执行了save操作。
其实这是模板方法这一设计模式的使用,为了更好的理解,我来模拟整个过程。创建接口MyHibernateCallback和类MyHibernateTemplate。
public interface MyHibernateCallback {
void doInHibernate(Session session);
}
public class MyHibernateTemplate {
public void executeWithNativeSession(MyHibernateCallback callback) {
Session session = null;
try {
session = getSession();
session.beginTransaction();
callback.doInHibernate(session);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
} finally {
if (session != null) {
session.close();
}
}
}
public Session getSession() {
return null;
}
public void save(final Object entity) {
this.executeWithNativeSession(new MyHibernateCallback() {
public void doInHibernate(Session session) {
session.save(entity);
}
});
}
}
将模板代码定义在executeWithNativeSession方法中,具体的业务逻辑通过参数callback传递进来,在执行callback的doInHibernate方法时,将当前的Session作为参数传递过去。不要在意此处的getSession方法,纯粹是为了说明。可以看到MyHibernateTemplate 的save方法与HibernateTemplate中的save方法已经非常类似了。
总结
理解了Hibernate Template的封装之后,使用起来非常方便。感觉比较惊艳的是Spring在实现Hibernate Template时的设计思想。自己对设计模式的了解还是太少,这部分的知识还需要补充。