ssh框架中事务的声明和两种配置事务的方法

我在做SSH项目的时候使用Spring的声明式事务代理,开发环境用的是myeclipse。


声明式事务管理:

Spring也提供了声明式事务管理。这是通过Spring AOP实现的。

Spring中进行事务管理的通常方式是利用AOP(面向切片编程)的方式,为普通java类封装事务控制,它是通过动态代理实现的,由于接口是延迟实例化的,spring在这段时间内通过拦截器,加载事务切片。原理就是这样,具体细节请参考jdk中有关动态代理的文档。本文主要讲解如何在spring中进行事务控制。
动态代理的一个重要特征是,它是针对接口的,所以我们的dao要通过动态代理来让spring接管事务,就必须在dao前面抽象出一个接口,当然如果没有这样的接口,那么spring会使用CGLIB来解决问题,但这不是spring推荐的方式,所以不做讨论.

大多数Spring用户选择声明式事务管理。这是最少影响应用代码的选择, 因而这是和非侵入性的轻量级容器的观念是一致的。

从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。 它们的基本方法是相似的:都可以指定事务管理到单独的方法;如果需要可以在事务上 下文调用setRollbackOnly()方法。不同之处如下:

  • 不象EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。 只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作

  • Spring可以使声明式事务管理应用到普通Java对象,不仅仅是特殊的类,如EJB

  • Spring提供声明式回滚规则:EJB没有对应的特性, 我们将在下面讨论这个特性。回滚可以声明式控制,不仅仅是编程式的

  • Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务 回滚中插入定制的行为。你也可以增加任意的通知,就象事务通知一样。使用 EJB CMT,除了使用setRollbackOnly(),你没有办法能 够影响容器的事务管理

  • Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如 果你需要这些特性,我们推荐你使用EJB。然而,不要轻易使用这些特性。通常我 们并不希望事务跨越远程调用

回滚规则的概念是很重要的:它们使得我们可以指定哪些异常应该发起自 动回滚。我们在配置文件中,而不是Java代码中,以声明的方式指定。因此,虽然我们仍 然可以编程调用TransactionStatus对象的 setRollbackOnly()方法来回滚当前事务,多数时候我们可以 指定规则,如MyApplicationException应该导致回滚。 这有显著的优点,业务对象不需要依赖事务基础设施。例如,它们通常不需要引 入任何Spring API,事务或其他任何东西。

EJB的默认行为是遇到系统异常(通常是运行时异常), EJB容器自动回滚事务。EJB CMT遇到应用程序异常 (除了java.rmi.RemoteException外的checked异常)时不 会自动回滚事务。虽然Spring声明式事务管理沿用EJB的约定(遇到unchecked 异常自动回滚事务),但是这是可以定制的。

按照我们的测试,Spring声明式事务管理的性能要胜过EJB CMT。

通常通过TransactionProxyFactoryBean设置Spring事务代理。我们需 要一个目标对象包装在事务代理中。这个目标对象一般是一个普通Java对象的bean。当我 们定义TransactionProxyFactoryBean时,必须提供一个相关的 PlatformTransactionManager的引用和事务属性。 事务属性含有上面描述的事务定义。

<bean id="petStore" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref bean="petStoreTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

事务代理会实现目标对象的接口:这里是id为petStoreTarget的bean。(使用 CGLIB也可以实现具体类的代理。只要设置proxyTargetClass属性为true就可以。 如果目标对象没有实现任何接口,这将自动设置该属性为true。通常,我们希望面向接口而不是 类编程。)使用proxyInterfaces属性来限定事务代理来代 理指定接口也是可以的(一般来说是个好想法)。也可以通过从 org.springframework.aop.framework.ProxyConfig继承或所有AOP代理工厂共享 的属性来定制TransactionProxyFactoryBean的行为。

这里的transactionAttributes属性定义在org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource 中的属性格式来设置。这个包括通配符的方法名称映射是很直观的。注意 insert*的映射的值包括回滚规则。添加的-MyCheckedException指定如果方法抛出MyCheckedException或它的子类,事务将 会自动回滚。可以用逗号分隔定义多个回滚规则。-前缀强制回滚,+前缀指定提交(这允许即使抛出unchecked异常时也可以提交事务,当然你自己要明白自己 在做什么)。

TransactionProxyFactoryBean允许你通过 “preInterceptors”和“postInterceptors”属性设置“前”或“后”通知来提供额外的 拦截行为。可以设置任意数量的“前”和“后”通知,它们的类型可以是 Advisor(可以包含一个切入点),MethodInterceptor或被当前Spring配置支持的通知类型 (例如ThrowAdviceAfterReturningtAdviceBeforeAdvice, 这些都是默认支持的)。这些通知必须支持实例共享模式。如果你需要高级AOP特 性来使用事务,如有状态的maxin,那最好使用通用的org.springframework.aop.framework.ProxyFactoryBean, 而不是TransactionProxyFactoryBean实用代理创建者。

也可以设置自动代理:配置AOP框架,不需要单独的代理定义类就可以生成类的 代理。


