上节简单记录了Spring整合mybatis,这节记录Spring整合事务的开发,持久层这块学的太菜了,基于事务的开发我只会用注解,比如:

@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)

具体里面的细节,比如说事务中的参数定义(也就是事务属性)、使用IOC+AOP来做事务开发等等都不了解。接下来逐一分析一下:

一、关于事务

事务的原理:数据库事务的概念及其实现原理

事务的概念就是保证业务操作完整性的一种数据库机制,对应的四个特性是ACID,分别是原子性、一致性、隔离性、持久性。不同的持久层开发框架整合事务的方式也不同,比如:

JDBC:
 Connection.setAutoCommit(false);
 Connection.commit();
 Connection.rollback();
Mybatis:
 Mybatis⾃动开启事务

 sqlSession(Connection).commit();
 sqlSession(Connection).rollback();

调用方式虽有不同,但可以看到事务 最主要的就是三个方法:1、事务的创建 2、事务的提交 3、事务的回滚。而且控制事务的底层,都是由Connection对象来完成。

二、Spring整合事务开发

前几节写AOP时也有记到,事务是可以通过额外功能实现的,那么就一定可以被整合到Spring,满足AOP开发的四个步骤即可:

1、创建原始对象,交由工厂管理

这步没啥好说的,基本是创建在Service层,再注入Dao对象

public class XXXUserServiceImpl{
 private xxxDAO xxxDAO
 set get
 1. 原始对象 ---》 原始⽅法 ---》核⼼功能 (业务处理+DAO调⽤)
 2. DAO作为Service的成员变量,依赖注⼊的⽅式进⾏赋值
}

2、写额外功能

这里的额外功能自然就是事务的编写了,通过 org.springframework.jdbc包,工厂创建一个事务管理器对象即可,然后在里面注入连接池对象,然后将该对象和切入点做个切面就可以了(为啥没看到事务的创建、提交、回滚方法,问就是封装好了),创建及实现方式:

创建
1. org.springframework.jdbc.datasource.DataSourceTransactionManager
2. 注⼊DataSource
实现
MethodInterceptor或 @Aspect注解

3、切入点

@Transactional
后面会看到以标签方式开发切入点
事务的额外功能加⼊给那些业务⽅法。
1. 类上:类中所有的⽅法都会加⼊事务
2. ⽅法上:这个⽅法会加⼊事务

4、组装切面

引入标签自动扫描@Transactional注解 为扫描到的切入点加入额外功能
<tx:annotation-driven transaction-manager=""/>

代码测试:

引依赖

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-tx</artifactId>
 <version>5.1.14.RELEASE</version>
</dependency>

潦草的实现类:

package com.jin.service;

import com.jin.dao.UserDAO;
import com.jin.entity.User;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author jinyunlong
 * @date 2021/12/14 10:19
 * @profession ICBC锅炉房保安
 */
//@Transactional(isolation = Isolation.SERIALIZABLE)
//@Transactional(timeout = 2)
@Transactional
public class UserServiceImpl implements UserService{
    private UserDAO userDAO;

    public UserDAO getUserDAO() {
        return userDAO;
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @Override
    public void register(User user) {
//        try {
//            Thread.currentThread().sleep(3000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        userDAO.save(user);
//        throw new RuntimeException("测试");
    }

    @Override
//    @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
    public void login(String name, String password) {

    }
}

配置文件,连接池对象引用上节:

    <bean id="userService" class="com.jin.service.UserServiceImpl">
        <property name="userDAO" ref="userDAO" />
    </bean>

<!--    DataSourceTransactionManager-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
    /*
      用于测试:Spring的事务处理
     */
    @Test
    public void test2(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserService userService = (UserService) ctx.getBean("userService");

        User user = new User();
        user.setName("fuck78");
        user.setPassword("hgjgm");
        userService.register(user);
    }

向表中插入该实体:

14.PNG

三、事务的属性

5种属性,隔离属性、传播属性、只读属性、超时属性、异常属性。原以为自己没用过,其实是都用的默认值。不在@Transactional中加参数即可,加参数写法:

@Transactional(isloation=,propagation=,readOnly=,timeout=,rollbackFor=,
noRollbackFor=,)

有些属性在注解里不声明值的话,那么默认值就是由数据库控制的,mysql有其默认值,oracle同理。

在开发中,相当通用的一套声明方案如下:

1. 隔离属性 默认值
2. 传播属性 Required(默认值) 增删改 Supports 查询操作
3. 只读属性 readOnly false 增删改 true 查询操作
4. 超时属性 默认值 -1
5. 异常属性 默认值
增删改操作 @Transactional
查询操作
@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)

