Spring中的事务及其配置(分别基于XML、注解、半XML半注解)

本文详细介绍了Spring中的事务管理,包括事务的概念、四大特性、并发问题及其解决方案——事务隔离级别。此外,还探讨了事务的传播行为,如REQUIRED、SUPPORTS等。接着,讲解了Spring中如何通过XML配置和注解进行事务管理,包括PlatformTransactionManager接口、事务通知、事务属性等。最后,展示了基于XML和注解的事务配置实例,以及测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Spring中的事务

1. 事务的回顾

1.1 事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从而确保了数据的准确与安全。

例如:A——B转帐,对应于如下两条sql语句:

  /*转出账户减钱*/
  update account set money=money-100 where name=‘a’;
  /**转入账户加钱*/
  update account set money=money+100 where name=‘b’;

这两条语句的执行,要么全部成功,要么全部不成功。

1.2 事务的四大特性

  • 原子性:一个事务内的操作,要么都成功,要么都失败。很经典的例子:转账,汇款和收款要成功都成功,要失败都失败。
  • 一致性:指的是数据的一致性,和原子性其实是一件事情,只不过描述的角度不一样,原子性是从事务的操作的角度,一致性是从数据的角度来描述的,比如转账之前(1000,1000),如果转账100,那么数据状态应该是(900、1100),不应该出现中间状态(900,1000)或者(1000,1100)
  • 隔离性:事务并发的时候,比如事务1做的动作给员工涨工资2000块,但是此时事务还没有提交,事务2去查询工资发现工资多了2000块,这就是脏读。解决方法就是建立事务之间的隔离机制。
  • 持久性:事务一旦提交,事务提交,变化即生效。即使数据库服务器宕机,那么恢复之后,数据也应该是事务提交之后的状态,不应该回滚到以前了。

1.3 关于事务并发问题

  • 脏读:事务A读到了事务B未提交的数据,例如
    • 财务人员今天心情不好,状态不好,误操作发起事务1给员工张三本月涨了1w块钱工资,但是还没有提交事务;此时,张三发起事务2,查询当月工资,发现多了1W块钱,涨工资了,财务人员发现不对劲,把操作撤回,把涨工资的事务1给回滚了。
  • 幻读:是指当事务不是独立执行时发生的一种现象,幻读出现在增加insert和删除delete的时候,例如:
    • 事务1查询工资表中工资为1w的员工的个数(10个员工),此时事务1还没有结束。正在这个时候,事务2,人力部门有两个新员工入职,他们的工资也是1w,人力部门通过事务2向工资表插入了两条记录,并且提交事务了。 这个时候,事务1又去查询工资为1w的员工个数,发现多了两个员工(12个人),见鬼了,这种情况就叫做幻读
  • 不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。 不可重复读出现在修改update的时候。例如:
    • 员工发起事务1查询工资,工资为1w,事务1尚未关闭。人力部门发起事务2给你涨了工资,涨工资到1.2W(update你的工资表的字段信息),并且提交了事务了。此时,事务1又再次查询自己的工资,发现工资为1.2W,原有的1w这个数据已经读不到了,这就叫做不可重复读

1.4 关于事务隔离级别(解决事务并发问题)

  • 极端模式:读未提交 Read_uncommited,就好比十字路口没有红绿灯一样,效率高,但是风险也高,此时什么事务控制都没有。不要使用这种模式

  • 读已提交 Read_commited,顾名思义,其他事务提交之后,才能读取到这个事务提交的数据,这种模式能解决脏读(因为脏读事务是没提交造成的)问题,解决不了幻读和不可重复读(因为这两个问题的产生就是insert delete update的时候提交了事务造成的)

  • 可重复读 Repeatable_Read,可以进行数据重复读, 解决了脏读和不可重复读的问题

  • 极端模式:串行化Serializable:所有的事务一个个来,不争不抢,一个事务处理完了,另外一个事务继续进行,这样不会出现并发问题。比如ATM机

  • 默认:DEFAULT,默认是数据库的默认,默认模式来源于上面四种模式之一,mysql数据库默认隔离级别可重复读Repeatable_Read,oracle数据库默认级别读已提交Read_commited

  • 设置事务隔离级别

    • read uncommitted 未提交读,脏读,不可重复读,虚读都可能发生.
    • read committed 已提交读,避免脏读,但是不可重复读和虚读有可能发生(Oracle默认)
    • repeatable read 可重复读,避免脏读,不可重复读,但是虚读有可能发生(MySql默认)
    • serializable 串行化的,避免脏读,不可重复读,虚读的发生
    • 查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
事务的隔离级别脏读不可重复读幻读
Read uncommitted可能发生可能发生可能发生
Read committed (Oracle默认)避免可能发生可能发生
Repeatable read(MySql默认)避免避免可能发生
Serializable避免避免避免

1.5 关于事务传播行为

