# 事务概念

什么是事务

​ 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败

经典场景:银行转账

事务四个特性 (ACID)

  1. 原子性
  2. 一致性
  3. 隔离性
  4. 持久性

# 事务操作 (搭建事务操作环境)

引入相关 jar 包依赖

image-20230613103011469

image-20230612200731209

1. 创建数据库表,添加记录

image-20230612194305882

2. 创建 service,创建 dao,完成对象创建和注入关系

2.1 service 中注入 dao,dao 中注入 jdbcTemplate,JdbcTemplate 模版中注入 DataSource

<?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"
       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">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useUnicode=true
jdbc.username=root
jdbc.password=dkx
public interface UserService {
}
@SuppressWarnings("all")
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
}
public interface UserDao {
}
@SuppressWarnings("all")
@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

3, 在 dao 创建两个方法,多钱和少钱的方法,在 service 创建方法 (转账方法)

Dao

public interface UserDao {
    // 多钱
    int addMoney();
    // 少钱
    int reduceMoney();
}
@SuppressWarnings("all")
@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    // 多钱
    @Override
    public int addMoney() {
        String sql = "update t_account set money = money - ? where username = ?";
        int flag = jdbcTemplate.update(sql,100,"lucy");
        return flag;
    }
    // 少钱
    @Override
    public int reduceMoney() {
        String sql = "update t_account set money = money + ? where username = ?";
        int flag = jdbcTemplate.update(sql,100,"mary");
        return flag;
    }
}

Service

public interface UserService {
    // 转账方法
    boolean accountMoney();
}
@SuppressWarnings("all")
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public boolean accountMoney(){
        int flag = userDao.addMoney();
        int flag1 = userDao.reduceMoney();
        return flag > 0 && flag1 > 0 ? true : false;
    }
}

测试代码:

public class AppTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext
                ("appliction.xml");
        UserService service = context.getBean("userServiceImpl", UserService.class);
        boolean flag = service.accountMoney();
        System.out.println(flag == true ? "转账OK" : "转账NO");
    }
}

测试结果:

Jun 12, 2023 8:41:46 PM com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
INFO: {dataSource-1} inited
转账OK

刷新数据库之前

image-20230612204222085

刷新数据库之后

image-20230612204237063

4. 上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题,如下演示:

  1. 在转账方法程序过程中制造一个异常
@SuppressWarnings("all")
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public boolean accountMoney(){
        int flag = userDao.addMoney();
        // 制造异常,让程序终止与此
        int i = 1/0;
        int flag1 = userDao.reduceMoney();
        return flag > 0 && flag1 > 0 ? true : false;
    }
}
  1. 恢复数据库的数据

image-20230612204700368

  1. 执行程序

image-20230612204805942

  1. 查看数据库数据

image-20230612204830052

lucy 少钱了,mary 且没有加钱

# 上述问题使用事务进行解决:要么都成功,要么都失败

# 事务操作 (Spring 事务管理介绍)

1. 事务添加到 JavaEE 三层结构里面 Service 层 (业务逻辑层)

2. 在 Spring 进行事务管理操作

  • 有两种方式:编程式事务管理和声明式事务管理 (使用)

3. 声明式事务管理

  1. 基于注解方式
  2. 基于 xml 配置文件方式

4. 在 spring 进行声明式事务管理,底层使用 AOP 原理

5.spring 事务管理 API

  • 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
  • PlatformTransactionManager

image-20230612214113877

# 事务操作 (注解声明式事务管理)

# 1. 在 spring 配置文件配置事务管理器

<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <!-- 注入数据源 -->
   <property name="dataSource" ref="dataSource"/>
</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: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">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 创建事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

# 2. 在 spring 配置文件,开启事务注解

  1. 在 spring 配置文件引入名称空间:tx
<?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"
       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">
</beans>
  1. 开启事务注解
<!-- 开启事务注解 -->
    <!--
        transaction-manager: 指定使用的事务管理器
    -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

