spring学习笔记(22)声明式事务配置,readOnly无效写无异常

在上一节内容中,我们使用了编程式方法来配置事务,这样的优点是我们对每个方法的控制性很强,比如我需要用到什么事务,在什么位置如果出现异常需要回滚等,可以进行非常细粒度的配置。但在实际开发中,我们可能并不需要这样细粒度的配置。另一方面,如果我们的项目很大,service层方法很多,单独为每个方法配置事务也是一件很繁琐的事情。而且也可能会造成大量重复代码的冗杂堆积。面对这些缺点,我们首要想到的就是我们spring中的AOP了。spring声明式事务的实现恰建立在AOP之上。

在这一篇文章中,我们介绍spring的声明式事务配置。

实例分析

声明式事务配置原理相当于使用了环绕增强,拦截目标方法,在其调用前织入我们的事务,然后在调用结束根据执行情况提交或回滚事务。通过横切的逻辑,能够让我们的service层更专注于自身业务逻辑的处理而免去繁琐的事务配置。

配置声明式事务的核心在于配置我们的TransactionProxyFactoryBean和BeanNameAutoProxyCreator。先看下面一个实例配置

事务核心类配置

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager" /><!-- 指定一个事务管理器-->
    <property name="transactionAttributes"><!-- 配置事务属性 `-->
        <props>
            <prop key="add*" >PROPAGATION_REQUIRED,-Exception</prop>
            <prop key="update*">PROPAGATION_REQUIRED,+Exception</prop>
                <prop key="delete*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames"><!-- 配置需要代理的Bean-->
        <list>
            <value>myBaseServiceImpl</value>
        </list>
    </property>
    <property name="interceptorNames"><!-- 声明拦截器-->
        <list>
            <value>transactionInterceptor</value>
        </list>
    </property>
</bean>
<!-- 测试用到的相关依赖-->
<bean id="myBaseDao" class="com.yc.dao.MyBaseDaoImpl">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="myBaseServiceImpl" class="com.yc.service.MyBaseServiceImpl">
    <property name="myBaseDao" ref="myBaseDao" />
</bean>

属性详细分析

在实例中我们通过配置拦截器和代理生成器。在配置TransactionInterceptor事务属性时,key对应于方法名,我们以add*来匹配目标类中所有以add开头的方法,在针对目标对象类的方法进行拦截配置事务时,我们根据属性的定义顺序拦截,如果它被key="add*"所在事务属性拦截,即使后面有key="*"可以匹配任意方法,也不会再次被拦截。关于标签内的事务属性格式如下:

传播行为 [,隔离级别] [,只读属性] [,超时属性] [,-Exception] [,+Exception]

其中除了传播行为外,其他都是可选的。每个属性说明可见下表

属性 说明
传播行为 取值必须以“PROPAGATION_”开头,具体包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值。
隔离级别 取值必须以“ISOLATION_”开头,具体包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
只读属性 如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。
超时属性 取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
+Exception 即使事务中抛出了这些类型的异常,事务仍然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。可同时指定多个,如+Exception1,+Exception2
-Exception 当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。可同时指定多个,如-Exception1,-Exception2

从配置文件中可以看出,我们可以配置多个拦截器和多个Bean来适配不同的事务。这种声明式事务使用起来还是很方便的。

service层配置

使用声明式事务后,相对于上篇文章例子,我们的service层需改写成:

public class MyBaseServiceImpl implements MyBaseService{
    private MyBaseDao myBaseDao;

    @Override
    public void queryUpdateUser(final Integer id,final String newName) {
            User user = myBaseDao.queryUnique(User.class, id);
            System.out.println(user);
            user.setName(newName);
            myBaseDao.update(user);
            System.out.println(user);
    }

    public void setMyBaseDao(MyBaseDao myBaseDao) {
        this.myBaseDao = myBaseDao;
    }

}

可见,我们去除了事务模板的侵入式注入,同时还去除了事务(在每一个方法中的)侵入式配置。当然,编程式事务的好处是能将事务配置细粒度到每个方法当中。。当我们大部分方法的事务还是一致的,我们可以使用声明式事务,针对那些需要独立配置的,我们可以将其排除出声明式事务,然后使用编程式事务或后面我们会提到的注解式事务单独配置。

测试结果和分析

下面,运行我们相同的测试方法:

public class Test1 {
    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-datasource.xml");
        MyBaseServiceImpl myBaseService= (MyBaseServiceImpl) ac.getBean("myBaseServiceImpl");
        myBaseService.queryUpdateUser(1, "newName2");
    }
}

运行测试方法,会发现报错:

java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to com.yc.service.MyBaseServiceImpl

意思是我们的代理类无法转换成我们自定义的Service实现类。究其原因,是因为我们的BeanNameAutoProxyCreator没有默认使用CGLib代理,这样我们的代理类是利用JDK动态代理基于接口创建的,而非基于类创建,我们有以下两种解决方法:

1. 将代理类转换成MyBaseServiceImpl所实现的接口MyBaseService而非MyBaseServiceImpl:

MyBaseService myBaseService= (MyBaseService) ac.getBean("myBaseServiceImpl");

2. 在BeanNameAutoProxyCreator配置下添加:

<property name="proxyTargetClass" value="true"/>,即

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="proxyTargetClass" value="true"/>
    <property name="beanNames">
        <list>
            <value>myBaseServiceImpl</value>
        </list>
    </property>
    <property name="interceptorNames">
        <list>
            <value>transactionInterceptor</value>
        </list>
    </property>
</bean>

然后,再运行测试程序,我们会得到正确的结果,部分打印信息如下所示:

DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtaining JDBC connection

DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtained JDBC connection

DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin

DEBUG: org.hibernate.loader.Loader - Done entity load

User [id=1, name=newName]

User [id=1, name=newName2]

DEBUG: org.springframework.orm.hibernate4.HibernateTransactionManager - Initiating transaction commit

DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - committing

这和我们使用编程式事务的结果基本是一致的。

拓展测试

现在,在我们的拦截器中稍微修改一行:

<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

我们将其设置为只读模式,这时候,调用我们的测试方法,queryUpdateUser(1,”newName3”)(因为前面测试已将name修改成newName2,为了显示不同的结果,这里射程newName3做参数)。显然,前面的add*,update*,delete*都不能匹配。这时候必定启动key="*"所属事务。运行方法,我们会发现结果:

User [id=1, name=newName2]

User [id=1, name=newName3]

这似乎和我们没设置readOnly应有的结果一致,但我们再次运行,程序没有抛出异常,而且会发现结果仍是:

User [id=1, name=newName2]

User [id=1, name=newName3]

说明我们的修改实际上并没有生效!这时在看DEBUG信息,发现在:

DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin信息上面多了一行:

DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Setting JDBC Connection [jdbc:mysql://localhost:3306/yc, [email protected], MySQL Connector Java] read-only

说明当前事务确实为只读模式

归纳

这里单独拿出readOnly来分析,主要是针对实际开发中可能遇到的麻烦。设想我们哪天只读属性配置错了。但我们没发现,而当我们试图进行相应的写数据操作时,发现程序并没有出现异常,但数据无论怎么都写不进去。这个时候就要好好看看我们的只读属性有没有跑到它不该到的地方去了!

时间: 2024-10-29 19:12:02

spring学习笔记(22)声明式事务配置,readOnly无效写无异常的相关文章

Spring学习七、声明式事务

十三.声明式事务 回顾事务 把一组事务当成一个业务来做要么都成功,要么都失败 涉及到数据一致性的问题,不能马虎 确保完整性和一致性 事务的ACID原则 原子性 一致性 隔离性 多个业务操作同一个资源,防止事务损坏 持久性 事务一旦提交,无论系统发生什么,结果都不受影响,被持久化写到存储器中 Spring中的事务管理 声明式事务:AOP 编程事务:需要在代码中 <!-- 配置声明式事务--> <bean id="transactionManager" class=&qu

spring声明式事务配置详解

spring声明式事务配置详解 君子不器 2013年06月16日 编程世界 5273次阅读 查看评论 理解Spring的声明式事务管理实现 本节的目的是消除与使用声明式事务管理有关的神秘性.简单点儿总是好的,这份参考文档只是告诉你给你的类加上@Transactional注解,在配置文件中添加('<tx:annotation-driven/>')行,然后期望你理解整个过程是怎么工作的.此节讲述Spring的声明式事务管理内部的工作机制,以帮助你在面对事务相关的问题时不至于误入迷途,回朔到上游平静

Spring声明式事务配置的两种策略SpringAop和Bean后处理器的代理BeanNameAutoProxyCreator

Spring的事务配置有两种:1编程式事务管理配置:2声明式事务管理配置.下面介绍两种声明式事务的配置,声明式事务相比于编程式事务代码耦合更低,无序书写任何事务管理的先关代码.两种声明式事务配置策略分别是:SpringAop事务管理和Bean后处理器的代理BeanNameAutoProxyCreator管理事务. 1.SpringAop事务管理配置 1.1.配置数据源: <bean id="pycDataSource" class="com.mchange.v2.c3p

AspectJ声明式事务配置

Spring声明式事务配置,实现模拟转账过程 (AspectJ) 编程式事务要修改service层的代码,很少用,相比之下,AspectJ增强事务管理器,在xml中配置切面切点(AOP),而service代码不用做修改. 1.新建数据表 DROP TABLE IF EXISTS `account`; CREATE TABLE `account` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `name` varchar(20) NOT NULL,   `

