…Spring 的声明式事务管理是通过 AOP 技术实现的事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
…声明式事务管理最大的优点是不需要通过编程的方式管理事务,因而不需要在业务逻辑代码中掺杂事务处理的代码,只需相关的事务规则声明便可以将事务规则应用到业务逻辑中。通常情况下,在开发中使用声明式事务处理不仅因为其简单,更主要的是因为这样使得纯业务代码不被污染,极大地方便了后期的代码维护。
…与编程式事务管理相比,声明式事务管理唯一不足的地方是最细粒度只能作用到方法级别,无法做到像编程式事务管理那样可以作用到代码块级别。但即便有这样的需求,也可以通过变通的方法进行解决,例如可以将需要进行事务处理的代码块独立为方法等。如果使用的是声明式事务,那么当你的业务方法不发生异常(或者发生异常 但该异常也被配置信息允许提交事务)时, Spring 就会让事务管理器提交事务,而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务。
…Spring的声明式事务管理可以通过两种方式实现,一是基于XML的方式,二是基于@Transactional注解的方式
public class Account {
private Integer aid;
private String aname; // 买方账户
private double balance;//账户余额
public Account() {
super();
}
public Account(String aname, double balance) {
super();
this.aname = aname;
this.balance = balance;
}
public Integer getAid() {
return aid;
}
public void setAid(Integer aid) {
this.aid = aid;
}
public String getAname() {
return aname;
}
public void setAname(String aname) {
this.aname = aname;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account [aid=" + aid + ", aname=" + aname + ", balance=" + balance + "]";
}
}
public class Stock {
private Integer sid;
private String sname;//所买的物品的名称
private int count;//所买物品的数量
public Stock() {
super();
}
public Stock(String sname, int count) {
super();
this.sname = sname;
this.count = count;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Stock [sid=" + sid + ", sname=" + sname + ", count=" + count + "]";
}
}
service层
使用了@Transactional注解
public class BuyStockServiceImpl implements IBuyStockService {
private IAccountDao adao;
private IStockDao sdao;
public void setAdao(IAccountDao adao) {
this.adao = adao;
}
public void setSdao(IStockDao sdao) {
this.sdao = sdao;
}
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
@Override
public void openAccount(String aname, double money) {
adao.insertAccount(aname,money);
}
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
@Override
public void openStock(String sname, int amount) {
sdao.insertStock(sname,amount);
}
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
@Override
public void buyStock(String aname, double money, String sname, int amount) throws BuyStockException{
boolean isBuy =true;
adao.updateAccount(aname,money,isBuy);
if(1==1) {
throw new BuyStockException("购买股票异常");
}
sdao.updateStock(sname,amount,isBuy);
}
}
xml
<!-- 注册数据源:Spring内置连接池 -->
<bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"> </property>
<property name="url" value="jdbc:mysql://localhost:3306/text?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 注册Dao -->
<bean id="accountDao" class="com.spring.dao.AccountDaoImpl">
<property name="dataSource" ref="myDataSource"> </property>
</bean>
<!-- 注册Dao -->
<bean id="stockDao" class="com.spring.dao.StockDaoImpl">
<property name="dataSource" ref="myDataSource"> </property>
</bean>
<!-- 注册Service -->
<bean id="buyStockService" class="com.spring.service.BuyStockServiceImpl">
<property name="adao" ref="accountDao"> </property>
<property name="sdao" ref="stockDao"> </property>
</bean>
<!-- 注册事务管理器 -->
<bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"></property>
</bean>
<!-- 使用声明式事务需要配置注解驱动,只需要加入如下的即可:-->
<tx:annotation-driven transaction-manager="myTransactionManager"/>
测试类:
public class Text {
private IBuyGameService service;
@Before
public void before() {
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
service=(IBuyGameService)ctx.getBean("buyStockService");
}
@Test
public void test01() {
service.openBuy(" 张三", 1000);
service.openSell("游戏币", 0);
}
@Test
public void test02() throws BuyGameException {
service.buyGame("张三", 200, "游戏币", 10);
}
}
<!-- 注册事务管理器 -->
<bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"></property>
</bean>
<!-- 生成事务代理对象 -->
<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="myTransactionManager"></property>
<property name="target" ref="buyStockService"></property>
<property name="transactionAttributes">
<props>
<!-- key代表的是业务方法的正则式匹配,而其内容可以配置各类事务定义参数-->
<prop key="open*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
<!-- (-)异常:表示发生指定异常后回滚,这时的异常通常时受查异常
(+)异常:表示发生指定异常后提交,这时的异常通常是运行时异常 -->
<prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
</props>
</property>
</bean>
…@Transaction注解可以使用在方法或者类上面,在 Spring IoC 容器初始化时, Spring 会读入这个注解或者XML配置的事务信息,并且保存到 个事务定义类里面( TransactionDefinition 接口的
子类),以备将来使用。当运行时会让 Spring 拦截注解标注的某一个方法或者类的所有方法。
…数据库事务正确执行的4个基础要素是原子性(Atomicity )、一致性( Consistency )、隔离性 Clso lation )和持久性( Durabi lity)。
①原子性: 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没被执行过一样。
②一致性:指一个事务可以改变封装状态(除非它是一个只读的〉。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
③隔离性: 它是指两个事务之间的隔离程度。
④持久性: 在事务完成以后,该事务对数据库所做的更改便持久保存在数据库之中,并不会被回滚。
…这里的原子性、一致性和持久性都比较好理解,而隔离性就不一样了,它涉及了多个事务并发的状态。首先多个事务并发会产生数据库丢失更新的问题,其次隔离性又分为多个层级。
假设一个场景,一个账户存在互联网消费和刷卡消费两种形式,而一对夫妻共用这个账户。老公喜欢刷卡消费,老婆喜欢互联网消费。那么可能会产生如表所示的场景:
整个过程中只有老公消费了1000元,而在最后的 T6 时刻,老婆回滚事务,却恢复了原来的初始值余额10000元,这显然不符合事实。这样的两个事务并发, 一个回滚、 一个提交成功导致不一致,我们称为第一类丢失更新。
… 整个过程中存在两笔交易,一笔是老公的请客吃饭, 另一笔是老婆 的网购,但是两者都提交了事务,由在不同的事务中,无法探知其他事务的操作,导致两 者提交后,余额都为9000元,而实际正确的应为8000 元,这就是第二类丢失更新。
…脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。
…在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据并修改数据。那么,在第一个事务的两次读数据之间。由于另一个事务的修改,那么第一个事务两次读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。就比如两个并发的事务,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。避免了更新丢失,却可能出现脏读、不可重复读、幻读的情况。
读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。该隔离级别避免了脏读,但是却可能出现不可重复读、幻读的情况。
读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。避免了不可重复读取和脏读,但是有时可能出现幻读。这中隔离级别可以通过“共享读锁”和“排他写锁”实现。