事务
事务的特性4个:
原子性
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。
一致性
事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。
隔离性
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为隔离性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。防止数据丢失。
持久性
事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
事务的传播行为有7种:
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类 似的操作。 |
事务的隔离级别:
1. ISOLATION_DEFAULT:
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。
3. ISOLATION_READ_COMMITTED:
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
其中 MySQL的默认隔离级别是:REPEATABLE_READ
其中SQL Server的默认隔离级别是:READ_COMMITTED
其中 Orache的隔离级别是:READ_COMMITTED
什么是脏读?什么是幻象读?什么是不可重复读?
1. 脏读 :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
2. 不可重复读 :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
3. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。 如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
什么是丢失更新?
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其他事务的存在。最后的更新将覆盖由其他事务所做的更新,这将导致数据丢失。
关于丢失更新的解决方案:1.乐观锁
2.悲观锁
1.乐观锁的原理是:认为事务不一定会产生丢失更新,让事务进行并发修改,不对事务进行锁定。发现并发修改某行数据时,乐观锁抛出异常。让用户解决。可以通过给数据表添加自增的version字段或时间戳timestamp。进行数据修改时,数据库会检测version字段或者时间戳是否与原来的一致。若不一致,抛出异常。
乐观锁不会锁住任何东西,也就是说,它不依赖数据库的事务机制,乐观锁完全是应用系统层面的东西。
如果使用乐观锁,那么数据库就必须加版本字段,否则就只能比较所有字段,但因为浮点类型不能比较,所以实际上没有版本字段是不可行的。
2.悲观锁是指假设并发更新冲突会发生,所以不管冲突是否真的发生,都会使用锁机制。
悲观锁会完成以下功能:锁住读取的记录,防止其它事务读取和更新这些记录。其它事务会一直阻塞,直到这个事务结束。
悲观锁是在使用了数据库的事务隔离功能的基础上,独享占用的资源,以此保证读取数据一致性,避免修改丢失。
悲观锁可以使用Repeatable Read事务,它完全满足悲观锁的要求。
还有一种是死锁
除了Read UnCommitted和Snapshot,其它类型的事务都可能产生死锁。
当二或多个工作各自具有某个资源的锁定,但其它工作尝试要锁定此资源,而造成工作永久封锁彼此时,会发生死锁。例如:
1. 事务 A 取得数据列 1 的共享锁定。
2. 事务B 取得数据列 2 的共享锁定。
3. 事务A 现在要求数据列 2 的独占锁定,但会被封锁直到事务B 完成并释出对数据列 2 的共享锁定为止。
4. 事务B 现在要求数据列 1 的独占锁定,但会被封锁直到事务A 完成并释出对数据列 1 的共享锁定为止。
等到事务B 完成后,事务A 才能完成,但事务B 被事务A 封锁了。这个状况也称为「循环相依性」(Cyclic Dependency)。事务A 相依于事务B,并且事务B 也因为相依于事务A 而封闭了这个循环。
关于事务的案例:购买股票的案例,买股票,个人金钱减少,对应买的股票的股数增加
下面看一下数据库 和分层结构
首先我们写2个方法,一个是改变Account表的方法 一个是改变Stock表的方法
接口和实现类省略......
直接写实现类中的代码:
首先是Account的方法:
这个方法要实现我们自己写的接口,并且继承jdbcDaoSupport这和类。
我们要写修改的方法,返回值类型是Boolean类型
首先声明一个boolean类型的变量,并且方法带参,参数是对应表的id 和钱数,并声明一个boolean isBuy这个是用于判断是买股还是卖股的,
如果是买股 我们的钱数会减少,我们股数会增加。
如果是卖股相反。
接下来是编写SQL语句的时候了。根据isBuy判断走那个SQL语句
如果SQL语句那边没文件 判读一下真假 返回的是真,否则我们默认返回false;
Stock中的方法相似。。。。。。。
package cn.happy.spring23tx.dao;
import cn.happy.spring23tx.entity.Account;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* Created by linlin on 2017/8/4.
*/
public class AccountDao extends JdbcDaoSupport implements IAccountDao{
public boolean addAccount(Account account) {
return false;
}
public boolean updateAccount(int aid, double money,boolean isBuy) {
boolean flag=false;
String sql=null;
if(isBuy){
sql="update Account set balance=balance-? where aid=?";
}else{
sql="update Account set balance=balance+? where aid=?";
}
int count1=this.getJdbcTemplate().update(sql,money,aid);
if(count1>0){
flag=true;
}
return flag;
}
}
在然后是Stock中的方法;
public boolean updateStock(int sid, double count, boolean isBuy) {
boolean flag=false;
String sql=null;
if(isBuy){
sql="update Stock set count=count+? where sid=?";
}else{
sql="update Stock set count=count-? where sid=?";
}
int count1=this.getJdbcTemplate().update(sql,count,sid);
if(count1>0){
flag=true;
}
return flag;
}
接下来是 service中的代码,
先在接口中编写一个方法 我们会在这个方法中调用我们之前写的所有方法 上面的那俩个 ,因为 增加股数,减少钱数这个是同步执行的。我们在这里声明了一个异常,自己写的异常。StockExection.这个方法返回的是空,并且参数挺多的 有股票ID 股票数 购买人ID 钱数
声明一个boolean类型的变量 我们先给他附一个定值,我们调用别的方法 ,并在每个方法都带上参数isBuy isBuy是啥 在哪个方法里面我们就走哪个SQL语句。
在这里我们抛出一个异常,我们让他走这个异常,然后我们在测试类中会走一个配置 xml的配置 ,在这里面 配置是核心。。。
public void buyStock(int sid,int count,int aid,double money) throws StockExection;
package cn.happy.spring23tx.service;
import cn.happy.spring23tx.dao.IAccountDao;
import cn.happy.spring23tx.dao.IStockDao;
import cn.happy.spring23tx.entity.Account;
import cn.happy.spring23tx.entity.Stock;
import cn.happy.spring23tx.entity.StockExection;
/**
* Created by linlin on 2017/8/4.
*/
public class ServiceImpl implements IService{
private IAccountDao accountDao;
private IStockDao stockDao;
public IAccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public IStockDao getStockDao() {
return stockDao;
}
public void setStockDao(IStockDao stockDao) {
this.stockDao = stockDao;
}
public boolean addAccount(Account account) {
return false;
}
public boolean addStock(Stock stock) {
return false;
}
public void buyStock(int sid, int count, int aid, double money) throws StockExection {
boolean isBuy=true;
accountDao.updateAccount(aid,money,isBuy);
if(1==1){
throw new StockExection();
}
stockDao.updateStock(sid,count,isBuy);
}
}
首先我们看一下配置。
我们先配置2个实现类 和数据源 这些联通以后我们配置service 联通实现类的配置
最重要的一步是我们配置的事务这块,事务 成功---提交,失败----回滚
我们先建立一个bean节点配置事务管理器。
再建立一个bean 几点里面属性包括transaxtionManager,tager,还有一个name=transacrionAttributes的节点里面写的是key value的值,分别是方法名是提交的方法啥的 是事务的隔离级别,提交方式,我们在测试类中的名是事务的id.
transactionManager
<?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 ">
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<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>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="accountDao" class="cn.happy.spring23tx.dao.AccountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="stockDao" class="cn.happy.spring23tx.dao.StockDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="service" class="cn.happy.spring23tx.service.ServiceImpl">
<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="serviceProxys" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"></property>
<property name="target" ref="service"></property>
<property name="transactionAttributes">
<props>
<prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
<prop key="buy*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockExection</prop>
</props>
</property>
</bean>
</beans>
@Test
public void test01(){
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext19tx.xml");
IService i=(IService)ctx.getBean("serviceProxys");
try {
i.buyStock(1,2,1,20000);
} catch (StockExection stockExection) {
}
}
测试类中我们要用xml 使用接口getBean(“。。。”)因为我们底层走的是JDK的动态代理 所以是接口
如果是CGlib我们就可以直接用实现类了,
并 调用方法 把值放进去,因为我们抛出了异常,所要以我们要异常环绕一道。。
我的博客完结了。。。