Spring中,如果选用Hibernate作为持久层框架,往往需要在beans.xml中配置好SessionFactory,然后将SessionFactory注入到对应的DAO类。当我们使用SessionFactory来进行CRUD,配合对应的异常处理,会使得真正有用的业务逻辑代码显得微不足道。而且,除了那部分业务逻辑,创建Session、开启事务、处理异常、关闭资源这一系列代码在大多数场景下都是重复的。为了解决这个问题,Spring引入Hibernate Template让我们专注于业务逻辑代码。

问题

举个简单栗子,当前我们需要将User保存到数据库中,并且在保存用户的同时,在日志表中插入一条记录。因此,我们引入Spring中针对Hibernate的声明式事务管理,并在Service层方法上添加事务。需要两个DAO分别负责UserLog,此处忽略Transaction的配置,将重心放在Hibernate Template

beans.xml中配置好ComponentDataSourceSessionFactoryTransactionManagerUserDAOImpl负责将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;
    }
}

注意到在UserDAOImplLogDAOImplsave方法中,真正和业务逻辑相关的代码只有session.save()Hibernate Template的作用就是将这部分重复的代码抽出来,作为一个模板,我们在使用Hibernate Template时只需关注具体业务。

配置Hibernate Template

配置Hibernate Template只需引入HibernateTemplate,并将SessionFactory注入到HibernateTemplate即可。此处为了说明的连续性,将DataSourceSessionFactoryHibernateTemplate的配置信息都附上。

<?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注入到UserDAOImplLogDAOImpl,代码精简了好多。

@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将所有的异常都封装成了DataAccessExceptionDataAccessException 继承了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传递进来,在执行callbackdoInHibernate方法时,将当前的Session作为参数传递过去。不要在意此处的getSession方法,纯粹是为了说明。可以看到MyHibernateTemplate 的save方法与HibernateTemplate中的save方法已经非常类似了。

总结

理解了Hibernate Template的封装之后,使用起来非常方便。感觉比较惊艳的是Spring在实现Hibernate Template时的设计思想。自己对设计模式的了解还是太少,这部分的知识还需要补充。