五spring 声明式事务注解配置

Posted 啄木鸟伍迪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五spring 声明式事务注解配置相关的知识,希望对你有一定的参考价值。

一、事务概述:

  • 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用;比如 用户购买图书;购买动作之前需要确认 ①图书的数量是否足够;②用户账号余额是否足够;如果①满足条件 那么 库存减-1 ;如果②满足条件  则账户余额- 书价 ;如果 ① 和②只要有一个不满足条件 则 图书库存回滚到之前的状态(此次操作之前的数量) 用户余额回滚到原来的状态(此次操作之前的余额);① 和②都满足条件 则 事务动作完成,事务就被提交.;
  • 事务的四个关键属性(ACID)
    • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
    • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
    • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
    • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
  • spring的事务管理
    • 支持编程式事务管理:将事务管理代码 写入代码中;存在代码冗余;
    • 支持声明式事务管理:将事务管理代码 从代码中分离,通过声明的方式来实现事务管理;应用更广泛更方便;

二、声明式事务注解配置

事务的配置以实例:

用户购买图书;购买动作之前需要确认 ①图书的数量是否足够;②用户账号余额是否足够;如果①满足条件 那么 库存减-1 ;如果②满足条件  则账户余额- 书价 ;如果 ① 和②只要有一个不满足条件 则 图书库存回滚到之前的状态(此次操作之前的数量)且 用户余额回滚到原来的状态(此次操作之前的余额);① 和②都满足条件 则 事务动作完成,事务就被提交;

用户账户表表SQL:

1 CREATE TABLE `account` (
2   `id` int(10) NOT NULL AUTO_INCREMENT,
3   `userName` varchar(20) NOT NULL,
4   `balance` varchar(20) NOT NULL,
5   PRIMARY KEY (`id`)
6 ) 

 

图书库存表bookstockSQL:

1 CREATE TABLE `bookstock` (
2   `id` int(10) NOT NULL AUTO_INCREMENT,
3   `isbn` int(20) NOT NULL,
4   `stock` varchar(20) NOT NULL,
5   PRIMARY KEY (`id`)
6 ) 

 

图书表bookSQL:

1 CREATE TABLE `book` (
2   `id` int(10) NOT NULL AUTO_INCREMENT,
3   `Isbn` int(20) NOT NULL,
4   `price` int(10) NOT NULL,
5   `bookName` varchar(20) CHARACTER SET utf8 NOT NULL,
6   PRIMARY KEY (`id`)
7 ) ;

 

1.配置事务管理器

1     <bean id="transactionManager" 
2         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
3         <property name="dataSource" ref="datasource"></property>
4     </bean>

 

2.启用事务注解

 

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

 附上xml 文件 引入了context tx bean的命名空间:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:tx="http://www.springframework.org/schema/tx"
 6     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 7         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
 8         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
 9 
10 <context:component-scan base-package="lixiuming.spring.tx"></context:component-scan>
11 <!-- 导入资源文件 -->
12 <context:property-placeholder location="classpath:db.properties"/>
13 <!-- 配置c3p0数据源 -->
14 <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
15     <property name="user" value="${jdbc.user}"></property>
16     <property name="password" value="${jdbc.password}"></property>
17     <property name="driverClass" value="${jdbc.driverClass}"></property>
18     <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
19     
20     <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
21     <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
22         <property name="maxStatements" value="${jdbc.maxStatements}"></property>
23 </bean>
24 
25 <!-- 配置 NamedParameterJdbcTemplate 该对象可以使用具名参数  他没有无参数的构造器,必须指定构造器参数-->
26 <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
27     <constructor-arg ref="datasource"></constructor-arg>
28 </bean>
29 
30 <!--配置spring的 jdbcTemplate -->
31 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
32     <property name="dataSource" ref="datasource"></property>
33 </bean>
34 
35 <!-- 1.配置事务管理器 -->
36     <bean id="transactionManager" 
37         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
38         <property name="dataSource" ref="datasource"></property>
39     </bean>
40     
41     <!-- 2.启用事务注解 -->
42     <tx:annotation-driven transaction-manager="transactionManager"/>
43 </beans>
db.properties:
1 jdbc.user=root
2 jdbc.password=
3 jdbc.driverClass=com.mysql.jdbc.Driver
4 jdbc.jdbcUrl=jdbc:mysql:///test
5 
6 
7 jdbc.initPoolSize =5
8 jdbc.maxPoolSize = 10
9 jdbc.maxStatements=0

 

