Spring之旅 - 记录学习 Spring 框架的过程和经验(十一)基于XML方式、注解的声明式事务控制、Spring整合Web环境

前言

在接下来的内容中,我们将首先介绍 Spring 的事务管理基础知识,包括事务的概念和 Spring 如何提供支持。然后,我们将分别展示如何使用 XML 和注解两种方式进行声明式事务控制,并讨论它们各自的优缺点。最后,我们还会探讨如何将 Spring 的事务管理功能有效集成到 Web 应用程序中,以实现更高效的数据处理和用户体验。


XML方式声明事务通知

结合上面我们学习的AOP的技术,很容易就可以想到,可以使用AOP对Service的方法进行事务的增强。

  • 目标类:自定义的AccountServicelmpl,内部的方法是切点
  • 通知类:Spring提供的,通知方法已经定义好,只需要配置即可

我们分析:

  • 通知类是Spring提供的,需要导入Spring事务的相关的坐标;
  • 配置目标类AccountServicelmpl;
  • 使用advisor标签配置切面。
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:context="http://www.springframework.org/schema/context"
       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.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd

       ">
    <!--组件扫描-->
    <context:component-scan base-package="com.itheima"/>

    <!--加载properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据源信息-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到spring容器-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--MapperScannerConfigurer,作用扫描指定的包,产生Mapper对象存储到Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.itheima.mapper"></property>
    </bean>
    <!--配置平台事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>

    </bean>

    <!--配置Spring提供好的Advice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--事务增强的AOP-->
    <aop:config>
        <!--配置切点表达式-->
        <aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--配置织入 通知advice-ref引入Spring提供好的-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"></aop:advisor>
    </aop:config>

</beans>

事务管理器配置

  • 定义了一个事务管理器,类型为DataSourceTransactionManager,它是Spring提供的用于管理JDBC事务的实现。
  • dataSource属性引用了一个名为dataSource的Bean,这个Bean通常用于配置数据库连接。

事务通知配置

  • 定义了一个事务通知(Advice),其ID为txAdvice,并将其与之前定义的transactionManager关联。
  • tx:attributes中定义了一个通配符*,表示对所有方法调用应用事务管理。这意味着任何匹配切点的服务方法都会自动开启事务。

AOP配置

  • aop:config标签用于配置AOP相关的内容。
  • aop:pointcut定义了一个切点,ID为txPointcut,表达式execution(* com.itheima.service.impl..(...))表示匹配com.itheima.service.impl包下所有类的所有方法。
  • aop:advisor将txAdvice与txPointcut关联起来,表示当某个方法执行时,会执行与之关联的事务管理逻辑。

初始数据

java 复制代码
	@Override
    public void transforMoney(String outAccount, String inAccount, double money) {
        accountMapper.decrMoney(outAccount,money);
        int i = 1 / 0;
        accountMapper.incrMoney(inAccount,money);
    }

在转账操作中加入错误,运行后看数据是否会发生修改

测试代码:

java 复制代码
	public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService =(AccountService) applicationContext.getBean("accountService");
        accountService.transforMoney("Business","Emergency",500);

    }

如若不进行事务管理,会产生错误操作即Business的money减少,而Emergency的money不变。

运行后数据为:

加入事务管理后运行后数据为

此时数据均未发生修改,说明此时事务控制成功。

如若不进行事务管理,会产生错误操作即Business的money增加,而Emergency的money不变。

isolation属性:指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READCOMMITTED和 REPEATABLE READ

propagation属性:设置事务的传播行为,主要解决是A方法调用B方法时,事务的传播方式问题的,例如:使用单方的事务,还是A和B都使用自己的事务等。事务的传播行为有如下七种属性值可配置

xml 复制代码
	<!--配置Spring提供好的Advice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>

            <!--配置不同的方法的事务属性
            name:方法名 *代表通配符 添加操作addUser、addAccount、addOrders=>add*
            isolation:事务的隔离级别,解决事务并发问题
            timeout:超时时间 默认-1 单位是s
            read-only:是否只读,查询操作才设置为只读状态
            propagation:事务的传播行为,解决业务方法调用业务方法(事务嵌套问题)
            -->
            <tx:method name="*" isolation="READ_COMMITTED" timeout="3" propagation="REQUIRED"  read-only="false"/>
        </tx:attributes>
    </tx:advice>

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

注解就是对xml的替代

java 复制代码
@Service("accountService")
public class AccoutServiceImpl implements AccountService {
	@Autowired
	private AccountMapper accountMapper;
	//<tx:method name-"*" isolation-"REPEATABLE READ" propagation-"REQUIRED "/>
	@Transactional(isolation = Isolation.REPEATABLE_READ,propagation =Propagation.REQUIRED, readOnly = false,timeout = 5)
	public void transferMoney(String decrAccountName, String incrAccountName, int money) {
		accountMapper.decrMoney (decrAccountName,money); 
		//转出钱inti=1/0;
		//模拟某些逻辑产生的异常
  		accountMapper.incrMoney(incrAccountName,money);
  		//转入钱
   }
}