1、使用application.xml配置的方法(每个Bean都有一个代理


我Spring  配置文件是这样的:

XML/HTML code
?
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
42
43
44
45
46
47
48
<? xml  version = "1.0"  encoding = "UTF-8" ?>
< beans
     xmlns = "http://www.springframework.org/schema/beans"
     xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
     xmlns:aop = "http://www.springframework.org/schema/aop" 
     xmlns:tx = "http://www.springframework.org/schema/tx" 
     
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd 
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd 
     ">
 
 
     < bean  id = "sessionFactory"
        class = "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
        < property  name = "configLocation"
            value = "classpath:hibernate.cfg.xml" >
        </ property >
     </ bean >
 
     
     <!-- 配置事务管理器 -->
     < bean  id = "transactionManager"  class = "org.springframework.orm.hibernate3.HibernateTransactionManager" >
        < property  name = "sessionFactory" >
            < ref  local = "sessionFactory" />
        </ property >
     </ bean >
     
     <!-- 配置事务的传播特性 -->
     < tx:advice  id = "txAdvice"  transaction-manager = "transactionManager"  >
        < tx:attributes >
            <!-- 在开发的时候可以这样定义,但部署的时候一定要详细定义 -->
            < tx:method  name = "*"  propagation = "REQUIRED" />
        <!--  
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="del*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="*" read-only="true"/>
        -->
        </ tx:attributes >
     </ tx:advice >
     
     <!-- 配置哪些类哪些方法使用事务 -->
     < aop:config >
        < aop:pointcut  id = "txAdvicePointcut"  expression = "execution(* com.nuist.service.*.*(..))" />
        < aop:advisor  advice-ref = "txAdvice"  pointcut-ref = "txAdvicePointcut" />
     </ aop:config >
</ beans >


<tx:method name="add*" propagation="REQUIRED"/>里面的name对应是是方法的名称,*代表所有,也就是说以add字段开头的方法都会通过aop动态来
创建一个事务


 <aop:pointcut id="txAdvicePointcut" expression="execution(* com.nuist.service.*.*(..))"/>
 expression里面的值对应的是你要自动装配要扫面的是那一个包下面的文件,com.nuist.service.表示com.nuist.service下的
所有文件里的方法创建事务。

第2种方式:全注解

View Row Code
1 ......
2 <context:annotation-config />
3 <context:component-scan base-package="com.bluesky" />
4
5 <tx:annotation-driven transaction-manager="transactionManager" />
6
7 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
8 <property name="configLocation" value="classpath:hibernate.cfg.xml" />
9 <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
10 </bean>
11
12 <!-- 定义事务管理器(声明式的事务) -->
13 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
14 <property name="sessionFactory" ref="sessionFactory" />
15 </bean>

此时在DAO上需加上@Transactional注解(同时在继承过来父类的baseDao也要注解),如下:

View Row Code
1 @Transactional
2 @Component("userDao")
3 publicclass UserDaoImpl extends HibernateDaoSupport implements UserDao {
4 publicList<User>listUsers() {
5 returnthis.getSession().createQuery("from User").list();
6 }
7 ......
8 }





二、事务的传播属性(Propagation)

Propagation :key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

  • PROPAGATION_REQUIRED--加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
  • PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

1: PROPAGATION_REQUIRED

加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务。

比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。

2: PROPAGATION_SUPPORTS

如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行。

3: PROPAGATION_MANDATORY

必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。

4: PROPAGATION_REQUIRES_NEW

这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

5: PROPAGATION_NOT_SUPPORTED

当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

6: PROPAGATION_NEVER

不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。

7: PROPAGATION_NESTED

理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

而Nested事务的好处是他有一个savepoint。

View Row Code
1 ServiceA {
2 //事务属性配置为 PROPAGATION_REQUIRED
3 voidmethodA() {
4 try {
5 //savepoint
6 ServiceB.methodB();//PROPAGATION_NESTED 级别
7 } catch (SomeException) {
8 // 执行其他业务, 如 ServiceC.methodC();
9 }
10 }
11 }

也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如:

ServiceC.methodC,继续执行,来尝试完成自己的事务。

但是这个事务并没有在EJB标准中定义。





注意问题:如果是用了spring整合之后的动态代理来创建事务的话,一定要修改hibernate上面的配置文件,否则会报错

hibernate和spring整合之后就不用自己管理事务了,还有这一段配置可能hibernate会继续自己的管理事务 
 <property name="current_session_context_class">
thread
</property> 




<!-- web.xml解决的是延迟加载的问题

<filter>   
        <filter-name>OpenSessionInViewFilter</filter-name>   
        <filter-class>   
                org.springframework.orm.hibernate4.support.OpenSessionInViewFilter 
        </filter-class>   
        指定在spring配置的sessionFactory  
        <init-param>   
            <param-name>sessionFactoryBeanName</param-name>   
            <param-value>sessionFactory</param-value>   
        </init-param>   
        singleSession默认为true,若设为false则等于没用OpenSessionInView     
        <init-param>   
                <param-name>singleSession</param-name>   
                <param-value>true</param-value>   
        </init-param>   
     </filter> -->










你可能感兴趣的:(web框架整合)