3.添加事务注解@Transactional

  DAO层:

 1 package lixiuming.spring.tx;
 2 
 3 public interface BookShopDao {
 4 
 5     /**
 6      * 根据书号查找书的价格
 7      * 
 8      * @param Isbn
 9      * @return
10      */
11     public int findBookPriceByIsbn(int Isbn);
12 
13     /**
14      * 使书号对应的库存减一
15      */
16     public void updateBookSock(int Isbn);
17 
18     /**
19      * 更新用户的账户余额:blance -price
20      */
21     public void updatUserAccount(String userName, int price);
22 
23 }
 1 package lixiuming.spring.tx;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.jdbc.core.JdbcTemplate;
 5 import org.springframework.stereotype.Repository;
 6 
 7 @Repository("bookShopImpl")
 8 public class BookShopImpl implements BookShopDao {
 9 
10     @Autowired
11     private JdbcTemplate jdbcTemplate;
12 
13     @Override
14     public int findBookPriceByIsbn(int Isbn) {
15         String sql = "select price from book where isbn = ? ";
16         return jdbcTemplate.queryForObject(sql, Integer.class, Isbn);
17     }
18 
19     @Override
20     public void updateBookSock(int Isbn) {
21         // 检查书的库存是否足够,若不够则抛出异常
22         String sql2 = "select stock from bookstock where isbn =?";
23         int stock = jdbcTemplate.queryForObject(sql2, Integer.class, Isbn);
24         if (stock == 0) {
25             throw new BookStockException("库存不足");
26         }
27 
28         String sql = "update bookstock set stock = stock-1 where Isbn = ?";
29         jdbcTemplate.update(sql, Isbn);
30 
31     }
32 
33     @Override
34     public void updatUserAccount(String userName, int price) {
35         String sql2 = "select balance from account where userName =?";
36         int account = jdbcTemplate.queryForObject(sql2, Integer.class, userName);
37         if (account < price) {
38             throw new UserAccountException("余额不足");
39         }
40         String sql = "update account set balance = balance-? where userName =?";
41         jdbcTemplate.update(sql, price, userName);
42     }
43 
44 }

  Service层:

1 package lixiuming.spring.tx;
2 
3 public interface BookShopService {
4     
5     public void purchase(String userName,int isbn);
6 
7 }
 1 package lixiuming.spring.tx;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.stereotype.Service;
 5 import org.springframework.transaction.annotation.Propagation;
 6 import org.springframework.transaction.annotation.Transactional;
 7 
 8 @Service("bookShopServiceImpl")
 9 public class BookShopServiceImpl implements BookShopService {
10 
11     @Autowired
12     private BookShopDao dao;
13 
14     @Transactional15     @Override
16     public void purchase(String userName, int isbn) {
17         // 书的单价
18         int price = dao.findBookPriceByIsbn(isbn);
19         // 更新库存
20         dao.updateBookSock(isbn);
21         // 更新余额
22         dao.updatUserAccount(userName, price);
23 
24     }
25 
26 }

其他(自定义异常):

  • 库存异常
 1 package lixiuming.spring.tx;
 2 
 3 public class BookStockException extends RuntimeException {
 4 
 5     /**
 6      * 
 7      */
 8     private static final long serialVersionUID = 4237643951857538899L;
 9 
10     /**
11      * 
12      */
13 
14     public BookStockException() {
15         super();
16         // TODO Auto-generated constructor stub
17     }
18 
19     public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20         super(message, cause, enableSuppression, writableStackTrace);
21         // TODO Auto-generated constructor stub
22     }
23 
24     public BookStockException(String message, Throwable cause) {
25         super(message, cause);
26         // TODO Auto-generated constructor stub
27     }
28 
29     public BookStockException(String message) {
30         super(message);
31         // TODO Auto-generated constructor stub
32     }
33 
34     public BookStockException(Throwable cause) {
35         super(cause);
36         // TODO Auto-generated constructor stub
37     }
38 
39 }
  • 用户账户异常
 1 package lixiuming.spring.tx;
 2 
 3 public class UserAccountException extends RuntimeException {
 4 
 5     /**
 6      * 
 7      */
 8     private static final long serialVersionUID = -3973495734669194251L;
 9 
10     /**
11      * 
12      */
13 
14     public UserAccountException() {
15         super();
16         // TODO Auto-generated constructor stub
17     }
18 
19     public UserAccountException(String message, Throwable cause, boolean enableSuppression,
20             boolean writableStackTrace) {
21         super(message, cause, enableSuppression, writableStackTrace);
22         // TODO Auto-generated constructor stub
23     }
24 
25     public UserAccountException(String message, Throwable cause) {
26         super(message, cause);
27         // TODO Auto-generated constructor stub
28     }
29 
30     public UserAccountException(String message) {
31         super(message);
32         // TODO Auto-generated constructor stub
33     }
34 
35     public UserAccountException(Throwable cause) {
36         super(cause);
37         // TODO Auto-generated constructor stub
38     }
39 
40 }

 