事务往往加载service层方法上。当service层方法A()直接调用service层方法B()时,A()和B()都有自己的事务控制,那么相互调用的时候就会有问题啊,A和B应该有一个关于事务的协商机制,这种机制就叫做事务的传播行为

  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)

  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)

2. Spring中事务的API

虽然可以自己写事务切面增强(通知)类,但是Spring中有相应的API,只要调用就好了。

2.1 PlatformTransactionManager接口

  • 实现类:org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或MyBatis 进行持久化数据时使用
  • 实现类:org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate版本进行持久化数据时使用
  • 配置spring的事务管理对象
<!-- spring的声明式事务 AOP实现,事务管理类DataSourceTransactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>

2.2 事务配置相关标签及属性

  • 事务配置<tx:advice>通知标签
    • 属性id:自定义唯一表示
    • transaction-manager属性:事务管理类,配置事务管理类的id属性值
  • 事务属性配置<tx:attributes>子标签
    • <tx:method>事务方法标签
      • 属性name:方法名
      • 属性read-only:是否只读事务,查询都是只读,其他是非只读; 默认配置为false
      • 属性propagation:事务的传播行为,默认配置REQUIRED或者SUPPORTS
      • 属性isolation:事务隔离级别,默认配置DEFAULT
      • 属性timeout:事务超时时间,配置-1
      • 属性no-rollback-for:遇到什么异常不回滚,配置异常类名,多个类逗号分开
      • 属性rollback-for:遇到什么异常回滚
        • 以上回滚属性不配置,遇到异常就回滚
  • aop切面配置<aop:config>标签
    • <aop:advisor>子标签
      • 属性advice-ref:引用通知(增强),配置tx:advice标签的属性值
      • 属性pointcut:切点配置

3. Spring基于XML的事务配置(理解)

3.1 配置文件

这里基于纯XML配置,主要分为这几部分配置:Spring的IOC配置,Mybatis与Spring的整合配置,事务的配置,将下面几部分的标签放在一起,就是ApplicationContext.xml的全部内容。

  • Spring的IOC配置:
<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置service-->
    <bean id="accountService" class="com.szz.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置properties文件的位置-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
</beans>
  • Mybatis与Spring的整合配置
<!--配置mybatis的SqlSessionFactory工厂-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="typeAliasesPackage" value="com.szz.pojo"></property>
</bean>

<!--配置创建dao代理实现类的扫描器-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.szz.dao"></property>
</bean>
  • 事务的配置
<!--配置事务步骤:
	  第一步:配置事务管理器
	  第二步:配置事务的通知
	  第三步:配置事务的属性
	  第四步:配置aop切入点表达式和事务通知的关联
-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"></property>
</bean>

<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--配置事务的属性-->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
    <!--配置切入点表达式-->
    <aop:pointcut id="pt" expression="execution(* com.szz.service.impl.*.*(..))"></aop:pointcut>
    <!--配置表达式和事务通知的关联-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>

3.2 标签详解

tx:advice

<!--
	作用:
		用于配置事务的通知。
	出现位置:
		beans标签内部都可定义
	属性:
		id:为事务通知提供一个唯一标识。
		transaction-manager:为事务通知指定一个事务管理器的id引用。默认值是transactionManager。
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">

tx:attribute

<!--
	作用:
		用于配置事务的属性。
	出现位置:
		要求写在<tx:advice>标签内部
-->
<tx:attributes>

tx:method

<!--
	作用:
		用于配置每个需要事务支持的方法,所使用的特征。
	出现位置:
		要求写在<tx:attributes>标签内部
	属性:
		name:指定方法名称。支持通配符的配置方式。例如:*表示所有方法 find*表示以find开头的方法。
		read-only:指定是否为只读事务。默认值:false,表示非只读。只有查询方法可以设置为true。
		propagation:指定事务的传播行为。默认值是REQUIRED,表示必须有事务。查询方法设置为SUPPORTS,表示有事务就支持,没有事务就以非实物
		timeout:指定事务的超时时间。默认值是-1,表示永不超时。取值为正整数,以秒为单位。
		isolation:指定事务的隔离级别。默认值是DEFAULT,表示采用数据库的默认隔离级别,不同数据库的默认隔离级别不一样,mysql为REPEATABLE READ,Oracle为READ COMMITTED。其他取值还有:READ UNCOMMITTED和SERIALIZABLE。隔离级别越高执行效率越低,反之亦然。
		rollback-for:用于指定一个异常,当产生该异常时,事务回滚。产生其他异常时,事务不回滚。没有默认值。即,产生任何异常事务都回滚。
		no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚。产生其他异常时,事务回滚。没有默认值。即,产生任何异常事务都回滚。
-->
<tx:method name="*" propagation="REQUIRED" read-only="false" timeout="-1" isolation="" rollback-for="" no-rollback-for=""/>

aop:advisor

