【03-04】Spring声明式事务

本文详细介绍了Spring中JdbcTemplate的应用实践,包括导入pom依赖、配置数据库资源及核心配置文件,以及常用API的使用。接着深入讲解了声明式事务的概念、ACID特性,并对比了编程式事务。重点阐述了声明式事务的配置,如事务管理器的配置、事务注解的使用以及事务属性的设置,包括隔离级别、传播行为、超时和只读属性。最后提到了基于XML的事务配置方式。
摘要由CSDN通过智能技术生成

一、Spring JdbcTemplate

在Spring中为了更加方便的操作JDBC,在JDBC的基础之上定义了一个抽象层,此设计的目的是为了给不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式尽可能的保留了灵活性,将数据库存取的工作量降到最低。

1、应用实践

1.1 导入相关pom依赖

 	<!--导入SpringIOC的核心依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.28</version>
    </dependency>
    <!--连接数据库的核心以来-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <!--druid数据源连接池的核心依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>
    <!--SpringJDBC和事务的核心依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.3.28</version>
    </dependency>
    <!--测试单元的核心依赖-->
   	<dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.13</version>
       <scope>test</scope>
    </dependency> 

1.2 编写数据库信息的资源文件

db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/trs-db?useUnicode=true&amp;characterEncoding=utf8&amp;tinyInt1isBit=false&amp;useSSL=false&amp;serverTimezone=GMT%2B8&amp;allowMultiQueries=true&amp;zeroDateTimeBehavior=convertToNull
db.username=root
db.password=rootxq

1.3 编写Spring的核心配置文件

<?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 https://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:db.properties"></context:property-placeholder>

    <!--装配数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driver}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>

    <!--装配JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

1.4 常用API的实践

  • 通用的增删改方法:jdbcTemplate.update(Spring sql,Object…args)
  • 通用的批处理增删改方法:jdbcTemplate.batchUpdate(String sql,List args)
  • 查询单个值:jdbcTemplate.queryForObject(String sql,Class clazz,Object…args)
  • 查询单个对象:jdbcTemplate.queryForObject(String sql,RowMapper rm,Object … args)
  • 查询多个对象:jdbcTemplate.query(String sql,RowMapper rm,Object … args)
  ClassPathXmlApplicationContext applicationContext = null;
    @Before
    public void before(){
        applicationContext = new ClassPathXmlApplicationContext("spring-ioc-jdbc.xml");
    }

    @Test
    public void queryForObject(){
        //获取jdbcTemplate对象
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);

        // 查询单个值
        Long count = jdbcTemplate.queryForObject("select count(1) from users", Long.class);
        System.out.println("查询结果:" + count);


        //查询单个实体(实体属性与数据库字段名称一致)
        User user1 = jdbcTemplate.queryForObject("select id ,u_name as name,salary from users where id = 1", new BeanPropertyRowMapper<>(User.class));
        System.out.println(user1);

        //查询单个实体(实体属性与数据库字段名称不一致)
        User user2 = jdbcTemplate.queryForObject("select * from users where id = 2", (RowMapper<User>) (rs, rowNum) -> {
            User u = new User();
            u.setId(rs.getInt("id"));
            u.setName(rs.getString("u_name"));
            u.setSalary(rs.getDouble("salary"));
            return u;
        });
        System.out.println(user2);

        //查询列表(实体属性与数据库字段名称一致)
        List<User> users = jdbcTemplate.query("select id ,u_name as name,salary from users", new BeanPropertyRowMapper<>(User.class));
        System.out.println(users);

        //查询列表(实体属性与数据库字段名称不一致)
        List<User> users2 = jdbcTemplate.query("select * from users", (RowMapper<User>) (rs, rowNum) -> {
            User u = new User();
            u.setId(rs.getInt("id"));
            u.setName(rs.getString("u_name"));
            u.setSalary(rs.getDouble("salary"));
            return u;
        });
        System.out.println(users2);
    }

二、声明式事务

  • 事务:是把一组业务当成一个业务来做,要么全都成功,要么全都失败,是保证业务操作完整性的一种数据库机制。

  • 事务的ACID四大特性:

    • 原子性:在一组业务所有的操作步骤,要么全都成功,要么全都失败;
    • 一致性:事务执行前后,要保证数据一致性,例如,两个人转账,转账前后他们的账户总额应该是不变的。
    • 隔离性:在多个事务并发执行时,每个事务之间都是独立的,互不影响。
    • 持久性:事务一旦执行成功,对数据的影响是不可逆的、永久性的。
  • 事务分为编程式事务声明式事务两种:

    • 编程式事务:
      • 指由用户自己通过代码来控制事务的处理过程,需要在代码中显式调用开启、提交、回滚事务等方法,可以使用TransactionTemplate来实现。
    • 声明式事务:
      • 在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
      • 其本质就是通过AOP的功能,对方法前后进行拦截,将事务处理的逻辑编织在拦截的方法中。也就是在目标方法开始之前加入一个事务,然后根据目标方法的执行情况来提交或者回滚事务。

