Spring之声明式事务

时间:2022-09-04 19:23:27

在讲声明式事务之前,先回顾一下基本的编程式事务

编程式事务:
//1.获取Connection对象
Connection conn = JDBCUtils.getConnection();
try {
//2.开启事务:取消自动提交
conn.setAutoCommit(false);
//3.执行数据库操作
chain.doFilter(req,resp);
//4.提交事务
conn.commit();
}catch(Exception e) {
//5.回滚事务
conn.rollBack();

}finally{
//6.释放资源
}

然后讲一下Spring之声明式事务
情景举例:

小明有1000元,从自助书店买一本价格为100,库存为200本的《三国演义》。

正常交易时,小明余额现在还有900元,书店库存还有199本。

假如在小明买书的过程中,交易出现异常,小明花了100元,机器出现故障没有给出书,即书店这本书的库存没有减少。

这显然是小明亏了,交易不合理。此时,这种交易应该马上“ 取消  ”,即交易不能通过。

放在程序中表示就是:小明花钱和库存减少应该放在

一个事务中,有任何一种情况没有正常执行,则整个程序不执行(不交给数据库执行操作)。

通过程序实现:

创建一个动态web工程
1.加入jar包
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar

然后是mysql驱动包即C3P0的jar包:
c3p0-0.9.1.2.jar
mysql-connector-java-5.1.37-bin.jar
2.创建一份jdbc.properties文件
jdbc.user=root
jdbc.passowrd=123456
jdbc.url=jdbc:mysql://localhost:3306/tx
jdbc.driver=com.mysql.jdbc.Driver

3.在spring配置文件中配置数据源
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.passowrd}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
</bean>

4.测试数据源:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test() throws SQLException {
DataSource bean = ioc.getBean(DataSource.class);
System.out.println(bean.getConnection());
}

}
5.配置jdbcTemplate:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>

6.创建Dao类
<!-- 设置扫描的包 -->
<context:component-scan base-package="com.neuedu"></context:component-scan>

@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;


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

//[2]根据isbn的值减少书的库存,假设每次都只买1本书
public void updateStockByIsbn(String isbn){
String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
jdbcTemplate.update(sql, isbn);
}
//[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
public void updateBalance(String userName,int price){
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price,userName);
}
7.创建Service层:
@Service
public class BookService {
@Autowired
private BookDao bookDao;

public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);
}
}
8.注意哦:上面doCash方法中调用的三个方法应该在同一个事务中,要么同时成功,要么同时失败!
先统一设置一下:
然后先正常测试一下service中的doCash方法:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);
private BookService bookService = ioc.getBean(BookService.class);

@Test
public void test04() throws SQLException {
bookService.doCash("ISBN-001","Tom");
}
}

由于此时该方法没在事务中,如果doCash方法调用dao层的方法的时候,在中间位置出现了错误,此时就会造成一部分数据改掉了,而
另一部分数据没有改掉,这就麻烦了,所以此时应该加入事务机制!

9.如果想开启事务,就需要在spring的配置文件中配置事务管理器

<!-- 配置事务管理器,并为事务管理器配置数据源!-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->
<!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

然后在service层的doCash方法上加上:@Transactional注解这样就开启了事务!需要注意的是事务一般是加在service层的!


