Spring 事务

Spring中的事务

1.事务回顾

事务:是逻辑上一组操作,要么全都成功,要么全都失败.
事务特性:ACID

原子性:事务不可分割
一致性:事务执行的前后,数据完整性保持一致.
隔离性:一个事务执行的时候,不应该受到其他事务的打扰
持久性:一旦结束,数据就永久的保存到数据库.

如果不考虑隔离性:
脏读:一个事务读到另一个事务未提交数据
不可重复读:一个事务读到另一个事务已经提交数据(update)导致一个事务多次查询结果不一致
虚读:一个事务读到另一个事务已经提交数据(insert)导致一个事务多次查询结果不一致

事务的隔离级别:
未提交读:以上情况都有可能发生。
已提交读:避免脏读,但不可重复读,虚读是有可能发生。
可重复读:避免脏读,不可重复读,但是虚读有可能发生。
串行的:避免以上所有情况.

2.Spring中的事务

Spring中事务管理
分层开发:事务处在Service层.

Spring提供事务管理API

PlatformTransactionManager:平台事务管理器.
​ getTransaction(TransactionDefinition definition)
​ rollback(TransactionStatus status)
​ commit(TransactionStatus status)

TransactionDefinition:事务定义
​ ISOLation_XXX:事务隔离级别.
​ PROPAGATION_XXX:事务的传播行为.

TransactionStatus:事务状态
​ 是否有保存点
​ 是否是一个新的事务
​ 事务是否已经提交

关系:PlatformTransactionManager通过TransactionDefinition设置事务相关信息管理事务,管理事务过程中,产生一些事务状态,状态由TransactionStatus记录。

API详解:
​ PlatformTransactionManager:接口.
​ Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现

使用Spring JDBC或iBatis 进行持久化数据时使用(重点)
​ org.springframework.jdbc.datasource.DataSourceTransactionManager
使用Hibernate进行持久化数据时使用
​ org.springframework.orm.hibernate.HibernateTransactionManager
使用JPA进行持久化时使用
​ org.springframework.orm.jpa.JpaTransactionManager
当持久化机制是Jdo时使用
​ org.springframework.jdo.JdoTransactionManager
使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用
​ org.springframework.transaction.jta.JtaTransactionManager

TransactionDefinition:
​ ISOLATION_DEFAULT:默认级别. Mysql --> repeatable_read | Oracle -->> read_commited

级别如下:

​ ISOLATION_READ_UNCOMMITTED
​ ISOLATION_READ_COMMITTED
​ ISOLATION_REPEATABLE_READ
​ ISOLATION_SERIALIZABLE

3.Sping中事务的传播行为

事务的传播行为:(不是JDBC事务管理,用来解决实际开发的问题.)
传播行为:解决业务层之间的调用的事务的关系.

​ PROPAGATION_REQUIRED: 支持当前事务,如果不存在 就新建一个

  • A,B 如果A有事务,B使用A的事务,如果A没有事务,B就开启一个新的事务.(A,B是在一个事务中。)

PROPAGATION_SUPPORTS: 支持当前事务,如果不存在,就不使用事务

  • A,B 如果A有事务,B使用A的事务,如果A没有事务,B就不使用事务.

PROPAGATION_MANDATORY: 支持当前事务,如果不存在,抛出异常

  • A,B 如果A有事务,B使用A的事务,如果A没有事务,抛出异常.

PROPAGATION_REQUIRES_NEW: 如果有事务存在,挂起当前事务,创建一个新的事务

  • A,B 如果A有事务,B将A的事务挂起,重新创建一个新的事务.(A,B不在一个事务中.事务互不影响.)

PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果有事务存在,挂起当前事务

  • A,B 非事务的方式运行,A有事务,就会挂起当前的事务.

PROPAGATION_NEVER: 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED: 如果当前事务存在,则嵌套事务执行

  • 基于SavePoint技术.

  • A,B A有事务,A执行之后,将A事务执行之后的内容保存到SavePoint.B事务有异常的话,用户需要自己设置事务提交还是回滚.

  • 常用:(重点)
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRES_NEW
    PROPAGATION_NESTED