1、声明式事务的配置

  • Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员可以通过配置的方式进行事务管理。
  • Spring的事务管理器是PlatformTransactionManager,它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是不可缺少的。

1.1 在配置文件中添加事务管理器

<?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 https://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:db.properties"></context:property-placeholder>

    <!--装配数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driver}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

</beans>

1.2 添加事务注解

  • 在需要进行事务管理的方法或者类上添加事务注解:@Transactional
  • 用法
    • 可以标记在类上面,表示当前类中的所有方法都将被事务管理;
    • 可以标记在某个方法上,表示当前方法被事务管理;
    • 可以在类和方法上面同时标记:
      • 如果类和方法上都存在@Transactional,则会以方法上面的为准。
      • 如果方法上面没有@Transactional,则会以类上面的为准。
  • 建议
    • @Transactional写在方法上面,控制粒度更细。
    • @Transactional写在业务逻辑层上,因为只有业务逻辑层才会有嵌套调用的情况。

1.3 设置事务属性

1.3.1 设置事务隔离级别
  • 设置事务隔离级别是为了解决并发事务过程中产生的一些问题,例如:脏读、幻读、不可重复读等等。

  • 常见的事务问题

    • 脏读:一个事务读取到了另一个事务没有提交的数据,称为脏读;
      • 例如:张三的工资到位了,总共1000元。他和他的媳妇儿同时收到了短信。事务A:表示他的媳妇儿从中银行卡取了200元,此时钱还没到手,并发事务B:张三立马去查询银行卡的余额信息,发现只有800元。而事务A在操作过程中出现了一些问题取钱失败,事务A回滚。实际上,银行卡的余额还是1000元,事务B读取到的就是一个脏数据。
    • 不可重复读:一个事务前后多次读取同一行记录,但读取到的内容却不一样,称为不可重复读。因为并发事务对这行记录做了变更,所以前后读到的内容不同。
      • 例如:张三的工资到位了,总共1000元。他和他媳妇儿同时收到了短信。事务A表示张三查询账户余额,事务B表示他媳妇儿查看账户并取钱。他们两同时查看账户信息,但是张三略微快一点点先看到余额是 1000元,他媳妇儿紧接着就取了200元,此时事务A还想再确认一下,就又看看了账户余额,发现是 800元。事务A前后两次看到的余额不一致。
    • 幻读:一个事务前后多次读取整张表的数据,发现读取前后的内容多了几行或者少了几行,就好像发生幻觉了一样,称为幻读。
      • 例如:张三公司要统计上个月给每个人发的工资情况,A事务表示人事部门统计薪资发放表,B事务表示财务部统计薪资发放表。两个部门同时统计,但是人事部门略微先统计完,统计结果为5000元,而财务部此时发现有个人漏发了,就补发了1000元,财务部拿到的结果是6000元。当事务A再次统计时,发现变成了6000元,前后统计到的结果不同。
  • 事务隔离级别:

    • DEFAULT:它是 PlatfromTransactionManager 默认的隔离级别。通常是数据库默认的隔离级别(REPEATABLE_READ)
      • @Transactional(isolation = Isolation.DEFAULT)
    • READ_UNCOMMITTED【读未提交】,最低的隔离级别。它允许一个事务读取另一个事务未提交的数据。会导致脏读、不可重复读和幻读的问题。
      • @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    • READ_COMMITTED【读已提交】,保证一个事务只能读取其他事务已经提交的数据,不能读取到未提交的数据。避免了脏读,但是仍然会有不可重复读、幻读的问题。
      • @Transactional(isolation = Isolation.READ_COMMITTED)
    • REPEATABLE_READ【可重复读】,这种隔离级别保证一个事务在执行期间,其他事务不能对这个事务所操作的行记录做修改,从而保证这个事务多次读取到的内容是一致的。实际上:就是用了一个行锁将这行记录给锁起来了。
      • @Transactional(isolation = Isolation.REPEATABLE_READ)
    • SERIALIZABLE【串行化】,最高的隔离级别。它通过强制事务串行执行,避免了所有可能的并发问题,但也最大限度地降低了系统的并发能力。
      • 确保事务A可以多次从一个表中读取到相同的行,在事务A执行期间禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。实际上:就是用表锁把整个表都给锁了。
      • @Transactional(isolation = Isolation.SERIALIZABLE)
