Spring事务处理

1、什么是事务?

保证业务操作完整性的一种数据库机制(事务由数据库控制,java代码只是对这种事务的调用)

事务的四个特点

  1. A(原子性)
  2. C(一致性)
  3. I(隔离性)
  4. D(持久性)

如何控制事务

采用不同的持久化技术,控制事务的方式是不同的

  1. jdbc
  • Connection.setAutoCommit(false)`
  • Connection.commit();
  • Connection.rollback();
  1. Mybatis:
  • Mybatis自动开启事务,你只要关心事务提交和回滚
  • sqlSession.commit();
  • sqlSession.rollback();
    mybatis在事务提交时底层也是封装了Connection对象。

结论:无论是jdbc,还是mybatis、控制事务的底层都是通过Connection对象来完成事务的。

Spring如何控制事务

Spring是通过aop的方式进行事务开发的。
所以aop开发的四个步骤如下:

原始对象
一个一个service,因为我们的事务是加在service层的。

public class XXXUserServiceImpl(){
	private xxxDAO xxxDAO
	1.原始对象--->原始方法---->核心功能(业务处理+DAO调用)
	2.DAO作为Service的成员变量,依赖注入的方式进行赋值
	
}

额外功能(这里是环绕通知)

  //环绕通知
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("原始方法运行之前 开启事务--------------------");
        Object obj = joinPoint.proceed();//执行我们的原始方法,它会返回一个Object
        System.out.println("原始方法运行之后 提交事务 --------------------");
        return obj;
    }

但是如果元素代码抛异常我们需要回滚事务,该怎么做,把它们放在try-cath中就行

  //环绕通知
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    try{
		 System.out.println("原始方法运行之前 开启事务--------------------");
        Object obj = joinPoint.proceed();//执行我们的原始方法,它会返回一个Object
        System.out.println("原始方法运行之后 提交事务 --------------------");
	}catch(Exception e){
	}
       //回滚事务
    }
    return obj;
 }

这个代码,尽然你我都能想得到,spring自然想得到,所以spring尽然知道你写、我写的都一样,那我spring框架给你封装不就行了。spring就封装了一个类:org.springframework.jdbc.datasource.DataSourceTransactionManager 但是控制事务都是由连接对象Connection完成的,所以DataSourceTransactionManager就需要通过连接对象配合他来完成,所以在用DataSourceTransactionManager时,需要为其注入连接,但是我们为了提高创建连接的效率,我们提供了连接池,所以等效于DataSourceTransactionManager需要注入连接池。
所以在应用的过程中:
1、使用DataSourceTransactionManager类
2、注入连接池
切入点
只需要应用注解@Transactional这个注解来指定事务这个额外功能加给哪些事务方法:
该注解可以加在两个位置上

  1. 类上:类中所有的方法都会加入事务
  2. 方法上:这个方法加入事务

组装切面
切面有两部分组成:1、切入点 2、额外功能
在spring进行事务的控制过程中,它的组装切面是通过标签来进行的。这个标签是txt:annotation-driven tracscation-managers="(获取第二步中定义的额外功能) " 切入点该标签会自动的扫描你标识的@transactional这个注解来识别

2、Spring控制事务的编码?

开发环境

· 首先,导入spring框架的基本开发包,一共5个
在这里插入图片描述
-然后,导入mysql数据库驱动jar包
在这里插入图片描述

  • 接着,咱要在程序中使用jdbcTemplate模板类,所以还需要导入如下jar包。
    在这里插入图片描述
    其中,spring-tx-5.2.6.RELEASE.jar就是Spring用于事务管理时使用到的jar包。
  • 紧接着,还要在程序中使用德鲁伊连接池,所以导入德鲁伊连接池所需的jar包
    在这里插入图片描述
  • 在接着,由于咱们接下来会使用到Spring声明式事务管理这种方式来管理事务,所以还得导入Spring AOP开发的jar包。
    在这里插入图片描述
    在这里插入图片描述

事务案列

数据库

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;


在上面导入过jar包的module中新建com.atguigu.dao、com.atguigu.daoimpl、com.atguigu.service、com.atguigu.serviceimpl、com.atguigu.pojo、com.atguigu.test包。在com.atguigu.dao新建抽象类UserDao,抽象类中有一个抽象方法save()

public interface UserDao {

    void save(User user);
}
``
在com.atguigu.daoimpl包中新建一个类叫UserDaoImpl,该类实现接口UserDao,并重写save()

```java
public class UserDaoImpl implements UserDao {

