[Spring MVC菜鸟之旅] 通过配置文件分析Spring MVC与Hibernate的整合

https://tuchong.com/1813132/14953443/#image17736761

前言

当使用Spring开发持久层的时候,我们会面临多种选择。我们可以使用JDBC、Hibernate、MyBatis或其他任意的持久化框架。在这里简单介绍一下使用Hibernate作为持久化框架。

Spring的数据访问哲学

Spring的目标之一就是允许开发人员在开发应用程序时,能够遵循面向对象(OO)原则中的“针对接口编程”。Spring对数据访问的支持也不例外。
DAO是数据访问对象(data access object)的缩写。DAO提供了数据读取和写入到数据库中的一种方式。它们应该以接口的方式发布功能,而应用程序的其他部分就可以通过接口来进行访问。下图展现了设计数据访问层的合理方式。

如上图所示,服务对象通过接口来访问DAO。这样的好处有:
(1) 服务对象易于测试,因为它们不再与特定的数据访问实现绑定在一起。
(2) 数据访问层是以持久化技术无关的方式来进行访问。持久化方式的选择独立于DAO,只要相关的数据访问方法通过接口来进行发布。这可以实现灵活的设计并使得切换持久化框架对应用程序其他部分所带来的影响最小。如果将数据层的实现细节渗透到应用程序的其他部分中,那么整个应用程序将于数据访问层耦合在一起,从而导致僵化的设计。

配置数据源

Spring提供了在Spring上下文中配置数据源Bean的多种方式,包括:
(1) 通过JDBC驱动程序定义的数据源;
(2) 通过JNDI查找的数据源;
(3) 连接池的数据源
对于即将发布到生产环境中的应用程序,我建议使用从连接池获取连接的数据源。
我们把所有有关于Hibernate的配置都放置在resource目录下spring-db.xml的文件上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl">
<value>${jdbc.url}</value>
</property>
<property name="user">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="minPoolSize">
<value>${jdbc.pool.min}</value>
</property>
<property name="maxPoolSize">
<value>${jdbc.pool.max}</value>
</property>
<property name="initialPoolSize">
<value>${jdbc.pool.init}</value>
</property>
<property name="maxStatements">
<value>${jdbc.pool.maxStatements}</value>
</property>
<property name="maxIdleTime">
<value>${jdbc.pool.maxIdleTime}</value>
</property>
<property name="idleConnectionTestPeriod">
<value>${jdbc.pool.idleConnectionTestPeriod}</value>
</property>
<property name="acquireRetryAttempts">
<value>${jdbc.pool.acquireRetryAttempts}</value>
</property>
<property name="breakAfterAcquireFailure">
<value>${jdbc.pool.breakAfterAcquireFailure}</value>
</property>
<property name="testConnectionOnCheckout">
<value>${jdbc.pool.testConnectionOnCheckout}</value>
</property>
</bean>

在Spring中集成Hibernate

声明Hibernate的Session工厂

以前,在Spring应用程序中使用的Hibernate是通过HibernateTemplate进行的,它简化了Hibernate的繁琐工作,它的职责之一是管理Hibernate的Session。这涉及打开和关闭Session并确保每个事务使用相同的Session。HibernateTemplate的不足之处在于存在一定程度的侵入性。但我们在DAO中使用HibernateTemplate(不管直接使用还是通过HibernateDaoSupport)时,DAO类都会与SpringAPI产生耦合。

Hibernate3引入了上下文Session(Contextual session),这是Hibernate本身所提供的保证每个事务使用同一Session的方案。这种方式能够让你的DAO类不包含特定的Spring代码。我在《[Spring MVC菜鸟之旅] Spring MVC 与 Hibernate整合》中就是使用HibernateTemplate,但鉴于上下文Session是使用Hibernate的最佳实践,所以下面的介绍将只关注上下文Session。

使用Hibernate的主要接口实org.hibernate.Session。Session接口提供了基本的数据访问功能,通过Hibernate的Session接口,应用程序的DAO能够满足所有的持久化需求。

获取Hibernate Session对象的标准方式是借助Hibernate的SessionFactory接口的实现类。除了一些其他的任务,SessionFactory主要负责Hibernate Session的打开。关闭以及管理。