1.3.2 设置事务传播行为

事务的传播特性指的是当一个事务方法被另一个事务方法调用(事务方法嵌套)时,需要指定事务应该如何传播。

	 /**
     * 转账
     */
    @Transactional
    public void transferMoney(){
        //扣款
        subMoney();
        //记录转账流水日志
        addLog();
        //加款
        addMoney();
    }
    
    @Transactional
    public void subMoney(){
        System.out.println("扣除转账人的银行账户1000元");
    }
    
    public void addLog(){
        System.out.println("记录转账的银行交易流水日志");
    }

    @Transactional
    public void addMoney(){
        System.out.println("收款人银行账户加款1000元");
    }

此时,所有的方法都有事务,每个事务方法执行时事务应该怎么来控制?

  • 事务的7种事务传播行为:
    • REQUIRED(默认)
      • 如果外部不存在事务,就开启一个自己的新事务。如果外部存在事务,就融合到外部的事务中(以外部事务为准)。
      • @Transactional(propagation = Propagation.REQUIRED)
      • 适用于增删改查操作。
    • SUPPORTS
      • 如果外部不存在事务,就不开启事务。如果外部存在事务,就融合到外部的事务中。
      • @Transactional(propagation = Propagation.SUPPORTS)
      • 适用于查询操作。
    • REQUIRES_NEW
      • 如果外部不存在事务,就开启一个自己的新事务。如果外部存在事务,就将外部事务挂起(外部事务暂不执行),创建一个自己的新事务去执行。
      • @Transactional(propagation = Propagation.REQUIRES_NEW)
      • 适用于内部事务,例如:记录日志时,无论其他方法执行成功失败,都需要记录日志,而不是回滚,这时候,记录日志的方法就需要玩自己的事务,而不跟着外部事务走。
      • 注意:当涉及到事务挂起时,要求外部方法和内部方法必须存在不同的类中;
    • NOT_SUPPORTED
      • 如果外部不存在事务,也不会开启自己的新事务。如果外部存在事务,则挂起外部事务。
      • @Transactional(propagation = Propagation.NOT_SUPPORTED)
    • NEVER
      • 如果外部不存在事务,也不会开启自己的新事务。如果外部存在事务,则抛出异常。
      • @Transactional(propagation = Propagation.NEVER)
    • MANDATORY
      • 如果外部不存在事务,则抛出异常。如果外部存在事务,就融合到外部的事务中。
      • @Transactional(propagation = Propagation.MANDATORY)
    • NESTED
      • 如果外部不存在事务,则执行与REQUIRED类似的操作。如果外部存在事务,就融合到外部事务中,以嵌套事务的方式执行。
      • @Transactional(propagation = Propagation.NESTED)
1.3.3 设置超时属性
  • 指定事务等待的最长时间(秒)
  • 当前事务访问数据时,有可能访问的数据被别的事务进行了加锁的处理,那么此时事务就
    必须等待,如果等待时间过长给用户造成的体验感差。
  • @Transactional(timeout = 5)
1.3.4 设置事务只读
  • 一般事务方法中只有查询操作时才将事务设置为只读;
  • 当将事务设置只读 就必须要要求你的业务方法中没有增删改的操作。由于只
    读事务不存在数据的修改,数据库将会为只读事务提供一些优化手段。
  • 默认值:readonly = false
  • @Transactional(readOnly = true)
1.3.4 设置事务异常回滚
  • 设置当前事务出现的那些异常就进行回滚或者提交。
  • 默认对于RuntimeException 及其子类 采用的是回滚的策略。
  • 默认对于Exception 及其子类 采用的是提交的策略。
  • rollbackFor:设置发生异常时需要回滚的异常类型
    • @Transactional(rollbackFor = NullPointerException.class)
  • noRollbackkFor:设置发生异常时不需要回滚的异常类型
    • @Transactional(noRollbackkFor = NullPointerException.class)

2、基于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 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">

    <!--如果基于注解和XML的事务配置都存在,则会以注解的为主-->
    <!--基于XML的事务配置-->
    <!--用于声明事务切入的所有方法-->
    <aop:config>
        <aop:pointcut id="transactionCutPoint" expression="execution(* org.example.mvc.impl.*.*(..))"/>
    </aop:config>
    <!--用来明确切点匹配到的哪些方法需要使用事务-->
    <tx:advice>
        <tx:attributes>
            <!--可以使用通配符-->
            <tx:method name="add*"/>
            <tx:method name="update*" timeout="5" isolation="REPEATABLE_READ"/>
            <tx:method name="query*" propagation="SUPPORTS"/>
            <tx:method name="delete*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

</beans>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值