<!--
	作用:
		用于建立通知和切入点表达式的关系
	出现位置:
		要求写在<aop:config>标签内部
	属性:
		id:用于指定通知器的唯一标识
		advice-ref:用于指定通知的引用
		pointcut-ref:用于指定切入点表达式的引用
		pointcut:用于指定切入点表达式
		order:当配置多个advisor,用于指定执行优先级
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1" id="" pointcut="" order=""></aop:advisor>

3.3 测试

项目结构如下所示:
在这里插入图片描述
一些简单的代码就不贴了,之前反复写过无数遍,贴一下测试代码,主要是@ContextConfiguration里面写的。

/**
 * 测试类
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:ApplicationContext.xml"})
public class AccountServiceTest {
    // 依赖注入
    @Autowired
    private AccountService accountService;

    @Test
    public void transfer() {
        accountService.transfer("张三","李四",300);
    }
}

4. 基于xml和注解的事务配置

4.1 注解配置

作用在类名上的@Transactional注解表示类中除了标注@Transactional的其余方法都开启事务,其中find类都单独加了@Transactional注解,表明这几种方法开启的事务内部属性不全是默认。@Transactional注解的内部属性与3.2一致。

@Transactional注解是Spring注解配置事务的核心注解,无论是注解驱动开发还是注解和XML混合开发。只要涉及配置事务采用注解的方式,都需要使用此注解。该注解可以出现在接口上,类上和方法上。分别表明:

  • 接口上:当前接口的所有实现类中重写接口的方法有事务支持。
  • 类上:当前类中所有方法有事务支持。
  • 方法上:当前方法有事务的支持。
  • 优先级:方法上>类上>接口上。
@Service
@Transactional
public class AccountServiceImpl implements AccountService {

    // 依赖注入
    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String source, String target, double money) {
        // 1.根据name获取账户对象
        // 汇款人
        Account sourceAccount = accountDao.findByName(source);
        // 收款人
        Account targetAccount = accountDao.findByName(target);
        //2.更新
        sourceAccount.setMoney(sourceAccount.getMoney()-money);
        targetAccount.setMoney(targetAccount.getMoney()+money);
        // 5.数据库持久层更新
        accountDao.update(sourceAccount);
//        int i = 1/0;
        accountDao.update(targetAccount);

    }

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public Account findByName(String name) {
        return accountDao.findByName(name);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<Account> findAll() {
        return accountDao.findAll();
    }
}

4.2 XML配置

xml主要是配置IOC(开启SpringIOC容器的注解扫描),配置mybatis和spring整合,配置事务(开启Spring的注事务解扫描)。
xml文件如下:

  • 配置IOC
<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启SpringIOC容器的注解扫描  @Controller,@Service, @Repository, @Component, @Autowired等注解-->
    <context:component-scan base-package="com.szz"></context:component-scan>

    <!--配置properties文件的位置-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
</beans>
  • 配置mybatis和spring整合
<!--配置mybatis的SqlSessionFactory工厂-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="typeAliasesPackage" value="com.szz.pojo"></property>
</bean>

<!--配置创建dao代理实现类的扫描器-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.szz.dao"></property>
</bean>
  • 配置事务:
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启Spring的 事务注解扫描 @Transactional-->
<tx:annotation-driven></tx:annotation-driven>

测试类与3.3一致

5. 基于注解的事务配置

注解配置分解为JdbcConfigMybatisConfigTransactionManagerConfig,分别代码数据连接池配置,Mybatis配置,事务配置,下面具体贴出每个配置类的具体代码。

  • JdbcConfig

@PropertySource({"classpath:jdbc.properties"})
public class JDBCConfig {
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    //配置数据源
    @Bean
    public DataSource createDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        // 配置(驱动,url,用户名,密码)
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);

        return druidDataSource;
    }
}
  • MybatisConfig
public class MyBatisConfig {

    //配置SQLSessionFactoryBean对象
    @Bean
    public SqlSessionFactoryBean createSqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 配置数据源和pojo别名
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.szz.pojo");
        return sqlSessionFactoryBean;
    }

    // 配置dao扫描器对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        // 配置扫描的包的路径
        mapperScannerConfigurer.setBasePackage("com.szz.dao");
        return mapperScannerConfigurer;
    }

}

  • TransactionManagerConfig
public class TransactionConfig {
    // 配置Spring提供的事务管理器对象
    @Bean
    public DataSourceTransactionManager createDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
  • 总配置文件SpringConfig
@Configuration
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionConfig.class })
// 开启SpringIOC注解扫描
@ComponentScan("com.szz")
// 开启SpringAOP注解扫描
@EnableAspectJAutoProxy
// 开启Spring事务的注解扫描
@EnableTransactionManagement
public class SpringConfig {

}
  • 注:@EnableTransactionManagement注解是Spring支持注解驱动事务配置的标志, 表明Spring开启注解事务配置的支持, 是注解驱动开发事务配置的必备注解。它是用于替代<tx:annotation-driven transaction-manager="transactionManager"/>标签

Service和测试类中的注解与** 4.1**一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值