4.Spring的配置XML方式进行事务管理

支持声明式事务管理: 不需要手动编写代码,只需要配置即可.

事务操作的环境搭建

4.1创建表
CREATE TABLE `t_account` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(20) NOT NULL,
    `money` double DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `t_account` VALUES ('1', 'aaa', '1000');
INSERT INTO `t_account` VALUES ('2', 'bbb', '1000');
4.2导入依赖
<dependencies>
    <!-- spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.20</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.20</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.9.1</version>
    </dependency>
    <!-- tx 事务 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.20</version>
    </dependency>

    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.9</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>

</dependencies>
4.3AccountController
package com.qf.controller;

import com.qf.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class AccountController {

    @Autowired
    private AccountService accountService;

    /**
     * 转账:accountId1 给 accountId2 转 money
     */
    public void transfer(Integer fromAccountId,Integer toAccountId,Double money){
        accountService.transfer(fromAccountId,toAccountId,money);
    }
}
4.4AccountService
package com.qf.service;

public interface AccountService {
    
    //转账
    void transfer(Integer fromAccountId, Integer toAccountId, Double money);
    
}
4.5AccountServiceImpl
package com.qf.service.impl;

import com.qf.mapper.AccountMapper;
import com.qf.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    /**
     * 转账
     * @param fromAccountId
     * @param toAccountId
     * @param money
     */
    @Override
    public void transfer(Integer fromAccountId, Integer toAccountId, Double money) {

        //从账户转出
        accountMapper.decreaseMoney(fromAccountId,money);

        //手动模拟异常
        //int i = 1/0;

        //转入账户
        accountMapper.increaseMoney(toAccountId,money);
    }
}
4.6AccountMapper
package com.qf.mapper;

import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface AccountMapper {
    /**
     * 转出
     *
     * @param fromAccountId
     * @param money
     */
    void decreaseMoney(@Param("fromAccountId") Integer fromAccountId, @Param("money") Double money);

    /**
     * 转入
     *
     * @param toAccountId
     * @param money
     */
    void increaseMoney(@Param("toAccountId") Integer toAccountId, @Param("money") Double money);
}
4.7AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.qf.mapper.AccountMapper">

    <!-- 转出方法 -->
    <update id="decreaseMoney">
        update t_account set money = money - #{money} where id = #{fromAccountId}
    </update>

    <!-- 转入方法 -->
    <update id="increaseMoney">
        update t_account set money = money + #{money} where id = #{toAccountId}
    </update>

</mapper>
4.8db.properties
db.username = root
db.password = root
db.url = jdbc:mysql://localhost:3306/java2203?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
db.driver = com.mysql.cj.jdbc.Driver
4.9log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
4.10mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 配置日志 -->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

</configuration>
4.11applicationContext.xml ( 注意导入 tx 约束 )
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-4.2.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">

        <!-- 导入外部配置文件 db.properties -->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

        <!-- 配置数据源对象 -->
        <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
            <!-- 导入 db.properties 中的值-->
            <property name="username" value="${db.username}"></property>
            <property name="password" value="${db.password}"></property>
            <property name="url" value="${db.url}"></property>
            <property name="driverClassName" value="${db.driver}"></property>
        </bean>

        <!-- 扫描对应包下的注解 -->
        <context:component-scan base-package="com.qf"></context:component-scan>

        <!-- 配置sqlSessionFactory -->
        <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 必选配置 -->
            <property name="dataSource" ref="datasource"></property>
            <!-- 非必选属性,根据自己需求去配置 -->
            <!-- 导入 mybatis-config.xml -->
            <property name="configLocation" value="classpath:mybatis-config.xml"></property>
            <!-- 导入 Mapper.xml 文件,classpath后面不能有空格 -->
            <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
        </bean>

        <!-- 扫描 Mapper 接口,生成代理对象 -->
        <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 指定扫描的具体位置 -->
            <property name="basePackage" value="com.qf.mapper"></property>
        </bean>


    <!-- 事务核心管理器,封装了所有事务操作. 依赖于连接池 -->
    <bean name="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"></property>
    </bean>

    <!-- 配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 企业中配置CRUD方法一般使用方法名+通配符*的形式配置通知,此时类中的方法名要和配置的方法名一致 -->
            <!-- 以方法为单位,指定方法应用什么事务属性
                    isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
                    propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
                    read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
                    timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
                    rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
                    no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
                 -->
            <tx:method name="save*" isolatioaddn="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
            <tx:method name="add*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
            <tx:method name="update*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
            <tx:method name="modify*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
            <tx:method name="delete*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
            <tx:method name="remove*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
            <tx:method name="get*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="true" />
            <tx:method name="find*" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="true" />
            
            <tx:method name="transfer" isolation="REPEATABLE_READ"
                       propagation="REQUIRED" read-only="false" />
        </tx:attributes>
    </tx:advice>

    <!-- 配置织入 -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(* com.qf.service.impl.*ServiceImpl.*(..))" id="txPc" />
        <!-- 配置切面 : 通知+切点 advice-ref:通知的名称 pointcut-ref:切点的名称 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
    </aop:config>