完整 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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 创建事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 开启事务注解 -->
    <!--
        transaction-manager: 指定使用的事务管理器
    -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
  1. 在 service 类上面 (或者 service 类里面方法上面) 添加事务注解
  • @Transactional:这个注解添加到类上面,也可以添加到方法上面
  • 如果将注解添加类上面,这个类里面所有的方法都会添加事务
  • 如果将注解添加到方法上面,则仅为这个方法添加事务
@SuppressWarnings("all")
@Service
// 开启事务管理
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public boolean accountMoney(){
        //try {} catch 等逻辑代码是编程式事务,编写会让程序看起来很臃肿
//        try{
            // 第一步  开启事务
            int flag = userDao.addMoney();
            // 制造异常,让程序终止与此
            int i = 1/0;
            // 第二步  进行业务操作
            int flag1 = userDao.reduceMoney();
            return flag > 0 && flag1 > 0 ? true : false;
            // 第三步  没有发生异常,提交事务
//        }catch(ArithmeticException e){
//            throw new RuntimeException(e);
//            // 第四步  出现异常,事务回滚
//        }
    }
}
  1. 再来测试转账代码:
public class AppTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext
                ("appliction.xml");
        UserService service = context.getBean("userServiceImpl", UserService.class);
        boolean flag = service.accountMoney();
        System.out.println(flag == true ? "转账OK" : "转账NO");
    }
}

测试结果:image-20230612220116964

image-20230612220133700

数据库没有发生变化,要么都成功,要么都失败

# 事务操作 (声明式事务管理参数配置)

image-20230612220349678

  1. propagation:事务传播行为
    • 当一个事务方法被另外一个事务方法调用时候,这个事务方法如何进行

事务的传播行为可以由传播属性指定,Spring 定义了 7 种类传播行为

传播属性描述
REQUIRED如果有事务正在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
REQUIRED_NEW当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起,方法 1 调用方法 2 时出现了问题,方法 2 依然能正常提交
SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTE当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
MANDATORY当前的方法必须在运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行
// 开启事务管理
@Transactional(propagation = Propagation.REQUIRED)
  1. isolation:事务隔离级别

    1. 事务有特性称为隔离性,多事务操作之间不会产生影响,不考虑隔离性产生很多问题
    2. 有三个读问题:脏读,不可重复读,虚 (幻) 读
    3. 脏读:一个未提交事务读取到另一个未提交事务的数据
    4. 不可重复读:一个未提交事务读取到另一个提交事务修改的数据
    5. 虚 (幻) 读:一个未提交事务读取到另一个提交事务添加的数据
    6. 解决:通过设置事务隔离级别,解决读问题
    脏读不可重复读幻读
    RED_UNCOMMITTED<br>(读未提交)
    READ_COMMITTED<br/>(读已提交)
    REPEATABLE_READ<br>(可重复读)
    SERIALIZABLE<br/>(串行化)

    设置事务的隔离级别

    // 开启事务管理
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
  2. timeout:超时时间

    1. 事务需要在一定时间内进行提交,如果不提交进行回滚
    2. 默认值是 - 1,设置时间以秒单位进行计算
    // 开启事务管理
    @Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
  3. readOnly:是否只读

    1. 读:查询操作,写:添加修改删除操作
    2. readOnly 默认值 false,表示可以查询,可以添加修改删除操作
    3. 设置 readOnly 值是 true,设置成 true 之后,只能查询
    // 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
    @Transactional(readOnly = true,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)

    执行修改 Update 操作后就会报错:

    org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [update t_account set money = money - ? where name = ?]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
    
  4. rollbackFor:回滚

    1. 设置出现哪些异常进行事务回滚

    上面演示中使用的异常是 ArithmeticException 算数异常,报错后事务就会回滚,但是有些异常是不会回滚的比如 IOException 就不会,如下演示:

    @SuppressWarnings("all")
    @Service
    // 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
    @Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
        public boolean accountMoney() throws IOException {
                int flag = userDao.addMoney();
    //            if(true){
    //                throw new IOException();
    //            }
                int i = 1/0;
                int flag1 = userDao.reduceMoney();
                return flag > 0 && flag1 > 0 ? true : false;
        }
    }

    测试代码:

    public class AppTest {
        @Test
        public void test() throws IOException {
            ApplicationContext context = new ClassPathXmlApplicationContext
                    ("appliction.xml");
            UserService service = context.getBean("userServiceImpl", UserService.class);
            boolean flag = service.accountMoney();
            System.out.println(flag == true ? "转账OK" : "转账NO");
        }
    }

    测试结果:

    java.lang.ArithmeticException: / by zero
    

    image-20230613093453847

    上面的算数异常不会出现不一致性的问题但是改为 IOException 呢:

    @SuppressWarnings("all")
    @Service
    // 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
    @Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
        public boolean accountMoney() throws IOException {
                int flag = userDao.addMoney();
                if(true){
                    throw new IOException();
                }
    //            int i = 1/0;
                int flag1 = userDao.reduceMoney();
                return flag > 0 && flag1 > 0 ? true : false;
        }
    }

    直接看测试结果:

    java.io.IOException
    

    image-20230613093618342

    可以看出事务并没有起到作用,这时就需要使用 rollbackFor: 来针对性的异常事务回滚了:

    @SuppressWarnings("all")
    @Service
    // 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
    @Transactional(rollbackFor={IOException.class},readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
        public boolean accountMoney() throws IOException {
                int flag = userDao.addMoney();
                if(true){
                    throw new IOException();
                }
    //            int i = 1/0;
                int flag1 = userDao.reduceMoney();
                return flag > 0 && flag1 > 0 ? true : false;
        }
    }

    直接看测试结果:

    java.io.IOException
    

    image-20230613093834276

    事务发生 IO 异常后进行了回滚

    注意:指定了 IO 异常回滚并不会影响它自己的算术异常回滚的机制,但是可以使用下面的 noRollbackFor 来指定算数异常不回滚

  5. noRollbackFor:不回滚

    1. 设置出现哪些异常不进行事务回滚

