spring 事务详解

基于注解方式

事务,一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。

原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

例如:在银行转账的时候,假设要从账户A转账100元到账户B,只允许两种情况发生:①转账成功,即账户A余额减少100并且账户B余额增加100 ②转账失败,即账户A的余额不变并且账户B的余额不变。 除了这两种情况之外的都绝对不允许发生。

BookShopDao接口


1

2

3

4

5

6

7

8

9

10

11

public interface BookShopDao {

    //根据书号获取书的单价

    public int findBookPriceByIsbn(String isbn);

    

    //更新书的库存,使书号对应的库存-1

    public void updateBookStock(String isbn);

    

    //更新用户的账户余额,是username的balance-price

    public void updateUserAccount(String username, int price);

}

BookShopDao接口的实现类


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

@Repository("bookShopDao")

public class BookShopImpl 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 s = "select stock from book_stock where isbn = ?";

        int stock = jdbcTemplate.queryForObject(s, Integer.class, isbn);

        //System.out.println("stock:" + stock);

        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 username, int price) {

        //验证余额不否足够。若余额不足则抛出异常

        String s = "select balance from account where username = ?";

        int balance = jdbcTemplate.queryForObject(s, Integer.class, username);

        if (balance < price) {

            //System.out.println("余额不足");

            throw new UserAccountException("余额不足");

        }

        String sql = "update account set balance = balance - ? where username = ?";

        jdbcTemplate.update(sql, price, username);

    }

}

BookStockException.java和UserAccountException.java为自定义的两个异常类,继承RuntimeException。

BookShopService接口


1

2

3

4

public interface BookShopService {

    public void purchase(String username, String isbn);

}

BookShopService接口的实现类


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

@Service("bookShopService")

public class BookShopServiceImpl implements BookShopService{

    @Autowired

    private BookShopDao bookShopDao;

    

    //添加事务注解。

    //当用户的账户余额不足并且书的库存足够多的时候,

    //在执行findBookPriceByIsbn的时候不会抛出异常,在执行updateBookStock的时候也不会抛出异常。

    //而在执行updateUserAccount的时候会抛出“余额不足”的异常。

    //显然这次购买是不成功的。如果没有声明式事务,则书的库存会-1.显然这样是有问题的。

    //添加了声明式事务之后,下面的三个方法要么都执行成功,否则都不会对数据库进行操作。

    //这样就能保证数据的正确性。

    @Transactional

    @Override

    public void purchase(String username, String isbn) {

        //获取书的单价

        int price = bookShopDao.findBookPriceByIsbn(isbn);

        //更新书的库存

        bookShopDao.updateBookStock(isbn);

        //更新用户余额

        bookShopDao.updateUserAccount(username, price);

    }

}

applicationContext.xml配置


1

2

3

4

5

6

7

8

9

10

<!-- 配置Spring的JdbcTemplate -->

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

    <property name="dataSource" ref="dataSource"></property>

</bean>

<!-- 配置事务管理器 -->

<bean id="tansactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"></property>

</bean>

<!-- 启用事务注解 -->

<tx:annotation-driven transaction-manager="tansactionManager"/>

测试


1

2

3

4

5

6

7

8

private BookShopService bookShopService = null;

applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

bookShopService = applicationContext.getBean(BookShopService.class);

@Test

public void testBookShopService() {

    bookShopService.purchase("umgsai""1001");

}



事务的传播行为及事务的注解配置

定义接口CashierImpl.java及实现类。实现类如下。


1

2

3

4

5

6

7

8

9

10

11

12

13

@Service("cashier")

public class CashierImpl implements Cashier {

    @Autowired

    private BookShopService bookShopService;

    @Transactional

    @Override

    public void checkout(String username, List<String> isbns) {

        for (String isbn : isbns) {

            bookShopService.purchase(username, isbn);

        }

    }

}

BookShopServiceImpl实现类


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

@Service("bookShopService")

public class BookShopServiceImpl implements BookShopService{

    @Autowired

    private BookShopDao bookShopDao;

    