    //获取jdbcTemplate对象
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void save(User user) {
        //1 创建sql语句
        String sql="insert into t_user values(?,?,?)";
        int update=jdbcTemplate.update(sql,user.getId(),user.getUserName(),user.getPassword());
        System.out.println(update);
    }
}

在com.atguigu.service,新建抽象类UserService,里面有一个抽象方法 register()

public interface UserService {

    public void register(User user);
}

在com.atguigu.serviceImpl中创建类UserServiceImpl类,实现com.atguigu.service中UserService抽象类的抽象方法register()

//元素对象(核心功能:业务运算+调用dao)
    @Transactional//所有方法都有了这个事务
public class UserServiceImpl implements UserService {

    private UserDao userDao;//此时dao还是空的,需要在spring文件中进行赋值


    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void register(User user) {
        userDao.save(user);
    }
}

spring配置文件的配置:
jdbcTemplate的配置见这里,这里不再给出。
根据aop的四个步骤:
1、创建原始对象

<!--1、创建原始对象-->
    <bean id="userService" class="com.atguigu.serviceimpl.UserServiceImpl">
    	<!--给service里的dao层对象赋值-->
        <property name="userDao" ref="userDao"></property>
    </bean>

    <bean id="userDao" class="com.atguigu.daoimpl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

2、添加额外功能(创建事务管理器)

 <!--2、添加额外功能:创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

3、切入点:在类上加transactional注解

在这里插入图片描述
4、组装切面
这一步需要引入名称空间(第一个名称空间是引入数据库相关的外部文件的):
在这里插入图片描述

<!--4、组装切面:
        transaction-manager="transactionManager":关联额外功能
        切入点会自动扫描注解
    -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在com.atguigu.test创建一个类TransactionTest类,里面创建test1()的单元测试方法

  @Test
    public void test1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        //返回的是UserService的代理类对象
        UserService userService = context.getBean("userService", UserService.class);
        userService.register(new User(null,"shou","123456"));
    }

运行之后,数据库多一条记录
在这里插入图片描述
至此,我们也不知道事务添加了事务,所以下面验证。
验证事务是否添加
修改com.atguigu.serviceimpl包中的UserServiceImpl类
,把该类中的register方法修改为如下:

//元素对象(核心功能:业务运算+调用dao)
    @Transactional//所有方法都有了这个事务
public class UserServiceImpl implements UserService {

    private UserDao userDao;//此时dao还是空的,需要在spring文件中进行赋值


    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void register(User user) {
        userDao.save(user);
        throw new RuntimeException("这里有异常");
    }
}

我们给register()加上了事务,里面并添加了一个异常,查看添加到数据库是否成功,来查看我们添加的事务是否成功。
先把上面的数据库清空:
在这里插入图片描述

运行刚才单元测试方法:
控制台:
在这里插入图片描述
数据库
在这里插入图片描述
细节:
在这里插入图片描述

事务属性(Transaction Attribute)

1.什么是事务属性

属性:描述物体特征的一系列值(人:性别、身高、体重。。。)
事务属性包括

  1. 隔离属性
  2. 传播属性
  3. 只读属性
  4. 超时属性
  5. 异常属性

2.如何添加事务属性

语法

如何添加一个隔离属性:
@Transactional(isolation=,)
再添加一个传播属性:
@Transactional(isolation=,propagation=)

在异常属性上有两个值:
@Transactional(isolation=,propagation=,readonly=,timeout=,rollbackFors或者noRollbackFors)
@Transactional注解只能加在公共方法上·

隔离属性