spring基于xml的声明式事务控制配置步骤

<?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/

spring基于注解的声明式事务控制配置

配置文件: <?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/sc

spring学习笔记(23)基于tx/aop配置切面增强事务

在上一篇文章中,我们使用了声明式事务来配置事务,使事务配置从service逻辑处理中解耦出来.但它还存在一些缺点: 1. 我们只针对方法名的特定进行拦截,但无法利用方法签名的其它信息定位,如修饰符.返回值.方法入参.异常类型等.如果我们需要为同名不同参的同载方法配置不同事务就会出问题了. 2. 事务属性的配置串虽然能包含较多信息,但配置较易出错. 针对这些问题,我们可以基于Schema,引入tx和aop的命名空间来改进我们的配置: 引入命名空间 <beans xmlns="http://w

spring基于xml的声明式事务控制

配置文件bean.xml <?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

Spring(十五)之声明式事务

声明式事务管理方法允许你在配置的帮助下而不是源代码硬编程来管理事务.这意味着你可以将事务管理从事务代码中隔离出来.你可以只使用注释或基于配置的 XML 来管理事务. bean 配置会指定事务型方法.下面是与声明式事务相关的步骤: 我们使用标签,它创建一个事务处理的建议,同时,我们定义一个匹配所有方法的切入点,我们希望这些方法是事务型的并且会引用事务型的建议. 如果在事务型配置中包含了一个方法的名称,那么创建的建议在调用方法之前就会在事务中开始进行. 目标方法会在 try / catch 块中执行