整个业务逻辑的数据库操作:
[1]根据isbn的值查询书的价格
[2]根据isbn的值减少书的库存,假设每次都只买1本书
[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格

总结:事务可以分为编程式事务和声明式事务

声明式事务
①基本原理:AOP
[1]前置通知:开启事务
[2]返回通知:提交事务
[3]异常通知:回滚事务
[4]后置通知:释放资源
②事务管理器

③导入jar包
[1]IOC容器需要的jar包
[2]AOP需要的jar包
[3]JdbcTemplate操作需要的jar包
[5]MySQL驱动和C3P0

④配置
[1]配置数据源
[2]配置JdbcTemplate,并装配数据源
[3]配置事务管理器,并装配数据源
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name= "dataSource" ref ="dataSource"/>
</bean >
[4]开启基于注解的声明式事务功能
<tx:annotation-driven transaction-manager ="dataSourceTransactionManager"/>
如果事务管理器的bean的id正好是transaction-manager的默认值transactionManager,则可以省略

[5]在事务方法上加@Transactional注解

4.事务属性的设置
①事务的传播行为
②事务的隔离级别
③事务根据什么异常不进行回滚
④事务的超时属性
⑤事务的只读属性


①事务的传播行为[参见第8章world文档]
[1]解释:一个事务运行在另一个有事务的方法中,那么当前方法是开启新事务还是在原有的事务中运行。
[2]设置事务方法在调用其他事务方法时,自己的事务如何传播给被调用的方法。[默认是required]
[3]设置方式

案例演示:
在上面的dao中增加一个方法,用于演示事务!
//为了测试事务的传播行为,需要增加一个数据库操作
public void updatePrice(String isbn, int price){
String sql = "UPDATE book SET price = ? WHERE isbn = ?";
jdbcTemplate.update(sql, price,isbn);
}

同样,在service类中添加一个方法,如下所示:
@Transactional
public void updatePrice(String isbn, int price){
bookDao.updatePrice(isbn, price);
}
此时当前service类中就有两个事务方法了,然后我们现在新创建一个service类,加入到IOC容器中,并将bookService注入,
然后新建一个事务方法,如下所示:
@Component
public class MultiTX {
@Autowired
private BookService bookService;

@Transactional
public void multiTx(){
bookService.doCash("ISBN-003","Tom");
bookService.updatePrice("ISBN-005",888);
}
}

为了演示该service方法中调用的另外两个方法是不是使用了当前service方法的事务,这里我们将该service方法调用的第二个方法
弄出一个异常,然后看看该service方法调用的第一个service方法是不是回滚了!

先统一设置一下表中的数据:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;

用测试类进行测试:先正常测试一遍,然后再整出个异常测试一下:

public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);
private BookService bookService = ioc.getBean(BookService.class);
private MultiTX multiTx = ioc.getBean(MultiTX.class);
@Test
public void test04() throws SQLException {
multiTx.multiTx();
}
}

会发现@Transactional注解默认使用的传播属性就是required!
此时可以将BookService类中的两个方法都加上propagation=Propagation.REQUIRES_NEW 属性,也就是如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW)

然后在执行刚才的有异常的测试方法!



②事务的隔离级别 :用于解决并发问题[在两次获取价格的方法中间打断点]
isolation=Isolation. READ_COMMITTED

先恢复数据表中的数据:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;
修改service类中的doCash方法为:【即两次获取价格!】
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);

price = bookDao.findPriceByIsbn(isbn);
System.out.println("price2:"+price);
}
在测试方法中调用BookService类中的doCash方法,然后在doCash方法中两次获取价格的方法中间打断点,就会发现
第一次是1000元,然后我们修改了数据库之后,读出来的还是1000元,这是因为数据库默认的隔离级别就是可重复读!

如果我们在doCash方法的事务注解上加一个isolation属性,如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);

price = bookDao.findPriceByIsbn(isbn);
System.out.println("price2:"+price);
}
就会发现如果在两次获取价格之间打了断点,然后修改了值的话就会读出不一样的效果,说明事务的隔离级别设置就生效了!


③事务根据什么异常不进行回滚
默认情况下,出现异常就回滚,noRollbackFor属性可以设置出现什么异常不进行回滚:
noRollbackFor=ArithmeticException.class

@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor=ArithmeticException.class)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);
System.out.println(10/0);//出了异常信息
}

④事务的超时属性【timeout=3】
[1]数据库事务在执行过程中,会占用数据库资源,所以如果某一个事务执行的时间太长,
那么就会导致资源被长时间占用,影响其他事务执行。[死循环,网络问题]

[2]可以通过设置超时属性,将超时没有执行完的事务回滚,相当于对该操作进行撤销。
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor=ArithmeticException.class,
timeout=3)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bookDao.updateBalance(username, price);
}

⑤事务的只读属性【readOnly=true】
数据库会对事务进行优化,如果是一个查询操作,那么数据库可以有针对性的进行优化。我们可以通过设置事务属性,
告诉数据库当前操作是一个只读操作,便于数据库进行优化。

5.基于XML的声明式事务
<!-- 配置基于XML文件的声明式事务 -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(* com.neuedu.tx.service.BookService.*(String, String))" id="txPointCut"/>
<!-- 将事务切入点和事务建议的配置联系起来 -->
<aop:advisor advice-ref="bookTransaction" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 设置事务属性 -->
<tx:advice id="bookTransaction" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="doCash"
propagation="REQUIRED"
isolation="READ_COMMITTED"
read-only="false"
no-rollback-for="java.lang.ArithmeticException"
timeout="3" />
<!-- 将某一类方法统一设置为只读 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
</tx:attributes>
</tx:advice>