可见,增删改全默认,查询操作修改了传播属性和只读属性,那这些修改值都是个啥意思呢,下面详细看下:

隔离属性,概念很容易乱,因为可能会出现的问题不同,可能会出现脏读、不可重复读、幻影读。三种问题对表操作的影响程度不同。简单来说如果注解用隔离属性默认值的话就是加了一把行锁(mysql),其实用默认值就能解决绝大部分问题了,本质是解决并发。

1、脏读
⼀个事务,读取了另⼀个事务中没有提交的数据。会在本事务中产⽣数据不⼀致的问题
解决⽅案 @Transactional(isolation=Isolation.READ_COMMITTED)

2、不可重复读
⼀个事务中,多次读取相同的数据,但是读取结果不⼀样。会在本事务中产⽣数据不⼀致的问题
注意:1 不是脏读 2 ⼀个事务中
解决⽅案 @Transactional(isolation=Isolation.REPEATABLE_READ)
本质: ⼀把⾏锁

3、幻影读
⼀个事务中,多次对整表进⾏查询统计,但是结果不⼀样,会在本事务中产⽣数据不⼀致的问题
解决⽅案 @Transactional(isolation=Isolation.SERIALIZABLE)
本质:表锁
并发安全: SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
运⾏效率: READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE

传播属性不想多记,记多了反倒更容易乱:

增删改 ⽅法:直接使⽤默认值REQUIRED
查询 操作:显示指定传播属性的值为SUPPORTS (外部不存在事务时不开启事务——就把查询当非事务处理就行,不用管ACID...)

墙裂推荐一个解析事务传播的文章,这是干什么这太清晰了吧(PROPAGATION_REQUIRED): Spring事务传播行为

只读属性,提升效率的:

针对于只进⾏查询操作的业务⽅法,可以加⼊只读属性,提供运⾏效率
默认值:false

超时属性,比较简单:

指定了事务等待的最⻓时间
1. 为什么事务进⾏等待?
 当前事务访问数据时,有可能访问的数据被别的事务进⾏加锁的处理,那么此时本事务就必须
进⾏等待。
2. 等待时间 秒
3. 如何应⽤ @Transactional(timeout=2)
4. 超时属性的默认值 -1
 最终由对应的数据库来指定

异常属性,比较简单:

Spring事务处理过程中
默认 对于RuntimeException及其⼦类 采⽤的是回滚的策略
默认 对于Exception及其⼦类 采⽤的是提交的策略
rollbackFor = {java.lang.Exception,xxx,xxx}
noRollbackFor = {java.lang.RuntimeException,xxx,xx}
@Transactional(rollbackFor = {java.lang.Exception.class},noRollbackFor
= {java.lang.RuntimeException.class})
建议:实战中使⽤RuntimeExceptin及其⼦类 使⽤事务异常属性的默认值
四、使用标签进行事务开发

这次不用@Transactional注解了。因为使用注解就相当于找了切入点,且组装切面需要去寻找该注解并且组装额外功能。那么如果不用注解了,那么基于AOP的第3、4步就要用标签来代替了,如下:

基于标签定义属性值
    <tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="register" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

<aop:config>
        <aop:pointcut id="pc" expression="execution(* com.jin.service.UserServiceImpl.register(..))"/>
        <aop:advisor advice-ref="txadvice" pointcut-ref="pc"/>
    </aop:config>

测试方法和二一样,工厂可以换个xml加载,实体插入表:

15.PNG

这里的开发习惯,不同方法中加入事务属性使用通配方式,一般用包切入点。

<!--<!–    通配方式–>-->
<!--<!–    编程习惯 service中负责增删改操作的方法  都以modify开头    查询操作 命名无所谓 例–>-->
<!--    <tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">-->
<!--        <tx:attributes>-->
<!--            <tx:method name="modify*" />-->
<!--            <tx:method name="*" propagation="SUPPORTS" read-only="true" />-->
<!--        </tx:attributes>-->
<!--    </tx:advice>-->
<!--<!–    应用过程中,一般用包切入点,service放置到service包中–>-->
<!--    <aop:config>-->
<!--        <aop:pointcut id="pc" expression="execution(* com.jin.service.*.*(..))"/>-->
<!--        <aop:advisor advice-ref="txadvice" pointcut-ref="pc"/>-->
<!--    </aop:config>-->

持久化以前这块学的真不好啊,前有脚本小子,后有事务菜鸡。


标题:Spring(7)
作者:jyl
地址:http://jinyunlong.xyz/articles/2021/12/16/1639641914931.html