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

简介: <div class="markdown_views"><p>在上一节内容中,我们使用了编程式方法来配置事务,这样的优点是我们对每个方法的控制性很强,比如我需要用到什么事务,在什么位置如果出现异常需要回滚等,可以进行非常细粒度的配置。但在实际开发中,我们可能并不需要这样细粒度的配置。另一方面,如果我们的项目很大,service层方法很多,单独为每个方法配置事务也是一件很繁琐的

在上一节内容中,我们使用了编程式方法来配置事务,这样的优点是我们对每个方法的控制性很强,比如我需要用到什么事务,在什么位置如果出现异常需要回滚等,可以进行非常细粒度的配置。但在实际开发中,我们可能并不需要这样细粒度的配置。另一方面,如果我们的项目很大,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, UserName=yc@localhost, MySQL Connector Java] read-only
说明当前事务确实为只读模式

归纳

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

目录
相关文章
|
1月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
2月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
2月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
2月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
4月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
884 0
|
1月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
2月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
494 5
|
2月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
167 0
探索Spring Boot的@Conditional注解的上下文配置
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。