ArithmeticException 是默认进行事务回滚的,将其设置为不回滚:

@SuppressWarnings("all")
@Service
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
@Transactional(noRollbackFor={ArithmeticException.class},rollbackFor={IOException.class},readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public boolean accountMoney() throws IOException {
            int flag = userDao.addMoney();
//            if(true){
//                throw new IOException();
//            }
            int i = 1/0;
            int flag1 = userDao.reduceMoney();
            return flag > 0 && flag1 > 0 ? true : false;
    }
}

测试代码:

public class AppTest {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext
                ("appliction.xml");
        UserService service = context.getBean("userServiceImpl", UserService.class);
        boolean flag = service.accountMoney();
        System.out.println(flag == true ? "转账OK" : "转账NO");
    }
}

测试结果:

java.lang.ArithmeticException: / by zero

image-20230613094220323

# 事务操作 (XML 声明式事务管理)

1. 在 spring 配置文件中进行配置

开启 tx,aop,context 命名空间

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

第 0 步:配置通过外部引用读取数据库连接池配置,并创建 JdbcTemplate 对象

<?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 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">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

第一步:配置事务管理器

<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <!-- 注入数据源 -->
   <property name="dataSource" ref="dataSource"/>
</bean>

第二步:配置通知

  • 通知:实际被增强的逻辑部分
<!-- 配置通知 -->
<tx:advice id="txAdvice">
   <!-- 配置事务参数 -->
   <tx:attributes>
      <!-- 指定哪种规则的方法上面添加事务 -->
      <tx:method name="accountMoney" propagation="REQUIRED"/>
      <!--<tx:method name="accountMoney" propagation="REQUIRED"/>-->
   </tx:attributes>
</tx:advice>

第三步:配置切入点和切面

  • 切入点:实际被增强的方法

  • 切面:将通知应用到切入点的操作就是切面

<!-- 配置切入点和切面 -->
<aop:config>
   <!-- 配置切入点 -->
   <aop:pointcut id="pt" expression="execution(* com.dkx.service.impl.UserServiceImpl.*(..))"/>
   <!-- 配置切面 -->
   <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>

