spring的事务操作(重点)

这篇文章一起来回顾复习下spring的事务操作.事务是spring的重点, 也是面试的必问知识点之一.

说来这次面试期间,也问到了我,由于平时用到的比较少,也没有关注过这一块的东西,所以回答的不是特别好,所以借这一篇文章来回顾总结一下,有需要的朋友,也可以点赞收藏一下,复习一下这方面的知识,为年后的面试做准备.

首先,了解一下什么是事务?

---

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。这里简单提一下事务的四个基本属性,

A(Atomic) 原子性

事务必须是原子工作单元;对于其[数据修改]事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行.

C(Consistent) 一致性

事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。

I(Insulation) 隔离性

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

D(Duration) 一致性

事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。

了解了事务之后,我们为什么要使用事务呢?换句话说,用事务是为了解决什么问题呢?

首先我们来看一个业务场景:Tom在书店买书,java和Oracle,2种书,单价都是100,库存量都是10本,Tom目前身上有150元.现在Tom买1本书的钱是足够的,ok,买起来,交易结束后,对于Tom来说,买到了1本书,还剩下50元.正好要出门时接到jack的电话,原来是jack要Tom帮他捎本java,他要用来复习,那接下来的交易是否可以正常进行呢?常识来说,50元买价值100元的东西肯定是买不到的,那我们看看程序中是什么情况?

首先,需要构建三张表,余额表,商品表,和商品库存表,如下:

然后定义接口如下:

public interface BookShopDao {
    /**
     *   根据书名获取书的单价
     */
    public  int findBookPriceByIsbn(String isbn);

    /**
     *   更新书的库存,使书号对应的库存-1
     */
    public  void  updateBookStock(String isbn);

    /**
     * 更新用户的余额:使username的balance-price
     * @param name
     * @param price
     */
    public  void updateUserAccount(String name,int price);

}
@Repository
public class BookShopDaoImpl implements  BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT  price FROM book WHERE isbn = ?";
        return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //检查书的库存是否足够,不足够则抛出异常
        String sql2 = "SELECT stock  FROM book_stock WHERE isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if(stock == 0){
            throw new BookStockException("库存不足");
        }
        String sql ="UPDATE book_stock SET  stock= stock-1 where isbn = ?";
        jdbcTemplate.update(sql,isbn);

    }

    @Override
    public void updateUserAccount(String name, int price) {
        //验证余额是否足够,不足则抛出异常
        String sql2 ="SELECT balance FROM account WHERE username = ?";
        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, name);
        if(balance <price) {
            throw  new UserAccountException("余额不足");
        }
        String sql = "UPDATE account SET balance = balance - ? WHERE  username = ?";
        jdbcTemplate.update(sql,price,name);

    }
}

上面代码中有点要注意:库存余量是否充足,余额是否充足,需要在代码中去自己判断,mysql不会帮我们加,例如,当库存数为0时,如果仍需要减1,值会变为-1,这不是我们想要的结果.

接下里定义一个service:

public interface BookShopService {
    /**
     * 购物方法
     * @param username
     * @param isbn
     */
    public void  purchase(String username,String isbn);
}
@Service
public class BookShopServiceImpl implements BookShopService{
    @Autowired
    private BookShopDao shopDao;
    /**
     * @param username
     * @param isbn
     */
    @Override
    public void purchase(String username, String isbn) {
        //1.获取书的单价
        int price = shopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        shopDao.updateBookStock(isbn);
        //更新余额
        shopDao.updateUserAccount(username,price);
    }
}

到此,基本购买流程都已经实现,我们来写一个测试方法测试一下购买的结果是什么?

由图中可以看出,程序报了"余额不足"的异常,tom的余额没有减少,但是书店的库存量却减少了,这明显是违反常理的,书店不会白白把书送给tom的,怎么办呢?事务就可以帮助我们解决这个难题.

这里要先了解下事务的分类:

  • 编程式事务

    将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务当中,必须在每个事务操作中包含额外的事务管理代码,繁琐,不便.

  • 声明式事务

    是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需通过基于@Transactional注解的方式或者配置文件中做相关的事务规则声明,便可以将事务规则应用到业务逻辑中。

采用声明式事务,基于@Transactional注解,首先看下配置文件:

<?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:component-scan base-package="com.springtest"></context:component-scan>
    <!--导入资源文件-->
    <context:property-placeholder location="classpath:db.properties" />
    <!--配置数据源-->
    <bean id="jdbcSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
    </bean>
    <!--配置spring的jdbctemplate模版-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="jdbcSource"></property>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="jdbcSource"></property>
    </bean>
    <!--启用事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

接下来给方法purchase()加上注解

    @Transactional()
    @Override
    public void purchase(String username, String isbn) {
        //1.获取书的单价
        int price = shopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        shopDao.updateBookStock(isbn);
        //更新余额
        shopDao.updateUserAccount(username,price);
    }

结果如下:

由图观之,异常出现之后,事务发生了回滚,库存不再减少,钱也不会再减少,结果正常.

拓展问题(面试):Q1: 假如此时BookShopServiceImpl中另外一个方法调用了purchase方法,那么在另外一个方法中,事务是否起作用呢?

Q2:假如此时另外一个类中方法调用了BookShopServiceImpl类中的purchase方法,那么事务又是否起作用呢?

我们来一一验证一下,首先Q1

@Service
public class BookShopServiceImpl implements BookShopService{
    @Autowired
    private BookShopDao shopDao;

    @Transactional()
    @Override
    public void purchase(String username, String isbn) {
        //1.获取书的单价
        int price = shopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        shopDao.updateBookStock(isbn);
        //更新余额
        shopDao.updateUserAccount(username,price);
    }

    @Override
    public void purchaseAgain(String username, String isbn) {
        purchase(username,isbn);
    }
}

测试结果:

测试前,数据库数据为:

结果观之,事务并没有起作用,原因是什么?

启用事务首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务.而类内部的自我调用将无法实施切面中的增强.,解决方案的话限于篇幅,以后再写,这里知道原因就可以了.

接下来验证Q2,首先创建一个新的接口和实现类,里面调用BookShopService 的purchase方法,观察结果

@Service
public class TestBookShopServiceImpl implements TestBookShopService {
    @Autowired
    private BookShopService shopService;
    @Override
    public void testBookPurchase(String name, String isbn) {
        shopService.purchase(name,isbn);
    }
}

观察结果,在余额不足的情况下,外部方法调用purchase方法,抛出异常时,事务回滚,库存没有减少,原因同Q1相同,但正好相反,但是走了AOP代理,所以事务起作用了.



那么如果在内部的方法purchaseAgain,和外部的方法中加入事务控制又会是怎样的情况呢?

这里直接给出结论:

purchaseAgain方法加入注解@Transactional后,调用purchase方法(无论是否添加@Transactional),事务控制起作用;外部类的testBookPurchase方法调用本类的purchase方法,事务控制也是起作用的.

由此引入spring关于事务的传播行为的介绍:spring的事务传播行为一共分为以下几种:

  1. REQUIRED(常用)
  2. REQUIRES_NEW(常用)
  3. SUPPORTS
  4. NOT_SUPPORTED
  5. NEVER
  6. NESTED
  7. MANDATORY

    在@Transactional注解中是propagation属性;

分别介绍:

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。(是spring 的默认事务传播行为)。

PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。

PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。



事务的传播行为定义了事务的控制范围,那么事务的隔离级别定义的则是事务在数据库读写方面的控制范围.

有的时候,在程序并发的情况下,会发生以下的神奇情况:

  • 脏读:对于两个事务T1,T2,T1读取了T2更新但是还未提交的字段,之后,若T2回滚,那么T1读取的内容就是临时且无效的
  • 不可重复读:对于两个事务T1,T2, T1读取了一个字段,然后被T2更新了,之后T1再次读取,字段值变掉了.
  • 幻读:两个事务T1,T2, T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,如果T1再次读取同一个表,就会多出几行数据.

    那么以上的问题要如何来解决呢,spring给出了它的解决方案,将事务的隔离性分为以下几个等级

  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

    在@Transactional注解中是propagation属性;

分别介绍:

READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读;

READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 这种隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读;

REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读;

SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读;

以上几种隔离界别, 在了解了其作用及其可避免的情况之后,我们在工作中视情况采用,不过一般默认情况就可以处理大多数情况了.

---

最后小结

