spring之事物
1.事物:事物是一系列的动作,他们综合在一起才是一个完整的单元,这些动作必须全部完成,如果有一个失败的话,事物就会回滚到最初始的状态,仿佛什么都没有发生过。
事物有四个特性:
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
2.spring中事物有5个隔离级别
隔离级别是指若干个并发事物之间的隔离水平,TransactionDefinition介绍了五种隔离级别
1)ISOLATION_DEFAULT:这是默认值,表示采用使用的数据库的默认的隔离级别
2)ISOLACTION_COMMITTED(读已提交):允许读取其他并发事物已经提交的更新(防止脏读)
3)ISOLACTION_UNCOMMITTED(读未已提交):允许读取其他并发事物还未提交的更新,会导致事物之间的3个缺陷发生,这是速度最快的一个隔离级别,但也是隔离级别最低的一个。
4)ISOLACTION_REPEATABLE_READ(重复读):除非事物自身修改了数据,否则规定事物多次重复读取数据必须相同,(防此脏读,不可重复读)
5)ISOLACTION_SERIALIZABLE(串行化):这是最高的隔离级别,它可以防止脏读、不可重复读和幻读。
注意:并不是所有的资源管理器都支持所有的隔离级别,可针对不同的资源管理使用以上的隔离级别。
3.事物的只读性
在对数据库的操作中,查询是使用最频繁的操作,每次执行查询时都要从数据库中重新读取数据,有时多次读取的数据都是相同的,这样的数据操作不仅浪费了系统资源,还影响了系统速度。对访问量大的程序来说,节省这部分资源可以大大提 升系统速度。
如果将事物声明为只读的,那么数据库可以根据事物的特性优化事物的读取操作,事物的只读性需要配合事物的传播行为共同设置
<prop key="query">PROPAGATION_REQUIRED,readOnly<prop>
4.事物的超时性
这个属性和事务的只读属性一样需要搭配事务的传播行为共同设置,它设置了事务的超时时间,事务本身可能会因某种原因很长没有回应,在这期间事务可能锁定了数据库的表格,这样会出现严重的性能问题。通过设置事务的超时时间,从开始执行事务起,在规定的超时时间内如果没有事务就将它回滚。事务的超时属性以timeout_为前缀和一个整型数字定义,例如:
<prop key="query*">PROPAGATION_REGUIRED,timeout_5,readOnly</prop>
5.编程式事物和声明式事物
编程式事物: Transaction tx = con.beginTransaction(); tx.commit();
声明式事物:用框架去维护事物对象,和事物的提交和回滚
Spring特有的事物传播行为,spring支持7种传播行为,确定客户端和被调用端的事物边界(说的通俗一点就是多个具有事物控制的service的相互调用时所形成的复杂的事物边界控制)。
传播行为 | 含义 |
PROPAGATION_REQUIRED(XML文件中为REQUIRED) | 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚) |
PROPAGATION_SUPPORTS(XML文件中为SUPPORTS) | 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行 |
PROPAGATION_MANDATORY(XML文件中为MANDATORY) | 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常 |
PROPAGATION_NESTED(XML文件中为NESTED) | 表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同PROPAGATION_REQUIRED的一样 |
PROPAGATION_NEVER(XML文件中为NEVER) | 表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW) | 表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。 |
PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED) | 表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行 |
6.在Spring的事物管理中主要涉及下面三个接口,platformTransactionManager,TransactionDefinition,TransactionStatus,其中platformTransactionManager抽取了事物管理过程中的整个流程中的最顶层的操作接口。
7.Spring对事物的提交还是回滚:异常类型是编译时异常,默认是提交的
异常类型是运行时异常,默认是回滚的
8.事物的并发会产生什么问题
1)第一类丢失更新:在没有事物隔离的情况下,两个事物同时更新一行数据,但是第二个事物却中途失败退出,导致对数据的两个修改都失效了。
例如:张三的工资为5000元,事物A中获取工资为5000,事物B获取工资为5000,汇入100,并提交数据库,工资变为5100,随后,事物A发生异常,回滚了,恢复张三的工资为5000元,这样就导致了事物B的跟新丢失了
2)脏读:脏读就是指当一个事物正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另一个事物也访问这个数据,然后使用了这个数据。
例如:张三的工资为5000,事物A中把他的工资为改为8000,但事物A尚未提交。于此同时,事物B正在读取张三的工资,读取到张三的工资为8000。随后,事物A发生异常,而回滚了事物。张三的工资又回滚到5000。最后,事物B读取到的张三工资为8000的数据即为脏数据,事物B做了一次脏读。
3)不可重复读:是指在一个事物内,多次读同一数据。在这个事物还没有结束时,另一个事物也访问该同一数据,那么,在第一个事物中的两次读数据之间,由于第二个事物的修改,那么第一个事物中两次读取数据之间,由于第二个事物的修改,那么第一个事物两次读到的数据可能是不一样的。这样就发生了在同一个事物内两次读到的数据是不一样的,因此称为是不可重复度的。
例如:在事物A中,读取到张三的工资为5000,操作没有完成,事物还没提交。于此同时,事物B把张三的工资改为8000,并提交了事物。随后,在事物A中,在次读取到张三的工资,此时工资变为8000.在一个事物中前后两次读取到的结果并不一致,导致了不可重复度。
4)第二类丢失更新:不可重复读的特利。有两个并发事物同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交,这就会造成第一次读写操作失效。
例如:在事物A中,读取到张三的存款为5000,操作没有完成,事物还没提交。于此同时,事物B,存储1000,把张三的存款改为6000,并提交了事物,这样事物A的更新覆盖了事物B的更新。
5)幻读:是指当前事物不是独立执行时发生的一种现象,例如第一个事物对表中的数据进行了修改,这种修改涉及到表中的全部数据行,同时,第二个事物也修改了这个表中的数据,这种修改是向表中插入一行新的数据,那么,以后就会发生操作第一个事物的用户发向表中还有没有修改的数据行,就好像发生了幻觉一样。
例如:目前工资为5000的员工有10人,事物A读取所有工资为5000的人数为10人。此时,事物B插入一条工资也为5000的记录,这时,事物A再次读取工资为5000的员工,记录为11人,此时产生了幻读。
提示:不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来的发现值不一样了。
幻读的重点在于新增或者删除,同样的条件,第一次和第二次读出来的记录数不一样
9.事物的实例
买股票的事件
01.创建实体类
package cn.happy.entity; /* * 银行账户 * */ public class Account { private Integer aid; //帐户id private String aname; //帐户名 private double blance; //帐户余额 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 getBlance() { return blance; } public void setBlance(double blance) { this.blance = blance; } }
Stock类
package cn.happy.entity; /* * 股票类 * */ public class Stock { private Integer sid; //股票id private String sname; //股票名 private Integer 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 Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } }
异常类
StockException
/* * 编译时异常 spring默认 自动提交事物 * 运行时异常 spring默认 自动回滚事物 * */ public class StockException extends Exception { public StockException() { super(); } public StockException(String message) { super(message); } }
02.创建dao层
public interface IAccountDao { //核心业务 public void updateAccount(int aid,double money,boolean isBuy); }
public interface IStockDao { public void updateStock(int sid,int count,boolean isBuy); }
03.创建dao的实现类
import cn.happy.dao.IAccountDao; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class IAccountDaoImpl extends JdbcDaoSupport implements IAccountDao { public void updateAccount(int aid, double money, boolean isBuy) { String sql = null; if(isBuy){
//购买股票 sql = "update account set blance=blance-? where aid=?"; }else{
//抛出股票 sql = "update account set blance=blance+? where aid=?"; } this.getJdbcTemplate().update(sql,money,aid); } }
import cn.happy.dao.IStockDao; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class IStockDaoImpl extends JdbcDaoSupport implements IStockDao { String sql = null; public void updateStock(int sid, int count, boolean isBuy) { if(isBuy){ //购买股票 sql="update stock set count=count+? where sid=?"; }else{ //抛出股票 sql="update stock set count=count-? where sid=?"; } //对数据进行增 删 改 都用update this.getJdbcTemplate().update(sql, count, sid); } }
04. 创建service层
public interface IStockService { public void buyStock(int sid,int counts,int aid,double money); }
05.创建service实现类
import cn.happy.dao.IAccountDao; import cn.happy.dao.StockDao; import cn.happy.entity.StockException; public class AccoutServiceImpl implements IAccountService { //植入AccountDao private IAccountDao accountDao; //植入StockDao private StockDao stockDao; public void buyStock(int sid, int count, int aid, int money) throws StockException { boolean isBuy = true; //购买股票 accountDao.updateAccount(aid,money,isBuy); //手动制造异常 if(1==1) { throw new StockException(); } stockDao.updateStock(sid,count,isBuy); } public IAccountDao getAccountDao() { return accountDao; } public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } public StockDao getStockDao() { return stockDao; } public void setStockDao(StockDao stockDao) { this.stockDao = stockDao; } }
06.编写配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--1.识别jdbc.properties文件--> <context:property-placeholder location="jdbc.properties"></context:property-placeholder> <!--2.建立数据远,${} spring内置的数据源 DriverMangerDateSource--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> </bean> <!--4.dao的配置--> <bean id="AccountDao" class="cn.happy.dao.impl.IAccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="StockDao" class="cn.happy.dao.impl.IStockDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!--5.service的配置--> <bean id="stockService" class="cn.happy.service.StockServiceImpl"> <property name="accountDao" ref="AccountDao"></property> <property name="stockDao" ref="StockDao"></property> </bean> <!--事物管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事物,拦截业务方法--> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!--目标类型(要增强的类)--> <property name="target" ref="stockService"></property> <property name="transactionManager" ref="transactionManager"></property> <!--增强--> <property name="transactionAttributes"> <props> <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockException</prop> </props> </property> </bean> </beans>
07.编写测试类
public class TestSW { @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IStockService stockService = (IStockService)context.getBean("stockService"); stockService.buyStock(34,10,3,1000); } }
在发生异常时,余额和股票数都不会变。