概念:它描述了事务解决并发问题的特征。
引出3个问题:
1、什么是并发?多个事务(用户)同一时间,访问操作了相同的数据。同一时间:也并不是刚好相同时间(比如:0.000几秒,是一个非常小的时间)
2、并发会产生哪些问题

  • 脏读
  • 不可重复读
  • 幻读
    3、并发问题的解决—隔离属性:隔离属性中设置不同的值,解决并发处理过程中的问题(本质都是加锁)。
    脏读
    一个事务,读取了另一个事务中没有提交的数据,会在本事务中产生数据不一致的问题。
    你是否会问,事务而怎么会读取到事务一中没有提交的数据呢?

在没有正式提交之前,数据是先更新到日志之中的,只有commit提交之后,才真正再从日志中更新到mysql表中。你不用去纠结为什么能读到它。你只需知道,是事务与事务并发进行时,是具有影响性的,这种影响性的大小被称为隔离级别。那怎么防止未提交读呢?就是加写锁,然后提交之后释放锁,在这个时候,mysql的隔离级別就变成了已提交读。而加锁,释放锁是由mysql自动完成的,你无需关心。所以,其实本质上是锁的应用,最终导致了不同的隔离级别,不同的隔离级别,锁的应用也不同。

解决方案:·@Transaction(isolation=Isolation.READ_COMMITTED)·
不可重复读
一个事务中,多次读取相同的数据,但是读取结果不一样,会咋本事务中产生数据不一致的问题
注意:1 不是脏读 2 一个事务中
解决方案:@Transaction(isolation=Isolation.REPEATABLE_READ)
本质:行锁
幻读
一个事务中,多次对整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致的问题。
解决方案:@Transaction(isolation=Isolation.SERIALIZABLE)
本质:表锁
总结:并发安全 SERIALIZABLE–>REPEATABLE_READ–>READ_COMMITTED
运行效率:READ_COMMITTED–》REPEATABLE_READ—》SERIALIZABLE

数据库对隔离属性的支持

在这里插入图片描述
spring使用的是数据库的默认隔离属性:
mysql—》REPEATABLE_READ
oracle—>READ_COMMITTED
推荐使用Spring指定的隔离级别 ,因为未来中的实战中,并发访问情况很低。
如果遇到并发问题,乐观锁。

传播属性(PROPAGATION)

传播属性的概念:它描述了事务解决嵌套问题的特征。
什么叫做事务的嵌套:事务直接的包含关系:
传播属性的核心:在同一时间只有同一个事务。
在这里插入图片描述
什么情况下会可能出现事务嵌套呢?
service调用service的情况下,有可能事务嵌套,如下。
在这里插入图片描述
问题:大事务中融合了很多小的事务,它们彼此影响,最终会导致外部大的事务,丧失了原子性。—解决:传播属性在这里插入图片描述
传播属性的值及其用法:

1、REQUIERD属性:
先看下面三个业务类
在这里插入图片描述
下面是AService中的aMethod方法调用了另外两个service中的两个方法。给aMethod()、bMethod()、cMethod()都加上传播属性:
在这里插入图片描述
对应aMethod由于该事务的属性是:Propagation_REQUIRED,所以aMethod外部不存在事务,所以开启新的事务,对于b和c的属性也是Propagation_REQUIRED,而b和c它们外部已经有a事务了,所以b和c融合到a事务中—体现了传播属性的核心:同一时间只有同一个事务。
在实战中的应用:增、删、改方法中用该属性。(在增删改中加了该事务,无论是融合了还是保留自己的事务,它都有事务)
在这里插入图片描述
2、SUPPORTS属性:
在这里插入图片描述
在实战中,这个属性会应用在查询的业务方法中,因为查询的业务方法,它不需要事务。如果独立应用,像上述的amethod,他就不开了,如果它被别人调用,反正我不用事务,我可以配合外部的事务,融合到外部事务中,如上图第二种情况。

以上两个传播属性解决了我们为来开发中99%的属性设置,因为REQUIRED解决了增、删、改,SUPPORTS解决了查询,而我们大多业务就是增删改。