Spring之声明式事务的更多相关文章

  1. spring aop 声明式事务管理

    一.声明式事务管理的概括 声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一. Spring的声明式事务顾名思义就是采用声明 ...

  2. 【Spring】——声明式事务配置详解

    项目中用到了spring的事务: @Transactional(rollbackFor = Exception.class, transactionManager = "zebraTrans ...

  3. Spring AOP声明式事务异常回滚&lpar;转&rpar;

    转:http://hi.baidu.com/iduany/item/20f8f8ed24e1dec5bbf37df7 Spring AOP声明式事务异常回滚 近日测试用例,发现这样一个现象:在业务代码 ...

  4. &commat;Transactional、Spring的声明式事务

    传送门 一.Spring的声明式事务 需要在xml文件中配置 <!--配置事务管理器类--> <bean id="transactionManager" clas ...

  5. 使用注解实现Spring的声明式事务管理

    使用注解实现Spring的声明式事务管理,更加简单! 步骤: 1) 必须引入Aop相关的jar文件 2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类 3)在需要添加事务控制 ...

  6. Spring&lpar;四)Spring JdbcTemplate&声明式事务

    JdbcTemplate基本使用 01-JdbcTemplate基本使用-概述(了解) JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装.spr ...

  7. 保护亿万数据安全,Spring有&OpenCurlyDoubleQuote;声明式事务”绝招

    摘要:点外卖时,你只需考虑如何拼单:选择出行时,你只用想好目的地:手机支付时,你只需要保证余额充足.但你不知道这些智能的背后,是数以亿计的强大数据的支持,这就是数据库的力量.那么庞大数据的背后一定会牵 ...

  8. spring&plus;springMVC&comma;声明式事务失效,原因以及解决办法

    http://blog.csdn.net/z69183787/article/details/37819627#comments 一.声明式事务配置: <bean id="transa ...

  9. spring的声明式事务,及redis事务。

    Redis的事务功能详解 http://ghoulich.xninja.org/2016/10/12/how-to-use-transaction-in-redis/ MULTI.EXEC.DISCA ...

随机推荐

  1. AC日记——将字符串中的小写字母换成大写字母 openjudge 1&period;7 13

    13:将字符串中的小写字母转换成大写字母 总时间限制:  1000ms 内存限制:  65536kB 描述 给定一个字符串,将其中所有的小写字母转换成大写字母. 输入 输入一行,包含一个字符串(长度不 ...

  2. ArcMap 查询取位SQL

    shp:substring("XZQDM",7,1)='1' mdb:mid("XZQDM",7,1)='1'

  3. Core模块其他常用知识点[OpenCV 笔记14]

    Matx 轻量级的Mat,必须在使用前规定好大小,比如一个2x3的float型的Matx,可以声明为Matx23f Vec Vec是Matx的一个派生类,是一个一维的Matx,跟vector很相似.在 ...

  4. oracle 中查看一张表是否有主键,主键在哪个字段上的语句怎么查如要查aa表,

    select a.constraint_name, a.column_name from user_cons_columns a, user_constraints b where a.constra ...

  5. jQuery 的&period;data&lpar;&rpar;方法

    jQuery文档对.data()方法的描述: As of jQuery 1.4.3 HTML 5 data- attributes will be automatically pulled in to ...

  6. python基础(7):元祖类型(赋值补充)

    前面学了列表和字典,今天我们看一个和列表相似的类型元祖. 预习: 简单购物车 实现打印商品详细信息,用户输入商品名和购买个数,则将商品名,价格,购买个数加入购物列表,如果输入为空或其他非法输入则要求用 ...

  7. Java环境的搭建

    一.JDK的下载 JDK又称Java SE,可以从Oracle公司的官网上https://www.oracle.com/index.html下载. 1.打开Oracle官网.将光标移到[Menu]-[ ...

  8. RDIFramework&period;NET ━ &period;NET快速信息化系统开发框架 V3&period;2-模块管理按子系统进行分类管理

    在RDIFramework.NET以往的框架中,模块管理界面展示了整个框架所管理的所有模块,如果系统过多,达几十个甚至上百个子系统时,管理起来就非常的麻烦,不光加载效率会很低,页面展示也会很不友好.框 ...

  9. chrome 下 input&lbrack;file&rsqb; 元素cursor设置pointer不生效的解决

    https://jingyan.baidu.com/article/48b558e32fabb67f38c09a81.html 环境是chrome浏览器,今天发现为html网页中的input [fil ...

  10. &period;NET 操作 EventLog(Windows事件日志监控)(转载)

    操作Windows日志:EventLog 如果要在.NET Core控制台项目中使用EventLog(Windows事件日志监控),首先需要下载Nuget包: System.Diagnostics.E ...