完整 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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <context:component-scan base-package="com.dkx.dao,com.dkx.service" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 创建事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 配置通知 -->
    <tx:advice id="txAdvice">
        <!-- 配置事务参数 -->
        <tx:attributes>
            <!-- 指定哪种规则的方法上面添加事务 -->
            <tx:method name="accountMoney" propagation="REQUIRED"/>
            <!--<tx:method name="accountMoney" propagation="REQUIRED"/>-->
        </tx:attributes>
    </tx:advice>
    <!-- 配置切入点和切面 -->
    <aop:config>
        <!-- 配置切入点 -->
       <aop:pointcut id="pt" expression="execution(* com.dkx.service.impl.UserServiceImpl.*(..))"/>
        <!-- 配置切面 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>
</beans>

将注解注释掉:

@SuppressWarnings("all")
@Service
// 开启事务管理 readOnly=true 开启只读操作,不能进行修改删除添加
//@Transactional(noRollbackFor={ArithmeticException.class},rollbackFor={IOException.class},readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public boolean accountMoney() throws IOException {
            int flag = userDao.addMoney();
//            if(true){
//                throw new IOException();
//            }
            int i = 1/0;
            int flag1 = userDao.reduceMoney();
            return flag > 0 && flag1 > 0 ? true : false;
    }
}

测试代码:

public class AppTest {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext
                ("appliction1.xml");
        UserService service = context.getBean("userServiceImpl", UserService.class);
        boolean flag = service.accountMoney();
        System.out.println(flag == true ? "转账OK" : "转账NO");
    }
}

测试结果:

java.lang.ArithmeticException: / by zero

image-20230613102502454

# 事务操作 (完全注解声明式事务管理)

1. 创建配置类,使用配置类替代 xml 配置文件

目录结构:

image-20230613110906585

SpringConfig

// 设置为配置 Bean
@Configuration
// 配置组件扫描
@ComponentScan(basePackages = {"com.dkx.dao","com.dkx.service"})
@PropertySource("classpath:jdbc.properties")
// 开启事务管理
@EnableTransactionManagement
// 导入其它配置类
@Import({JdbcConfig.class,TransactionManagerConfig.class,JdbcTemplateConfig.class})
public class SpringConfig {
}

JdbcConfig

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    // 创建 DruidDataSource 对象
    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource druid = new DruidDataSource();
        // 注入数据库连接池信息
        druid.setDriverClassName(driver);
        druid.setUrl(url);
        druid.setUsername(username);
        druid.setPassword(password);
        return druid;
    }
}

JdbcTemplateConfig

public class JdbcTemplateConfig {
    // 创建 JdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbc = new JdbcTemplate();
        // 注入数据源
        jdbc.setDataSource(dataSource);
        return jdbc;
    }
}

TransactionManagerConfig

public class TransactionManagerConfig {
    // 创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        // 注入数据源
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

Dao

public interface UserDao {
    // 多钱
    int addMoney();
    // 少钱
    int reduceMoney();
}

DaoImpl

@SuppressWarnings("all")
@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    // 多钱
    @Override
    public int addMoney() {
        String sql = "update t_account set money = money - ? where name = ?";
        int flag = jdbcTemplate.update(sql,100,"lucy");
        return flag;
    }
    // 少钱
    @Override
    public int reduceMoney() {
        String sql = "update t_account set money = money + ? where name = ?";
        int flag = jdbcTemplate.update(sql,100,"mary");
        return flag;
    }
}

Service

public interface UserService {
    // 转账方法
    boolean accountMoney() throws IOException;
}

ServiceImpl

@SuppressWarnings("all")
@Service
// 开启事务管理
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    public boolean accountMoney() throws IOException {
            int flag = userDao.addMoney();
            int i = 1/0;
            int flag1 = userDao.reduceMoney();
            return flag > 0 && flag1 > 0 ? true : false;
    }
}

测试代码:

public class AppTest {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService service = context.getBean("userServiceImpl", UserService.class);
        boolean flag = service.accountMoney();
        System.out.println(flag == true ? "转账OK" : "转账NO");
    }
}

测试结果:

java.lang.ArithmeticException: / by zero

image-20230613111232201