2、REQUERS_NEW属性:

在这里插入图片描述
所以该属性一般是:日志记录的方法中使用。
另外三个属性,99%在开发中及其不常用,看下表的总结,了解一下即可。

总结
在这里插入图片描述
上表中只有前两个用的最多,第三个都不多,后面的三个更少。
REQUIRED是传播属性的默认值
推荐传播属性的使用方式
增、删、该 方法:直接使用默认值REQUIRED
查询操作:使用SUPPORTS

只读属性(readOnly)

针对于只进行查询操作的业务方法,可以加入只读属性,提高运行效率。(加上事务为了保证并发安全,我们就要加各种各样的锁,显然这个锁会把并行操作变成串行操作,当加上了只读属性后,就不会在加额外的锁了)

//元素对象(核心功能:业务运算+调用dao)
    @Transactional//所有方法都有了这个事务
public class UserServiceImpl implements UserService {

    private UserDao userDao;//此时dao还是空的,需要在spring文件中进行赋值


    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void register(User user) {
        userDao.save(user);
        throw new RuntimeException("这里有异常");
    }

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

    }
}

因为,我们在类上加了 @Transactional,所以每个方法都会加上,又因为每个属性都会使用默认的方法(对于隔离属性就是isolation = Isolation.REPEATABLE_READ、对于传播属性是:Propagation.REQUIRED),而我们的login方法对于隔离属性无所谓,默认的就行,对于传播属性(因为是查询),所以应该使用传播属性的SUPPORTS,而对于查询而言可以增加只读属性提高查询效率,所以我们在方法上加入 @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)进行设置,可以覆盖掉类上加的@Transactional的默认属性值。

只读属性的默认值是:false

超时属性(tiemout)

概念:指定了事务等待的最长时间
1.为什么事务运行等待?
当前事务访问数据时,有可能访问的数据被别的事务进行加锁处理,那么此时本事务就必须进行等待。
2.等待的时间以秒为单位
在这里插入图片描述
3、如何应用@Transactional(timeout=2)
代码中的演示:

//元素对象(核心功能:业务运算+调用dao)
    @Transactional(timeout = 2)//所有方法都有了这个事务
public class UserServiceImpl implements UserService {

    private UserDao userDao;//此时dao还是空的,需要在spring文件中进行赋值


    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void register(User user) {

        try {
            //设置等待3s
            Thread.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) {

    }
}

在这里插入图片描述
4.超时属性的默认值-1(最终由对应的数据库来指定)
5.开发中很少自己指定,一般使用默认值

异常属性(tiemout)

对于如下的类:

//元素对象(核心功能:业务运算+调用dao)
    @Transactional()//所有方法都有了这个事务
public class UserServiceImpl implements UserService {

    private UserDao userDao;//此时dao还是空的,需要在spring文件中进行赋值


    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void register(User user) {

        try {
            //设置等待3s
            Thread.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) {

    }
}

因为我们手动抛出了异常,事务会进行回滚
测试之前先看看数据库:
在这里插入图片描述
下面运行测试方法:
运行之后的数据库:
在这里插入图片描述
说明事务进行了回滚操作。
如果把throw new RuntimeException(“这里有异常”),改为
throw new Exception("这有异常呦!");,就会发生提交。

//元素对象(核心功能:业务运算+调用dao)
    @Transactional()//所有方法都有了这个事务
public class UserServiceImpl implements UserService {

    private UserDao userDao;//此时dao还是空的,需要在spring文件中进行赋值


    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void register(User user) throws Exception {

        try {
            //设置等待3s
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        userDao.save(user);
        //throw new RuntimeException("这里有异常");
        throw new Exception("这有异常呦!");
    }

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

    }
}

因为我们的register方法抛出了异常,所以Service方法中的抽象方法register也要抛出异常。
准备工作就绪,现在我们在运行测试方法,发现有了数据,说明事务进行了提交。
在这里插入图片描述
spring事务处理过程中
默认—对于RuntimeException及其子类,采用的是回滚的策略
默认—对于Exception以及子类,采用的是提交的策略。
那对于RuntimeException及其子类能不能进行提交的策略?
对于Exception及其子类能不能采用回滚的策略?
使用:
rollbackFor={java.lang.Exception.class,xxx,xxx}//大括号是个数组,里面可以定义多个异常,遇到这些异常都会回滚,但是要加.class
spring的处理逻辑是