    /**添加事务注解。假设A方法调用purchase方法。

    *1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时

    *如何使用事务,默认取值为REQUIRED,即使用A方法的事务。

    *REQUIRES_NEW:使用purchase方法自己的事务,A方法的事务会被挂起。

    *2.使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED。

    *3.默认情况下Spring的声明式事务对所有的运行时异常进行回滚。也可以通过对应的属性进行设置。

     *noRollbackFor={UserAccountException.class}  指定对于UserAccountException异常不回滚。

     *通常情况下回滚属性取默认值即可。

     *4.使用readOnly指定事务是否只读。表示这个事务只读取事务不更新事务,帮助数据库引擎优化事务。

     *如果是一个只读取数据库数据的方法应设置readOnly=true。

     *5.使用timeout指定强制回滚之前事务可以占用的时间。单位为秒。如果超时则会强制回滚。

     */

    @Transactional(propagation=Propagation.REQUIRES_NEW,

            isolation=Isolation.READ_COMMITTED

            )

    @Override

    public void purchase(String username, String isbn) {

        /*

        try {

            Thread.sleep(5000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        */

        //获取书的单价

        int price = bookShopDao.findBookPriceByIsbn(isbn);

        //更新书的库存

        bookShopDao.updateBookStock(isbn);

        //更新用户余额

        bookShopDao.updateUserAccount(username, price);

    }

}

测试方法


1

2

3

4

@Test

public void testTransactionalPropagation() {

    cashier.checkout("umgsai", Arrays.asList("1001""1002"));

}

基于配置文件方式

BookShopDao接口


1

2

3

4

5

6

7

8

9

10

11

public interface BookShopDao {

    //根据书号获取书的单价

    public int findBookPriceByIsbn(String isbn);

    

    //更新书的库存,使书号对应的库存-1

    public void updateBookStock(String isbn);

    

    //更新用户的账户余额,是username的balance-price

    public void updateUserAccount(String username, int price);

}

BookShopDao接口的实现类


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

public class BookShopImpl implements BookShopDao{

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

        this.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 s = "select stock from book_stock where isbn = ?";

        int stock = jdbcTemplate.queryForObject(s, Integer.class, isbn);

        //System.out.println("stock:" + stock);

        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 username, int price) {

        //验证余额不否足够。若余额不足则抛出异常

        String s = "select balance from account where username = ?";

        int balance = jdbcTemplate.queryForObject(s, Integer.class, username);

        if (balance < price) {

            //System.out.println("余额不足");

            throw new UserAccountException("余额不足");

        }

        String sql = "update account set balance = balance - ? where username = ?";

        jdbcTemplate.update(sql, price, username);

    }

}

BookStockException.java和UserAccountException.java为自定义的两个异常类,继承RuntimeException。

BookShopService接口


1

2

3

4

public interface BookShopService {

    public void purchase(String username, String isbn);

}

BookShopService接口的实现类


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

public class BookShopServiceImpl implements BookShopService{

    private BookShopDao bookShopDao;

    public void setBookShopDao(BookShopDao bookShopDao) {

        this.bookShopDao = bookShopDao;

    }

    

    @Override

    public void purchase(String username, String isbn) {

        /*

        try {

            Thread.sleep(5000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        */

        //获取书的单价

        int price = bookShopDao.findBookPriceByIsbn(isbn);

        //更新书的库存

        bookShopDao.updateBookStock(isbn);

        //更新用户余额

        bookShopDao.updateUserAccount(username, price);

    }

}

applicationContext.xml配置


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