同样,使用的事务的注解,平台事务管理器仍然需要配置,还需要进行事务注解开关的开启

xml 复制代码
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourcerransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的注解驱动-->
<tx:annotation-driven transaction-manager=" transactionManager"/>

把xml文件全用注解代替后的配置类SpringConfig.java

java 复制代码
package com.itheima.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
@MapperScan("com.itheima.mapper")
@EnableTransactionManagement //事务的自动代理
public class SpringConfig {

    @Bean
    public DataSource dataSource(@Value("${jdbc.driver}") String driver,
                                 @Value("${jdbc.url}") String url, @Value("${jdbc.username}") String username,@Value("${jdbc.password}") String password) {
        DruidDataSource  dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

}

其中,name属性名称指定哪个方法要进行哪些事务的属性配置,此处需要区分的是切点表达式指定的方法与此处指定的方法的区别?切点表达式,是过滤哪些方法可以进行事务增强;事务属性信息的name,是指定哪个方法要进行哪些事务属性的配置

Spring整合Web环境

Javaweb三大组件及环境特点

在Java语言范畴内,web层框架都是基于Javaweb基础组件完成的,所以有必要复习一下Javaweb组件的特点

Spring整合web环境的思路及实现

在进行Java开发时要遵循三层架构+MVC,Spring操作最核心的就是Spring容器,web层需要注入Service,service层需要注入Dao(Mapper),web层使用Servlet技术充当的话,需要在Servlet中获得Spring容器

java 复制代码
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationcontext(Applicationcontextconfig.class);
AccountService accountService = (AccountService)applicationContext.getBean("accountservice");
accountService.transferMoney("tom","lucy",100);

web层代码如果都去编写创建AnnotationConfigApplicationContext的代码,那么配置类重复被加载了,Spring容器也重复被创建了,不能每次想从容器中获得一个Bean都得先创建一次容器,这样肯定是不允许。所以,我们现在的诉求很简单,如下:

  • ApplicationContext创建一次,配置类加载一次;
  • 最好web服务器启动时,就执行第1步操作,后续直接从容器中获取Bean使用即可;
  • ApplicationContext的引用需要在web层任何位置都可以获取到。
java 复制代码
package com.itheima.web;


import com.itheima.service.AccountService;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //web层调用service层,获得AccountService,accountService存在applicationContext中
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml");
        AccountService bean = applicationContext.getBean(AccountService.class);
        bean.transforMoney("Business","Emergency",500);
    }
}

@WebServlet(urlPatterns = "/accountServlet") 表示这个 Servlet 的 URL 映射为 /accountServlet。当用户在浏览器中访问 http://< server>:< port>/spring_trans_war/accountServlet 时,Servlet 容器会调用 AccountServlet 类中的 doGet方法

此前数据库数据为:

访问http://< server>:< port>/spring_trans_war/accountServlet 后数据库的数据为:

针对以上诉求我们给出解决思路,如下:

  • 在ServletContextListener的contextlnitialized方法中执行ApplicationContext的创建。或在Servlet的init方法中执行ApplicationContext的创建,并给Servlet的load-on-startup属性一个数字值,确保服务器启动Servlet就创建;
  • 将创建好的ApplicationContext存储到ServletContext域中,这样整个web层任何位置就都可以获取到了

总结

希望通过本文的分享,能够帮助开发者更好地理解和使用 Spring 的事务管理功能,从而在实际项目中实现更高效、更可靠的业务处理。

相关推荐
半夏知半秋14 小时前
rust学习-闭包
开发语言·笔记·后端·学习·rust
sheji341614 小时前
【开题答辩全过程】以 基于微信小程序的在线学习系统为例,包含答辩的问题和答案
学习·微信小程序·小程序
IT=>小脑虎14 小时前
Go语言零基础小白学习知识点【基础版详解】
开发语言·后端·学习·golang
hkNaruto15 小时前
【AI】AI学习笔记:A2A(智能体协作)入门指南:从概念到实践
人工智能·笔记·学习
暖暖木头15 小时前
playwright学习笔记
笔记·学习
CCPC不拿奖不改名15 小时前
python基础:python语言中的控制结构+面试习题
开发语言·python·学习
短剑重铸之日16 小时前
《SpringBoot4.0初识》第五篇:实战代码
java·后端·spring·springboot4.0
heartbeat..16 小时前
Spring MVC 全面详解(Java 主流 Web 开发框架)
java·网络·spring·mvc·web
CUIYD_198916 小时前
Freemarker 无法转译 & 字符
java·开发语言·spring