java学习笔记——spring 之声明式事务、事务的传播行为(REQUIRED与REQUIRES_NEW)、事务的隔离级别、设置触发事务回滚的异常、事务的超时、只读属性、XML式的声明式事务配置
5、声明式事务
- 事务概述
●在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
●事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
●事务的四个关键属性(ACID)
○原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
○一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
○隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
○持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
- Spring事务管理
- 1编程式事务管理
①使用原生的JDBC API进行事务管理
[1]获取数据库连接Connection对象
[2]取消事务的自动提交
[3]执行操作
[4]正常完成操作时手动提交事务
[5]执行失败时回滚事务
[6]关闭相关资源
②评价
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
- 1声明式事务管理
大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
Spring既支持编程式事务管理,也支持声明式的事务管理。
- 2Spring提供的事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
- 3事务管理器的主要实现
①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
③HibernateTransactionManager:用Hibernate框架存取数据库
2、事务的传播行为
2.1、简介
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务传播属性可以在@Transactional注解的propagation属性中定义。
2.2、测试
2.3、说明
①REQUIRED传播行为
当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。
②REQUIRES_NEW传播行为
表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
2.4、补充
在Spring 2.x事务通知中,可以像下面这样在<tx:method>元素中设定传播事务属性。
3、事务的隔离级别
3.1、数据库事务并发问题
假设现在有两个事务:Transaction01和Transaction02并发执行。
①脏读
[1]Transaction01将某条记录的AGE值从20修改为30。
[2]Transaction02读取了Transaction01更新后的值:30。
[3]Transaction01回滚,AGE值恢复到了20。
[4]Transaction02读取到的30就是一个无效的值。
②不可重复读
[1]Transaction01读取了AGE值为20。
[2]Transaction02将AGE值修改为30。
[3]Transaction01再次读取AGE值为30,和第一次读取不一致。
③幻读
[1]Transaction01读取了STUDENT表中的一部分数据。
[2]Transaction02向STUDENT表中插入了新的行。
[3]Transaction01读取了STUDENT表时,多出了一些行。
3.2、隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
①读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
②读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
③可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
④串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
⑤各个隔离级别解决并发问题的能力见下表
|
脏读 |
不可重复读 |
幻读 |
READ UNCOMMITTED |
有 |
有 |
有 |
READ COMMITTED |
无 |
有 |
有 |
REPEATABLE READ |
无 |
无 |
有 |
SERIALIZABLE |
无 |
无 |
无 |
⑥各种数据库产品对事务隔离级别的支持程度
|
Oracle |
MySQL |
READ UNCOMMITTED |
× |
√ |
READ COMMITTED |
√ |
√ |
REPEATABLE READ |
× |
√(默认) |
SERIALIZABLE |
√ |
√ |
3.3、在Spring中指定事务隔离级别
①注解
用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别
②XML
在Spring 2.x事务通知中,可以在<tx:method>元素中指定隔离级别
隔离级别
4、触发事务回滚的异常
4.1、默认情况
捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。
4.2、设置途经
①注解
@Transactional 注解
[1]rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
[2]noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个
②XML
在Spring 2.x事务通知中,可以在<tx:method>元素中指定回滚规则。如果有不止一种异常则用逗号分隔。
5、事务的超时和只读属性
5.1、简介
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
5.2、设置
①注解
@Transaction注解
②XML
在Spring 2.x事务通知中,超时和只读属性可以在<tx:method>元素中进行指定
6、基于XML文档的声明式事务配置
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
<!-- 事务建议;事务增强 advice-ref:指向事务管理器的配置 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!-- 配置事务管理器; 事务建议;事务增强;事务属性;
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明哪些方法是事务方法;切入点表达式只是说,事务管理器要切入这些方法,哪些方法加事务使用tx:method指定的 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 都用;重要的用配置,不重要的用注解 -->
================================================================================================
环境搭建:
1、创建数据库表文件、导入jar
2、写几个类和方法模拟结账操作;
package com.gome.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 1、减余额
*
* 减去某个用户的余额
*/
public void updateBalance(String userName,int price){
String sql = "UPDATE## account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql, price,userName);
}
/**
* 2、按照图书的ISBN获取某本图书的价格
* @return
*/
public int getPrice(String isbn){
String sql = "SELECT price FROM book WHERE isbn=?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
/**
* 3、减库存;减去某本书的库存;为了简单期间每次减一
*/
public void updateStock(String isbn){
String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
jdbcTemplate.update(sql, isbn);
}
}
package com.gome.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.atguigu.dao.BookDao;
@Service
public class BookService {
@Autowired
BookDao bookDao;
/**
* 结账;传入哪个用户买了哪本书
* @param username
* @param isbn
*/
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
//2、减余额
bookDao.updateBalance(username, price);
}
}
声明式事务:
以前通过复杂的编程来编写一个事务,替换为只需要告诉Spring哪个方法是事务方法即可;
Spring自动进行事务控制;
编程式事务:
TransactionFilter{
try{
//获取连接
//设置非自动 提交
chain.doFilter();
//提交
}catch(Exception e){
//回滚
}finllay{
//关闭连接释放资源
}
}
AOP:环绕通知可以去做;
//获取连接
//设置非自动 提交
目标代码执行
//正常提交
//异常回滚
//最终关闭
最终效果:
BookService{
@this is a tx-method(Transactional)
public void checkout(){
//xxxxx
}
}
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
自己要写这个切面还是很麻烦;
这个切面已经有了;(事务切面===事务管理器);
这个事务管理器就可以在目标方法运行前后进行事务控制(事务切面);
我们目前都使用DataSourceTransactionManager;即可;
------
快速的为某个方法添加事务:
1)、配置出这个事务管理器让他工作;
2)、开启基于注解的事务
3)、给事务方法加@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-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<!-- 配置数据源-->
<bean id="pooledDataSource" 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>
</bean>
<!-- 配置JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!-- 事务控制 -->
<!--1:配置事务管理器(切面)让其进行事务控制;一定导入面向切面编程的几个包
spring-aspects-4.0.0.RELEASE.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
-->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制住数据源 -->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--2:开启基于注解的事务控制模式;依赖tx名称空间 -->
<tx:annotation-driven transaction-manager="tm"/>
<!--3:给事务方法加注解@Transactional -->
</beans>
测试代码如下:
1,注释方式使用声明式事务:
加载的jar包有:
1)tx.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: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-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 1、配置数据源 -->
<bean id="pooledDataSource" 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>
</bean>
<!-- 2、配置JdbcTemplate操作数据库 value="#{pooledDataSource}" ref="pooledDataSource"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{pooledDataSource}"></property>
</bean>
<!-- 3、配置声明式事务
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、开启基于注解的事务式事务;依赖tx名称空间
3)、给事务方法加注解
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2)dbconfig.properties
jdbc.user=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/gome
## http://localhost:8080/bookstore
## jdbc:mysql://localhost:3306/jdbc_template
jdbc.driverClass=com.mysql.jdbc.Driver
3)BookDao.java
package com.gome.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 1、减余额
*
* 减去某个用户的余额
*/
public void updateBalance(String userName,int price){
String sql = "UPDATE account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql, price,userName);
}
/**
* 2、按照图书的ISBN获取某本图书的价格
* @return
*/
public int getPrice(String isbn){
String sql = "SELECT price FROM book WHERE isbn=?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
/**
* 3、减库存;减去某本书的库存;为了简单期间每次减一
*/
public void updateStock(String isbn){
String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
jdbcTemplate.update(sql, isbn);
}
/**
* 4、改图书价格
* @param isbn
* @param price
*/
public void updatePrice(String isbn,int price){
String sql = "update book set price=? where isbn=?";
jdbcTemplate.update(sql, price,isbn);
}
4)BookService.java
package com.gome.service;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;
import com.gome.dao.BookDao;
@Service
public class BookService {
@Autowired
BookDao bookDao;
// @Autowired
// BookService bookService;
/**
* 事务细节:
* isolation-Isolation:事务的隔离级别;
*
*
*
* noRollbackFor-Class[]:哪些异常事务可以不回滚
* noRollbackForClassName-String[](String全类名):
*
* rollbackFor-Class[]:哪些异常事务需要回滚;
* rollbackForClassName-String[]:
*
* 异常分类:
* 运行时异常(非检查异常):可以不用处理;默认都回滚;
* 编译时异常(检查异常):要么try-catch,要么在方法上声明throws
* 默认不回滚;
*
* 事务的回滚:默认发生运行时异常都 回滚,发生编译时异常不会回滚;
* noRollbackFor:哪些异常事务可以不回滚;(可以让原来默认回滚的异常给他不回滚)
* noRollbackFor={ArithmeticException.class,NullPointerException.class}
* noRollbackForClassName
*
* rollbackFor:原本不回滚(原本编译时异常是不回滚的)的异常指定让其回滚;
*
* readOnly-boolean:设置事务为只读事务:
* 可以进行事务优化;
* readOnly=true:加快查询速度;不用管事务那一堆操作了。
*
* timeout-int(秒为单位):超时:事务超出指定执行时长后自动终止并回滚
* @throws FileNotFoundException
*
*
* propagation-Propagation:事务的传播行为;
* 传播行为(事务的传播+事务的行为);
* 如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务;
* 传播行为:
* AService{
* tx_a(){
* //a的一些方法
* tx_b(){
* }
* tx_c(){
* }
* }
* }
*
*
*/
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//2、减余额
bookDao.updateBalance(username, price);
//int i = 10/0;
//new FileInputStream("D://hahahahha.aa");
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn, price);
}
/**
* 根据业务的特性;进行调整
* isolation=Isolation.READ_UNCOMMITTED:读出脏数据
*
*
* READ_COMMITTED;实际上业务逻辑中用的最多的也是这个;
* REPEATABLEP_READ;
* @param isbn
* @return
*/
@Transactional(readOnly=true)
public int getPrice(String isbn){
return bookDao.getPrice(isbn);
}
@Transactional
public void mulTx(){
//ioc.getBean("BookSerice");
checkout("Tom", "ISBN-001");
updatePrice("ISBN-002", 998);
int i=10/0;
}
}
5)MulService.java
package com.gome.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
//都是可以设置的;
//传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(使用同一条连接);
//REQUIRED
bookService.checkout("Tom", "ISBN-001");
//REQUIRED REQUIRES_NEW
bookService.updatePrice("ISBN-002", 998);
//int i = 10/0;
}
}
6)TxTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gome.service.BookService;
import com.gome.service.MulService;
public class TxTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
/**
* 有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象
* @throws FileNotFoundException
*
*
* multx(){
* //REQUIRED
* A(){
* //REQUIRES_NEW
* B(){}
* //REQUIRED
* c(){}
* }
*
* //REQUIRES_NEW
* D(){
* DDDD()// REQUIRES_NEW不崩,REQUIRED崩
* //REQUIRED
* E(){
* //REQUIRES_NEW
* F(){
* //10/0(E崩,G崩,D崩,A,C崩)
* }
* }
* //REQUIRES_NEW
* G(){}
* }
*
*
* 10/0(B成功,D整个分支下全部成功)
* 任何处崩,已经执行的REQUIRES_NEW都会成功;
*
* 如果是REQUIRED;事务的属性都是继承于大事务的;
* 而propagation=Propagation.REQUIRES_NEW可以调整
* 默认:REQUIRED;
*
* REQUIRED:将之前事务用的connection传递给这个方法使用;
* REQUIRES_NEW:这个方法直接使用新的connection;
* }
*
*/
@Test
public void test() throws FileNotFoundException {
BookService bookService = ioc.getBean(BookService.class);
//MulService bean = ioc.getBean(MulService.class);
//bean.mulTx();
//bookService.checkout("Tom", "ISBN-001");
//int price = bookService.getPrice("ISBN-001");
//System.out.println("读取到的数据:"+price);
//System.out.println(bookService.getClass());
//效果都没改(相当于回滚了),虽然mulTx的两个方法都开新车
//bookService.mulTx();
System.out.println(bookService.getClass());
//如果是MulService --mulTx()---调用bookService两个方法
//BookService---mulTx()--直接调用两个方法
/***
* MulServiceProxy.mulTx(){
* bookServiceProxy.checkout();
* bookServiceProxy.updatePrice();
* }
*
*
* 本类方法的嵌套调用就只是一个事务;
* BookServiceProxy.mulTx(){
* checkout();
* updatePrice();
* //相当于
* bookDao.updateStock(isbn);
* int price = bookDao.getPrice(isbn);
* bookDao.updateBalance(username, price);
*
* bookDao.updatePrice(isbn, price);
* }
*/
}
}
=========================================================================================
2、xml方式使用声明式事务:
1)tx.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: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.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 1、配置数据源 -->
<bean id="pooledDataSource" 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>
</bean>
<!-- 2、配置JdbcTemplate操作数据库 value="#{pooledDataSource}" ref="pooledDataSource"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{pooledDataSource}"></property>
</bean>
<!-- 3、配置声明式事务
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、开启基于注解的事务式事务;依赖tx名称空间
3)、给事务方法加注解
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--
基于xml配置的事务;依赖tx名称空间和aop名称空间
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、配置出事务方法;
3)、告诉Spring哪些方法是事务方法;
(事务切面按照我们的切入点表达式去切入事务方法)
-->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
<!-- 事务建议;事务增强 advice-ref:指向事务管理器的配置 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!-- 配置事务管理器; 事务建议;事务增强;事务属性;
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明哪些方法是事务方法;
切入点表达式只是说,事务管理器要切入这些方法,
哪些方法加事务使用tx:method指定的 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 都用;重要的用配置,不重要的用注解 -->
</beans>
2)dbconfig.properties
jdbc.user=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/gome
## http://localhost:8080/bookstore
## jdbc:mysql://localhost:3306/jdbc_template
jdbc.driverClass=com.mysql.jdbc.Driver
3)BookDao.java
package com.gome.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 1、减余额
*
* 减去某个用户的余额
*/
public void updateBalance(String userName,int price){
String sql = "UPDATE account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql, price,userName);
}
/**
* 2、按照图书的ISBN获取某本图书的价格
* @return
*/
public int getPrice(String isbn){
String sql = "SELECT price FROM book WHERE isbn=?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
/**
* 3、减库存;减去某本书的库存;为了简单期间每次减一
*/
public void updateStock(String isbn){
String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
jdbcTemplate.update(sql, isbn);
}
/**
* 4、改图书价格
* @param isbn
* @param price
*/
public void updatePrice(String isbn,int price){
String sql = "update book set price=? where isbn=?";
jdbcTemplate.update(sql, price,isbn);
}
}
4)BookService.java
package com.gome.service;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;
import com.gome.dao.BookDao;
@Service
public class BookService {
@Autowired
BookDao bookDao;
public void checkout(String username,String isbn){
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username, price);
int i = 10/0;
}
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn, price);
}
public int getPrice(String isbn){
return bookDao.getPrice(isbn);
}
public void mulTx(){
checkout("Tom", "ISBN-001");
updatePrice("ISBN-002", 998);
//int i=10/0;
}
}
5)MulService.java
package com.gome.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MulService {
private BookService bookService;
public void mulTx(){
bookService.checkout("Tom", "ISBN-001");
bookService.updatePrice("ISBN-002", 998);
}
}
6)TxTest.java
package com.gome.test;
import java.io.FileNotFoundException;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gome.service.BookService;
import com.gome.service.MulService;
public class TxTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
/**
* 有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象
* @throws FileNotFoundException
*
*
* multx(){
* //REQUIRED
* A(){
* //REQUIRES_NEW
* B(){}
* //REQUIRED
* c(){}
* }
*
* //REQUIRES_NEW
* D(){
* DDDD()// REQUIRES_NEW不崩,REQUIRED崩
* //REQUIRED
* E(){
* //REQUIRES_NEW
* F(){
* //10/0(E崩,G崩,D崩,A,C崩)
* }
* }
* //REQUIRES_NEW
* G(){}
* }
*
*
* 10/0(B成功,D整个分支下全部成功)
* 任何处崩,已经执行的REQUIRES_NEW都会成功;
*
* 如果是REQUIRED;事务的属性都是继承于大事务的;
* 而propagation=Propagation.REQUIRES_NEW可以调整
* 默认:REQUIRED;
*
* REQUIRED:将之前事务用的connection传递给这个方法使用;
* REQUIRES_NEW:这个方法直接使用新的connection;
* }
*
*/
@Test
public void test() throws FileNotFoundException {
BookService bookService = ioc.getBean(BookService.class);
bookService.checkout("Tom", "ISBN-001");
//MulService bean = ioc.getBean(MulService.class);
//bean.mulTx();
//bookService.checkout("Tom", "ISBN-001");
//int price = bookService.getPrice("ISBN-001");
//System.out.println("读取到的数据:"+price);
//System.out.println(bookService.getClass());
//效果都没改(相当于回滚了),虽然mulTx的两个方法都开新车
//bookService.mulTx();
System.out.println(bookService.getClass());
//如果是MulService --mulTx()---调用bookService两个方法
//BookService---mulTx()--直接调用两个方法
/***
* MulServiceProxy.mulTx(){
* bookServiceProxy.checkout();
* bookServiceProxy.updatePrice();
* }
*
*
* 本类方法的嵌套调用就只是一个事务;
* BookServiceProxy.mulTx(){
* checkout();
* updatePrice();
* //相当于
* bookDao.updateStock(isbn);
* int price = bookDao.getPrice(isbn);
* bookDao.updateBalance(username, price);
*
* bookDao.updatePrice(isbn, price);
* }
*/
}
}