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

编程式事务:
//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声明式事务异常回滚(转)

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

  4. @Transactional、Spring的声明式事务

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

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

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

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

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

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

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

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

    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. HTML5 开发APP( 支付宝支付)

    我们搞app有一个重要的功能那就是支付功能.无论是微信支付,支付宝,还是银联支付总要有一样支付手段来支持我们网上付款.实现完整的功能.我们公司app的支付方式经过大家开会讨论选择了支付宝支付(其实是当 ...

  2. linux下使用scp远程传输自动输入密码

    由于需要将A服务器的文件 远程传输到B服务器 但是scp命令每次都要手动输入密码 这样脚本执行太繁琐,所以讲A服务器和B服务器互信即可,具体操作如下: 首先在A服务器配置: mkdir -p ~/.s ...

  3. vue组件的那些事($children,$refs,$parent)的使用

    如果项目很大,组件很多,怎么样才能准确的.快速的寻找到我们想要的组件了?? 1)$refs 首先你的给子组件做标记.demo :<firstchild ref="one"&g ...

  4. HDU1027 Ignatius and the Princess II

    Problem Description Now our hero finds the door to the BEelzebub feng5166. He opens the door and fin ...

  5. 用Eclipse生成JPA元模型

    在JPA criteria 动态查询中,有个"元模型"的东西,它是根据"实体"类动态生成的一个类,它的主要作用是实现JPA criteria查询的"类 ...

  6. 聪明的燕姿[JLOI2014]

    题目描述 阴天傍晚车窗外 未来有一个人在等待 向左向右向前看 爱要拐几个弯才来 我遇见谁会有怎样的对白 我等的人他在多远的未来 我听见风来自地铁和人海 我排着队拿着爱的号码牌 城市中人们总是拿着号码牌 ...

  7. PyCharm 去掉自动保存功能

    PyCharm 4.5.4 环境配置 1.去掉"自动保存功能" pycharm默认是自动保存的,习惯自己按 ctrl + s 的可以进行如下设置: 菜单File -> Set ...

  8. CocoaPods详解之----制作篇【转】

    Cocoapods是非常好用的一个iOS依赖管理工具,使用它可以方便的管理和更新项目中所使用到的第三方库,以及将自己的项目中的公共组件交由它去管理.Cocoapods的介绍及优点本文就不在赘述,我开始 ...

  9. [技术]浅谈OI中矩阵快速幂的用法

    前言 矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中,矩阵的运算是数值分析领域的重要问题. 基本介绍 (该部分为入门向,非入门选手可以跳过) 由 m行n列元素排列成的矩形阵列.矩阵里的 ...

  10. Spring整合CXF webservice restful 实例

    webservice restful接口跟soap协议的接口实现大同小异,只是在提供服务的类/接口的注解上存在差异,具体看下面的代码,然后自己对比下就可以了. 用到的基础类 User.java @Xm ...