</beans>
4.12测试
package com.qf.test;

import com.qf.controller.AccountController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

    @Test
    public void test_transfer(){

        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");

        AccountController accountController =
                (AccountController)applicationContext.getBean("accountController");

        accountController.transfer(1,2,100d);

    }
}

5.Spring的注解方式进行事务管理

5.1创建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.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context" 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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->


        <!-- 导入外部配置文件 db.properties -->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

        <!-- 配置数据源对象 -->
        <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
            <!-- 导入 db.properties 中的值-->
            <property name="username" value="${db.username}"></property>
            <property name="password" value="${db.password}"></property>
            <property name="url" value="${db.url}"></property>
            <property name="driverClassName" value="${db.driver}"></property>
        </bean>

        <!-- 扫描对应包下的注解 -->
        <context:component-scan base-package="com.qf"></context:component-scan>

        <!-- 配置sqlSessionFactory -->
        <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 必选配置 -->
            <property name="dataSource" ref="datasource"></property>
            <!-- 非必选属性,根据自己需求去配置 -->
            <!-- 导入 mybatis-config.xml -->
            <property name="configLocation" value="classpath:mybatis-config.xml"></property>
            <!-- 导入 Mapper.xml 文件,classpath后面不能有空格 -->
            <property name="mapperLocations" value="classpath:mapper/*.xml"></property>

        </bean>

        <!-- 扫描 Mapper 接口,生成代理对象 -->
        <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 指定扫描的具体位置 -->
            <property name="basePackage" value="com.qf.mapper"></property>
        </bean>


        <!-- 配置事务 -->
        <!-- 事务平台管理器,封装了所有的事务操作,依赖数据源 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="datasource"></property>
        </bean>

        <!-- 开启注解驱动事务支持 -->
        <tx:annotation-driven></tx:annotation-driven>

</beans>
5.2修改AccountServiceImpl
package com.qf.service.impl;

import com.qf.mapper.AccountMapper;
import com.qf.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Transactional 配置事务
 * 可以写在类上,也可以写在方法上
 */
//@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    /**
     * 转账
     * @param fromAccountId
     * @param toAccountId
     * @param money
     */
    @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
    @Override
    public void transfer(Integer fromAccountId, Integer toAccountId, Double money) {

        //从账户转出
        accountMapper.decreaseMoney(fromAccountId,money);

        //手动模拟异常
        int i = 1/0;

        //转入账户
        accountMapper.increaseMoney(toAccountId,money);
    }
}
5.3测试
package com.qf.test;

import com.qf.controller.AccountController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {


    /**
     * xml测试事务
     */
    @Test
    public void test_transfer(){

        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");

        AccountController accountController =
                (AccountController)applicationContext.getBean("accountController");

        accountController.transfer(1,2,100d);

    }

    /**
     * 注解测试事务
     */
    @Test
    public void test_transfer_annotation_driven(){

        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("bean.xml");

        AccountController accountController =
                (AccountController)applicationContext.getBean("accountController");

        accountController.transfer(1,2,100d);

    }
}
posted @ 2022-07-10 18:08  qtyanan  阅读(31)  评论(0编辑  收藏  举报