[Spring5]Spring5 事务

5.1 Spring 事务概述

事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。

事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。

  • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
  • 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
  • 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
  • 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有的操作都执行成功,那自然万事大吉。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。

5.1.1 事务管理方式

Spring 支持以下 2 种事务管理方式:

事务管理方式说明
编程式事务管理编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。
声明式事务管理Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。

  • 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
  • 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

5.1.2 事务管理器

Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下:

public interface PlatformTransactionManager extends TransactionManager {    
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;                     
    void commit(TransactionStatus status) throws TransactionException;    
    void rollback(TransactionStatus status) throws TransactionException;}

该接口中各方法说明如下:

名称说明
TransactionStatus getTransaction(TransactionDefinition definition)用于获取事务的状态信息
void commit(TransactionStatus status)用于提交事务
void rollback(TransactionStatus status)用于回滚事务

Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。

实现类说明
org.springframework.jdbc.datasource.DataSourceTransactionManager使用 Spring JDBC 或 iBatis 进行持久化数据时使用。
org.springframework.orm.hibernate3.HibernateTransactionManager使用 Hibernate 3.0 及以上版本进行持久化数据时使用。
org.springframework.orm.jpa.JpaTransactionManager使用 JPA 进行持久化时使用。
org.springframework.jdo.JdoTransactionManager当持久化机制是 Jdo 时使用。
org.springframework.transaction.jta.JtaTransactionManager使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。

这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。

5.1.3 事务的隔离级别

Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。

方法说明
ISOLATION_DEFAULT使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTEDOracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READMySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

5.1.4 事务的传播行为

事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。例如,事务方法 A 在调用事务方法 B 时,B 方法是继续在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。

事务方法指的是能让数据库表数据发生改变的方法,例如新增数据、删除数据、修改数据的方法

Spring 提供了以下 7 种不同的事务传播行为。

名称说明
PROPAGATION_MANDATORY支持当前事务,如果不存在当前事务,则引发异常。
PROPAGATION_NESTED如果当前事务存在,则在嵌套事务中执行。
PROPAGATION_NEVER不支持当前事务,如果当前事务存在,则引发异常。
PROPAGATION_NOT_SUPPORTED不支持当前事务,始终以非事务方式执行。
PROPAGATION_REQUIRED默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。
PROPAGATION_REQUIRES_NEW创建新事务,如果已经存在事务则暂停当前事务。
PROPAGATION_SUPPORTS支持当前事务,如果不存在事务,则以非事务方式执行。

5.2 Spring 基于 XML 实现事务管理

5.2.1 Spring 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/user_db?useUnicode=true&amp;characterEncoding=UTF-8" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
    </bean>

    <!--jdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入dataSource对象-->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--开启组件扫描-->
    <context:component-scan base-package="top.shiyiri.spring5" />

    <!--创建事务管理器-->
    <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" isolation="REPEATABLE_READ"/>
<!--            <tx:method name="account*"/>-->
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* top.shiyiri.spring5.service.UserService.accountMoney(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt" />
    </aop:config>
</beans>

5.2.2 Service 层

package top.shiyiri.spring5.service;

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;
import top.shiyiri.spring5.dao.UserDao;

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    //转账的方法
    public void accountMoney() {
//        try {
            //第一步,开启事务

            //第二部,进行业务操作
            //Lucy少100
            userDao.reduceMoney();

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

            //Mary多一百
            userDao.addMoney();

            //第三步,没有发生异常,提交事务
//        } catch (Exception e) {
//            //出现异常,事务回滚
//        }
    }
}

5.3 Spring 基于注解实现事务管理

5.3.1 在 Spring 配置文件配置事务管理器

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/user_db?useUnicode=true&amp;characterEncoding=UTF-8" />
    <property name="username" value="root" />
    <property name="password" value="123456" />
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>

<!--jdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入dataSource对象-->
    <property name="dataSource" ref="dataSource" />
</bean>

<!--开启组件扫描-->
<context:component-scan base-package="top.shiyiri.spring5" />

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

5.3.2 在 Spring配置文件开启事务注解

  • 在 spring 配置文件中引入名称空间 tx

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
  • 开启事务注解

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" />
    

5.3.3 在 Service 类上添加事务注解

  • @Transactional,这个注解可以添加到类上面,也可以添加到方法上面
  • 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
  • 如果把这个注解添加方法上面,为这个方法添加事务
5.3.3.1 声明式事务管理参数配置

image-20220220195404480

  • propagation:事务传播行为【见 5.1.4】
  • ioslation:事务隔离级别【见 5.1.3]】
  • timeout:超时时间
    • 事务需要在一定时间内进行提交,如果不提交进行回滚
    • 默认值式 -1,设置时间以秒为单位进行计算
  • readOnly:是否只读
    • 读:查询操作。写:添加修改删除操作
    • readOnly 默认值 false,表示可以增删改查
    • readOnly 设置为 true,只能查询
  • rollbackFor:回滚。设置出现哪些异常进行事务回滚
  • noRollBackFor:不回滚。设置出现哪些异常不进行事务回滚