<?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 http://www.springframework.org/schema/context/spring-context-4.1.xsd

        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd

        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

    <context:component-scan base-package="com.spring"></context:component-scan>

    <!-- 导入资源文件 -->

    <context:property-placeholder location="classpath:db.properties" />

    <!-- 配置C3P0数据源 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

        <property name="user" value="${jdbc.user}"></property>

        <property name="password" value="${jdbc.password}"></property>

        <property name="driverClass" value="${jdbc.driverClass}"></property>

        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></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="dataSource"></property>

    </bean>

    

    <!-- 配置bean -->

    <bean id="bookShopDao" class="com.spring.tx.xml.BookShopImpl">

        <property name="jdbcTemplate" ref="jdbcTemplate"></property>

    </bean>

    

    <bean id="bookShopService" class="com.spring.tx.xml.BookShopServiceImpl">

        <property name="bookShopDao" ref="bookShopDao"></property>

    </bean>

    

    <bean id="cashier" class="com.spring.tx.xml.CashierImpl">

        <property name="bookShopService" ref="bookShopService"></property>

    </bean>

    

    <!-- 1.配置事务管理器 -->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource" ref="dataSource"></property>

    </bean>

    

    <!-- 2.配置事务属性 -->

    <tx:advice id="txAdvice" transaction-manager="transactionManager">

         <tx:attributes>

           <!-- 根据方法名指定事务的属性 -->

           <tx:method name="purchase" propagation="REQUIRES_NEW" timeout="3"/>

           

           <tx:method name="*"/>

           <tx:method name="get*" read-only="true"/>

           <tx:method name="find*" read-only="true"/>

           

         </tx:attributes>

    </tx:advice>

    

    <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->

    <aop:config>

        <aop:pointcut expression="execution(* com.spring.tx.xml.BookShopService.*(..))" id="txPointCut"/>

        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>

    </aop:config>

    

    <aop:config>

        <aop:pointcut expression="execution(* com.spring.tx.xml.Cashier.*(..))" id="cashierPointCut"/>

        <aop:advisor advice-ref="txAdvice" pointcut-ref="cashierPointCut"/>

    </aop:config>

</beans>

测试


1

2

3

4

        @Test

    public void testTransactionalPropagation() {

        cashier.checkout("umgsai", Arrays.asList("1001""1002"));

    }

 

时间: 2024-11-16 03:43:27

spring 事务详解的相关文章

Spring事务详解

Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到解脱.使得我们再也无需要去处理获得连接.关闭连接.事务提交和回滚等这些操作.再也无需要我们在与事务相关的方法中处理大量的try-catch-finally代码.我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性.事务属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务只读标志组成.我们在进行事

Spring事务详解(转)

Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到解脱.使得我们再也无需要去处理获得连接.关闭连接.事务提交和回滚等这些操作.再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码.我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性.事务属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务只读标志组成.我们在进行事

spring事务详解(二)实例

在Spring中,事务有两种实现方式: 编程式事务管理: 编程式事务管理使用底层源码可实现更细粒度的事务控制.spring推荐使用TransactionTemplate,典型的模板模式. 申明式事务管理: 添加@Transactional注解,并定义传播机制+回滚策略.基于Spring AOP实现,本质是对方法前后进行拦截, 方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务. 关于spring事务实现方式: 引用博文:https://www.cnblogs.co

spring事务详解(一)初探讨

一.什么是事务 维基百科:数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成.理解:事务(Transaction)是数据库区别于文件系统的重要特性之一.传统关系型数据库设计原则是满足 ACID特性,用以保证数据库事务的正确执行.Mysql的innoDB引擎就很好的支持了ACID. 二.事务的ACID特性 (箭头后,翻译自官网介绍:InnoDB and the ACID Model ) 1,原子性(Atomicity):一个事务必须被视为一个不可分割的

Spring AOP详解(转载)

此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题.最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP 来解决.一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容.本文是权当本人的自己AOP学习笔记,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智. 对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况 监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员

J2EE进阶(四)Spring配置文件详解

J2EE进阶(四)Spring配置文件详解 前言 Spring配置文件是用于指导Spring工厂进行Bean生产.依赖关系注入(装配)及Bean实例分发的"图纸".Java EE程序员必须学会并灵活应用这份"图纸"准确地表达自己的"生产意图".Spring配置文件是一个或多个标准的XML文档,applicationContext.xml是Spring的默认配置文件,当容器启动时找不到指定的配置文档时,将会尝试加载这个默认的配置文件. 下面列举的是

Spring AOP 详解 【转】

此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题.最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP 来解决.一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容.本文是权当本人的自己AOP学习笔记,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智. 对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况 监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员

Spring JDBC详解

<Spring JDBC详解> 本文旨在讲述Spring JDBC模块的用法.Spring JDBC模块是Spring框架的基础模块之一. 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.概述 在Spring JDBC模块中,所有的类可以被分到四个单独的包:1)core即核心包,它包含了JDBC的核心功能.此包内有很多重要的类,包括:JdbcTemplate类.SimpleJdbcInsert类,SimpleJdbcCall类,以及NamedP

Spring AOP详解(转载)所需要的包

上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑,没有main方法,是怎么运行的.这是用的junit,结合spring来进行的测试类. Spring AOP详解(转载)所需要的包,布布扣,bubuko.com