测试方法:

 1 package lixiuming.spring.tx;
 2 
 3 import org.junit.Test;
 4 import org.springframework.context.ApplicationContext;
 5 import org.springframework.context.support.ClassPathXmlApplicationContext;
 6 
 7 public class SpringTransactionTest {
 8 
 9     private ApplicationContext cxt = null;
10     private BookShopService parchase = null;
11 
12     {
13         cxt = new ClassPathXmlApplicationContext("application_transaction.xml");
14         parchase = cxt.getBean(BookShopService.class);
15     }
16 
17     @Test
18     public void testpurchase() {
19         parchase.purchase("aa", 1001);
20     }
21 
22 }

4.测试

测试前提:用户账户表 账户金额为120 ; 书号1001的图书库存为 10 ;

当第一次运行testpurchase 时,没有异常 ; 书号为1001的库存为 9 ,账户金额为20;当第二次执行testpurchase时,抛出异常;异常内容为 余额不足且 书号为1001的库存为 9 ,账户金额为20;

 

三、声明式事务的事务传播行为

  • 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
  • 使用propagation指定事务的传播行为,

  • 事务的传播行为可以由传播属性指定. Spring 定义了 7  种类传播行为.默认为REQUIRED,常用为REQUIRED和REQUIRED_NEW;
  •  

1.REQUIRED

上一节 用户购买图书的实例。添加另外一个方法事务方法checkout,checkout 调用purchase 方法来测试 事务的传播行为;

添加 Cashier接口及其实现类:

1 package lixiuming.spring.tx;
2 
3 import java.util.List;
4 
5 public interface Cashier {
6 
7     public void checkout(String username, List<Integer> isbns);
8 
9 }
 1 package lixiuming.spring.tx;
 2 
 3 import java.util.List;
 4 
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.stereotype.Service;
 7 import org.springframework.transaction.annotation.Transactional;
 8 
 9 @Service("cashierImpl")
10 public class CashierImpl implements Cashier {
11 
12     @Autowired
13     private BookShopService bookShopService;
14 
15     @Transactional
16     @Override
17     public void checkout(String username, List<Integer> isbns) {
18         for (Integer isbn : isbns) {
19             bookShopService.purchase(username, isbn);
20         }
21     }
22 
23 }

测试方法:

 1 package lixiuming.spring.tx;
 2 
 3 import java.util.Arrays;
 4 
 5 import org.junit.Test;
 6 import org.springframework.context.ApplicationContext;
 7 import org.springframework.context.support.ClassPathXmlApplicationContext;
 8 
 9 public class Spr

Spring 事务控制 -- 基于注解的声明式事务控制

01:Spring 事务控制 – 编程式事务控制相关对象
02:Spring 事务控制 – 基于XML的声明式事务控制:环境搭建
03:Spring 事务控制 – 基于XML的声明式事务控制:详细配置
04:Spring 事务控制 – 基于注解的声明式事务控制


1. 基于注解的声明式事务控制

在此之前,你应该先把环境搭建好。
02:Spring 事务控制 – 基于XML的声明式事务控制:环境搭建


1.1 修改applicationContext.xml配置文件

一般来说,自己写的类通过注解的方式交给Spring IoC管理。非自定义的类通过配置文件的方式进行配置。

applicationContext.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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

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

    <!--组件扫描-->
    <context:component-scan base-package="com.tian"/>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
        <property name="user" value="root"/>
        <property name="password" value="317525"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

    <!--事物的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

1.2 托管IoC


1.3 注解配置事务

package com.tian.service.impl;

import com.tian.dao.AccountDao;
import com.tian.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("accountService")
// 该注解里面配置事务的属性信息:隔离级别、传播行为、超时时间、是否只读
// 如果在类上面配置了信息:那么这个事务的属性消息对于下面的方法是通用的
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, timeout = 2, readOnly = true)
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    // 方法上配置事务的属性信息会覆盖类上面配的信息
    @Transactional(isolation = Isolation.REPEATABLE_READ, timeout = 3, readOnly = false)
    public void transfer(String outMan, String inMan, double money) {
        // 这里应该开启事务
        accountDao.out(outMan, money);
        int i = 1 / 0;
        accountDao.in(inMan, money);
        // 这里应该提交事务
    }
}

运行结果:

我们先把accout表的金额都设置为5000
测试:手动制造异常

运行controller方法:

运行结果:



1.4 基于注解的声明式事务控制要点



以上是关于五spring 声明式事务注解配置的主要内容,如果未能解决你的问题,请参考以下文章

spring中声明式事务 配置好后,在java代码中怎么使用

Spring Boot 中使用 @Transactional 注解配置事务管理

Spring Boot 中使用 @Transactional 注解配置事务管理

Spring Boot 中使用 @Transactional 注解配置事务管理

Spring Boot 中使用 @Transactional 注解配置事务管理

spring的@Transactional注解