5.3.4 Dao 层创建

package top.shiyiri.spring5.dao;

public interface UserDao {

    void addMoney();
    void reduceMoney();
}
package top.shiyiri.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addMoney() {
        String sql = "update t_account set money=money+? where username=?";
        jdbcTemplate.update(sql, 100, "mary");
    }

    @Override
    public void reduceMoney() {
        String sql = "update t_account set money=money-? where username=?";
        jdbcTemplate.update(sql, 100, "lucy");
    }
}

5.3.5 Service 层创建

package top.shiyiri.spring5.service;

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;
import top.shiyiri.spring5.dao.UserDao;

//noRollbackFor = {ArithmeticException.class},
@Transactional(readOnly = false, timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    //转账的方法
    public void accountMoney() {
//        try {
            //第一步,开启事务

            //第二部,进行业务操作
            //Lucy少100
            userDao.reduceMoney();

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

            //Mary多一百
            userDao.addMoney();

            //第三步,没有发生异常,提交事务
//        } catch (Exception e) {
//            //出现异常,事务回滚
//        }
    }
}

5.3.6 测试类

@Test
public void testAccount() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.accountMoney();
}

5.3.7 TxConfig

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

package top.shiyiri.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = {"top.shiyiri.spring5"})
@EnableTransactionManagement //开启事务
public class TxConfig {

    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(){
        //到IoC容器中根据类型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(getDruidDataSource());
        return jdbcTemplate;
    }

    //创建事务管理器对象
    @Bean
    public DataSourceTransactionManager getTransactionManager(){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(getDruidDataSource());
        return transactionManager;
    }
}

5.3.8 测试类

@Test
public void testAccount2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = context.getBean("userService", UserService.class);
    userService.accountMoney();
}

6.1 Spring 整合日志框架

整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除

Spring5 整合了 log4j2

  • 第一步,引入 log4j2 相关 jar 包

  • 第二步,创建 log4j2.xml 配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
    <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
    <configuration status="INFO">
        <!--先定义所有的appender-->
        <appenders>
            <!--输出日志信息到控制台-->
            <console name="Console" target="SYSTEM_OUT">
                <!--控制日志输出的格式-->
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </console>
        </appenders>
        <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
        <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
        <loggers>
            <root level="info">
                <appender-ref ref="Console"/>
            </root>
        </loggers>
    </configuration>
    
  • 测试

    package top.shiyiri.spring5.test;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * @author Aunean
     * @date 2022/2/18 20:58
     */
    //自定义日志
    public class UserLog {
    
        private static Logger logger = LoggerFactory.getLogger(UserLog.class);
    
        public static void main(String[] args) {
            logger.info("hello log4j2");
            logger.warn("hello log4j2");
        }
    }
    

6.2 Spring5 @Nullable 注解

  • @Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
  • 注解用在方法上面,方法返回值可以为空
  • 注解使用在方法参数里面,方法参数可以为空
  • 注解使用在属性上面,属性值可以为空

6.3 Spring5 函数式风格编程

//Spring5特性,函数式风格创建对象,交给spring进行管理
@Test
public void testGenericApplicationContext() {
    //1.创建 GenericApplicationContext 对象
    GenericApplicationContext context = new GenericApplicationContext();
    //2.调用context的方法对象进行注册
    context.refresh();
    context.registerBean(User.class, User::new);
    //3.获取在Spring注册的对象,如果没指定名字,则需要使用全类名
    User user = (User) context.getBean("top.shiyiri.spring5.test.User");
    System.out.println(user);

    //指定bean的名字
    context.registerBean("user1", User.class, User::new);
    Object user1 = context.getBean("user1");
    System.out.println(user1);
}
/*输出:
top.shiyiri.spring5.test.User@614ddd49
top.shiyiri.spring5.test.User@694e1548
*/

6.4 Spring5 整合 JUnit5

package top.shiyiri.spring5.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.shiyiri.spring5.service.UserService;

/**
 * Spring框架整合 JUnit4
 */

@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
@ContextConfiguration("classpath:bean1.xml") //加载配置文件
public class JTest4 {
    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.accountMoney();
    }
}
package top.shiyiri.spring5.test;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.shiyiri.spring5.service.UserService;

/**
 * Spring框架整合 JUnit5
 */
//@ExtendWith(SpringExtension.class) //单元测试框架
//@ContextConfiguration("classpath:bean1.xml") //加载配置文件
//使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations="classpath:bean1.xml")
public class JTest5 {
    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.accountMoney();
    }
}

完整代码

https://gitee.com/aunean/spring5_txdemo1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值