这篇文章回顾了spring的事务相关的技术要点,包括什么是事务,事务的四个基本属性,为什么要使用事务,事务的分类,事务的传播种类以及事务的隔离级别.大体上涵盖了事务的相关知识,但是并没有深入到源码级别来研究事务的相关实现,有机会一定要深入源码了解实现,这样才能对知识的学习理解达到庖丁解牛的地步,对自己以后的知识积累和提升也会有很大的帮助.

原文地址:https://www.cnblogs.com/yunjiandubu/p/10165404.html

时间: 2024-10-09 23:24:00

spring的事务操作(重点)的相关文章

spring的事务操作

我们项目一期已经差不多结束了,所以一些细节也被拿了出来,出现最多的就是事务的操作了.因为自己负责的是一个模块(因为是另外一个项目的负责人),所以组员经常会遇到事务的问题,会出现很多奇葩的用法,各种乱用,估计他们就知道在方法上面注解@Transactional,但是其中的很多细节都不知道.所以经常会出现一个情况,就是一大坨代码出现了事务的问题,然后我就去各种改.所以今天也对事务做一个总结吧.以后忘记了可以回来看看. 一般我们使用事务最主要注重的是三个方面: 1.propagation:传播性  

Spring之事务操作(注解)

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

Spring之事务操作(配置文件)

UserDao.java 1 package helloworld.tx; 2 3 import org.springframework.jdbc.core.JdbcTemplate; 4 5 public class UserDao { 6 7 private JdbcTemplate jdbcTemplate; 8 9 public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 10 this.jdbcTemplate = jdbcTem

spring(三) spring事务操作

前面一篇博文讲解了什么是AOP.学会了写AOP的实现,但是并没有实际运用起来,这一篇博文就算是对AOP技术应用的进阶把,重点是事务的处理. --wh 一.jdbcTemplate 什么是JdbcTemplate? spring提供用于操作数据库模版,类似Dbutils,通俗点讲,我们操作数据库,spring也会帮我们提供一个操作数据库的工具供我们使用,而不用我们自己手动编写连接数据库,获取结果集等等操作,这个工具就是JdbcTemplate.跟Dbutils一样,想要使用JdbcTemplate

spring学习(三) ———— spring事务操作

前面一篇博文讲解了什么是AOP.学会了写AOP的实现,但是并没有实际运用起来,这一篇博文就算是对AOP技术应用的进阶把,重点是事务的处理. --wh 一.jdbcTemplate 什么是JdbcTemplate? spring提供用于操作数据库模版,类似Dbutils,通俗点讲,我们操作数据库,spring也会帮我们提供一个操作数据库的工具供我们使用,而不用我们自己手动编写连接数据库,获取结果集等等操作,这个工具就是JdbcTemplate.跟Dbutils一样,想要使用JdbcTemplate

如何处理Spring、Ibatis结合MySQL数据库使用时的事务操作

Ibatis是MyBatis的前身,它是一个开源的持久层框架.它的核心是SqlMap--将实体Bean跟关系数据库进行映射,将业务代码和SQL语句的书写进行分开.Ibatis是"半自动化"的ORM持久层框架.这里的"半自动化"是相对Hibernate等提供了全面的数据库封装机制的"全自动化"ORM实现而言的,"全自动"ORM实现了POJO与数据库表字段之间的映射并且实现了SQL的自动生成和执行.而Ibatis的着力点,则在于P

Java框架spring学习笔记(十八):事务操作

事务操作创建service和dao类,完成注入关系 service层叫业务逻辑层 dao层单纯对数据库操作层,在dao层不添加业务 假设现在有一个转账的需求,狗蛋有10000元,建国有20000元,狗蛋向建国转账1000元钱. 编写service层创建业务逻辑,OrderService.java 1 import cn.dao.OrderDao; 2 3 public class OrderService { 4 private OrderDao orderDao; 5 6 public voi

Spring中事务管理

1.什么是事务? 事务是逻辑上的一组操作,这组操作要么全部成功,要么全部失败 2.事务具有四大特性ACID 1)原子性(Atomicity):即不可分割性,事务要么全部被执行,要么就全部不被执行.如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生转换:如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态,不会发生状态转换. 2)一致性(Consistency):事务的执行使得数据库从一种正确状态转换成另一种正确状态.例如对于银行转账事务,不管事务

Spring事务配置的五种方式和spring里面事务的传播属性和事务隔离级别

转: http://blog.csdn.net/it_man/article/details/5074371 Spring事务配置的五种方式 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的. 总结如下: Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