  1. spring框架会手写检查方法抛出的异常是不是在rollbackFor的属性值中,如果异常在rollbackFor列表中,不管是RuntimeException异常还是Exception异常,一定回滚。
  2. 如果你的抛出异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,如果是一定回滚。
    noRollbackFor={{java.lang.RuntimeException,xxx,xxx}};//大括号是个数组,里面可以定义多个异常,遇到这些异常都不回滚。
    使用如下:
    在这里插入图片描述
    实战中,我们很少按照上面的自己设置,建议:实战中使用RuntimeException及其子类,使用事务异常属性的默认值。

事务属性常见配置总结

  1. 隔离属性 默认值
  2. 传播属性 required(默认值)增删改 SUPPORTS 查询操作
  3. 只读属性 readOnly 默认值(false) false:增删改 true 查询操作
  4. 超时属性 默认值-1
  5. 异常属性 默认值

这个结论记住,以后直接写就行了。
增、删、改 操作 直接:@Transactional(即隔离属性默认值、传播属性默认值、readonly默认值、超时、异常也是默认值)
查询操作 @Transactional(propagation=Propagation.SUPPORTS,realOnly=true)

基于xml的事务配置方式(事务开发第二种形式)

上面采用的都是基于注解的方式,先来回顾四个步骤。

<!--1、创建原始对象-->
    <bean id="userService" class="com.atguigu.serviceimpl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

    <bean id="userDao" class="com.atguigu.daoimpl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!--2、添加额外功能:创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--3、切入点:在类上加transactional注解-->
    @Transactional(rollbackFor = {java.lang.Exception.class},noRollbackFor = {java.lang.RuntimeException.class})//所有方法都有了这个事务
public class UserServiceImpl implements UserService {}
    <!--4、组装切面:
        transaction-manager="transactionManager":关联额外功能
        切入点会自动扫描注解
    -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

基于xml的配置方式和基于注解的配置方式的区别仅仅在于第三步和第四步

<!--1、创建原始对象-->
    <bean id="userService" class="com.atguigu.serviceimpl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

    <bean id="userDao" class="com.atguigu.daoimpl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!--2、添加额外功能:创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
	    <!--3、事务属性
         一定要加tx为结尾的,上面beans里面会有          xmlns:tx="http://www.springframework.org/schema/tx"
         id:自定义名称,表示<tx:advice></tx:advice>之间的配置内容
         transaction-manager:事务管理器对象的id
         -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
       <!--tx:attributes:配置事务属性-->
        <tx:attributes>
        	<!--tx:method:给具体的方法配置事务属性,可以出现多次,分别给不同    的方法设置事务属性-->
            <tx:method name="register" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="login" isolation="DEFAULT" propagation="SUPPORTS"></tx:method>
            <!--可以为多个方法进行配置-->

	等效于: @Transactional(rollbackFor = {java.lang.Exception.class},noRollbackFor = {java.lang.RuntimeException.class})//所有方法都有了这个事务
public class UserServiceImpl implements UserService {}
	  
        </tx:attributes>
    </tx:advice>
<!--配置切入点和切面-->
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* com.atguigu.serviceimpl.UserServiceImpl.login(*,*))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
    </aop:config>

如果实战中过多的方法,那么就会有上述第三步,需要多个方法

<tx:method name="register" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="login" isolation="DEFAULT" propagation="SUPPORTS"></tx:method>

的冗余。

基于xml的事务配置在实战中的应用方式:
第一、二步一样,
第三步改为:
在这里插入图片描述
一般小型项目使用@Transactional注解,如果大型项目可以用上面的第三步的声明,大型项目方法那么多,不可能用注解一个一个的加上

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值