在Spring中我们通过SPring的某一个Hibernate Session工厂Bean来获取Hibernate的SessionFactory。我们可以在应用程序的Spring上下文中,像配置其他Bean那样来配置Hibernate Session工厂。在配置Hibernate Session工厂的Bean的时候,我们一般在持久化域对象是通过XML来进行配置的。当选在XML中定义对象与数据库之间的映射,需要在Spring中配置LocalSessionFactoryBean(若更倾向使用注解的方式来定义持久化的话,需要使用AnnotationSessionFactoryBean配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">none</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.niklaus.springmvc.pojo</value>
</list>
</property>
</bean>

使用packagesToScan属性告诉Spring扫描一个或多个包以查找域类,这些类通过注解方式来表明要使用Hibernate进行持久化。

1
2
3
4
5
<property name="packagesToScan">
<list>
<value>com.niklaus.springmvc.pojo</value>
</list>
</property>

因为我只需要扫描一个包,所以我使用了一个内部的属性编辑器将单个的String自动转换为String数组。

构建不依赖于Spring的Hibernate代码

在Spring应用上下文中配置Hibernate Session工厂Bean后,可以准备创建自己的DAO类了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Repository
public class TestDAOImpl implements TestDAO{
private SessionFactory sessionFactory;
//构造DAO
@Autowired
public TestDAOImpl(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
public void addTest(Test test) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(test);
tx.commit();
session.close();
}
public Test getTestById(long id) {
Session session = sessionFactory.openSession();
Test test = (Test) session.get(Test.class,id);
session.close();
return test;
}
public void updateTest(Test test) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.update(test);
tx.commit();
session.close();
}
}

事务管理

在软件开发领域,全有或全无的操作被称为事务(transaction)。事务允许你讲几个操作组合成一个要么全部发生要么全部不发生的工作单元。如果一切顺利,事务将会成功。但是有任何一件事情出错的话,所发生的行为将会被清除干净,就像什么事情都没有发生一样。

事务特性(ACID)

原子性(Atomic)

事务是由一个或多个活动所组成的一个工作单元。原子性确保食物中的所有操作全部发生或者全部不发生。如果所有活动都成功了,事务也就成功了。如果任意一个活动失败了,整个事务也失败并回滚。

一致性(Consistent)

一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态。现实的数据不应该被损坏。

隔离性(Isolated)

事务允许多个用户对相同的数据进行操作,每个用户的操作不会与其他用户纠缠在一起。因此,事务应该被彼此隔离,避免发生同步读写相同数据的事情(注意的是,隔离性往往涉及到锁定数据库中的行或表)。

持久性(Durable)

一旦事务完成,事务的结果应该持久化,这样就能从任何的系统崩溃中会发过来。这一般会设计将结果存储到数据库或者其他形式的持久化存储中。

使用Hibernate事务管理器

如果应用程序的持久化是通过Hibernate实现的,那么久需要使用HibernateTransactionManager。需要在Spring上下文定义中添加如下的声明:

1
2
3
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

HibernateTransactionManager将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法。类似地,如果事务失败,Transaction对象的rollback()方法将会被调用。

声明式事务

在XML中定义事务

Spring为POJO提供了声明式事务的支持,它是通过Spring AOP框架实现的。Spring提供了3种方式来声明事务式边界,包括Spring AOP和TransactionProxyFactory的代理Bean来实现声明式事务,但自从Spring 2.0,声明事务的更好方式是使用Spring的tx命名空间和@Transactional注解。
使用tx命名空间会涉及将其添加到Spring XML配置文件中:

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

要注意的是,aop命名空间也应该包括在内。这是很重要的,因为有一些声明式事务配置元素依赖于部分Spring的AOP配置元素。

1
2
3
4
5
6
7
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 定义方法的过滤规则 -->
<tx:attributes>
<!-- 所有方法都使用事务 -->
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>

当使用来声明事务时,还需要一个事务管理器。根据约定优于配置,假定事务管理器被声明为一个id为transactionManager的Bean。如果碰巧为事务管理配置器配置了一个不同的id,则需要在transactionmanager属性中明确指定事务管理器的id。

只是定义了AOP通知,用于吧事务边界通知给方法。但是这只是事务通知,而不是完整的事务性切面。我们在中没有声明哪些Bean应该被通知—我们需要一个切点来做这件事。为了完整定义事务性切面,我们必须定义一个通知器(advisor)。

1
2
3
4
5
<aop:config>
<!-- 定义一个切入点 -->
<aop:pointcut id="services" expression="execution (* com.springmvc.niklaus.service.*Service.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="services" />
</aop:config>

这里的pointcut属性使用了AspectJ切入点表达式来表明通知器适用于哪些方法。哪些方法应该真正运行在事务中以及方法的事务属性都是由这个事务通知来定义的,而事务通知是advice-ref属性来制定的,它引用了名为txAdvice的通知。

定义注解驱动的事务

除了元素,tx命名空间还提供了元素,使用时,通常只需要一行XML:

1
<tx:annotation-driven transaction-manager="transactionManager"/>

通过transaction-manager属性(默认值为transactionManager)来制定特定的事务管理器。

元素告诉Spring检索上下文中所有的Bean并查找使用@Transactional注解的Bean,而不管这个注解使用在类级别上还是方法级别上。对于每一个使用@Transactional注解的Bean,都会自动为它添加事务通知。通知的事务属性是通过@Transactional注解的参数来定义的。

小结

github地址 SpringMVCRookie

参考&引用

《spring实战(第三版)》

更新时间

发布时间 : 2016-07-03

看你的了!