数据库事物

 

@Transactional
类级的注解、适用于类中所有的public的方法,当某些方法不需要事务,类上加注解需要事务的方法加注解
默认级别
PROPAGATION_REQUIRED(spring 默认)  如果已存在一个事务中就用已有事务,没有就创建
PROPAGATION_REQUIRES_NEW  不管是否在事务中,都会新建
PROPAGATION_SUPPORTS 如果已存在一个事务中就用已有事务,没有就不使用事务
事务隔离级别
read_uncommitted读取未提交
read_commited读取已提交
repeatable_read可重复读
serializable严格的顺序执行

 

DEFAULT:采用数据库默认隔离级别
READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据
READ_COMMITTED:保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
REPEATABLE_READ:mysql默认 保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大

@Transactional(isolation = Isolation.READ_UNCOMMITTED)读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)读取已提交数据(会出现不可重复读和幻读) 避免脏读
@Transactional(isolation = Isolation.REPEATABLE_READ)可重复读(会出现幻读) 避免了不可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)串行化


第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。
虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的
不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任
第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
幻觉读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样

(1)READ_UNCOMMITTED
  这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。
  解决第一类丢失更新的问题,但是会出现脏读、不可重复读、第二类丢失更新的问题,幻读 。
(2)READ_COMMITTED
  保证一个事务修改的数据提交后才能被另外一个事务读取,即另外一个事务不能读取该事务未提交的数据。
  解决第一类丢失更新和脏读的问题,但会出现不可重复读、第二类丢失更新的问题,幻读问题
(3)REPEATABLE_READ
  保证一个事务相同条件下前后两次获取的数据是一致的

       解决第一类丢失更新,脏读、不可重复读、第二类丢失更新的问题,但会出幻读。
(4)SERIALIZABLE
  事务被处理为顺序执行。
  解决所有问题



orcle 不支持脏读取

对于不同事务间的锁定,数据一致性冲突,ms sql server 有四种处理方式
read uncommitted:可以脏读,当然更可能发生不可重复的读 select 不产生任何锁
read committed:不能脏读,但可以发生不可重复的读... select 对被选中的行加共享锁(页内锁定行过多会上升为页锁),并对表加意图共享锁
reapetable read:事务内不允许别的对话update被锁定的数据 锁定方式同上,不同的是客户端得到数据后并不释放锁,而要等本事务提交后才释放
serializable read:事务内不允许别的对话插入数据,更别说update了,这种锁很厉害,直接一个表级共享锁,不允许别的对话对表的任何更改

oracle通常只有上述2,4类型(外加一个read only),oracle通常的select并不产生任何锁,而且保证读取数据的一致性,不过以我个人的实验和二者的机理得知在一般情况下oracle的回滚段机制并不比ms sqlserver 回传行数据给客户后立即释放行共享锁快(特别是delete),有时间以后详细说说
oracle的这两种方式的select同样不产生任何锁,不同的是第一种select可以看到别的事务已经commit的数据,而第二种需要在本事务提交后才能看到更改后的数据(即使发生在期间的别的事务已提交)

 

oracle
READ_COMMITTED 同一事务两次读取之间,又有事务更改并提交改行,造成两次读取不一致
0,两个事务同时读取到一条数据,事务A修改提交,事务B再次修改提交,造成修改丢失
1,测试时注意同一事务中两次查询字段不要一致,sql缓存
2,mybatis配置文件 useCache="false"(不用二级缓存) flushCache="true"(清空缓存) 防止sql缓存,可测试不可重复读
SERIALIZABLE 事务A中先查询,oracle客户的更改提交改数据(或新增加一条数据),A再做update,报异常
0,两个事务同时读取到一条数据,事务A修改提交,事务B再次修改提交,B修改失败(若事务B不做读取可修改成功)
1,如使用READ_COMMITTED级别则不会
2,如果不先查询,也能再次修改成功

mysql
同一事务中多次查询结果一致,不受其他事物干扰
READ_UNCOMMITTED 会读取到另一事务未提交的数据
READ_COMMITTED 不会读取其他事务没有提交的数据
REPEATABLE_READ 两个事务同时读取一条数据,B修改提交后,A读取的数据不变   默认隔离级别
SERIALIZABLE 等同于oracle

REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据  无法测试
即使READ_COMMITTED下,在一个事务未提交时,另一个事务是无法提交修改的
undo:
用来保证数据读一致的。数据再修改前会放入undo空间,如果修改失败了回滚会用到undo中的信息。还有其他用户正在被修改的数据时是都的undo表空间的数据,保证数据一致。redo是联机日志,没有单独的表空间

REPEATABLE_READ:
RR隔离级别下的一致性读,不是以begin开始的时间点作为snapshot建立时间点,而是以第一条select语句的时间点作为snapshot建立的时间点
对同一个表或者不同表进行的第一次select语句建立了该事务中一致性读的snapshot. 其它update, delete, insert 语句和一致性读snapshot的建立没有关系。在snapshot建立之后提交的数据,一致性读就读不到,之前提交的数据就可以读到
事务的起始点其实是以执行的第一条语句为起始点的
http://www.cnblogs.com/digdeep/p/4947694.html

READ_COMMITTED:
事务中每一次读取都是以当前的时间点作为判断是否提交的实际
 

 

innodb的默认事务隔离级别是rr(可重复读)。它的实现技术是mvcc。基于版本的控制协议。该技术不仅可以保证innodb的可重复读,而且可以防止幻读。但是它防止的是快照读,也就是读取的数据虽然是一致的,但是数据是历史数据。如何做到保证数据是一致的(也就是一个事务,其内部读取对应某一个数据的时候,数据都是一样的),同时读取的数据是最新的数据。innodb提供了一个间隙锁的技术。也就是结合grap锁与行锁,达到最终目的。当使用索引进行插入的时候,innodb会将当前的节点和上一个节点加锁。这样当进行select的时候,就不允许加x锁。那么在进行该事务的时候,读取的就是最新的数据。
实现:
1. 快照读(snapshot read)
简单的select操作(不包括 select ... lock in share mode, select ... for update)
2.当前读(current read)
select ... lock in share mode
select ... for update
insert
update
delete
在RR级别下,快照读是通过MVVC(多版本控制)和undo log来实现的,当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。
所以从上面的显示来看,如果需要实时显示数据,还是需要通过加锁来实现。这个时候会使用next-key技术来实现。
总结:在mysql中,提供了两种事务隔离技术,第一个是mvcc,第二个是next-key技术。这个在使用不同的语句的时候可以动态选择。不加lock inshare mode之类的就使用mvcc。否则使用next-key。mvcc的优势是不加锁,并发性高。缺点是不是实时数据。next-key的优势是获取实时数据,但是需要加锁。同时需要注意几点:1.事务的快照时间点是以第一个select来确认的。所以即便事务先开始。但是select在后面的事务的update之类的语句后进行,那么它是可以获取后面的事务的对应的数据。2.mysql中数据的存放还是会通过版本记录一系列的历史数据,这样,可以根据版本查找数据

mysqld -install
net start mysql
mysqld-nt --init-file=C:\mysql-init.txt
mysql -u root -p

sqlSession = sqlSessionFactory.openSession();
mapper = sqlSession.getMapper(UserMapper.class);
mapper.update(map);

net start oracleserviceORCL
Lsnrctl start
net stop oracleserviceORCL
Lsnrctl stop
管理员身份运行plsql 或 管理员身份打开cmd sqlplus命令 连接数据库 再运行plsql

repo
C:\Program Files (x86)\Java\jdk1.6.0_05\
zooker启动时需关闭:
service iptables stop

 

 

 

 

 

 

 

 

 

 

Spring对事务的解决办法其实分为2种:编程式实现事务,AOP配置声明式解决方案。
http://jinnianshilongnian.iteye.com/blog/1496953

Spring提供了许多内置事务管理器实现,常用的有以下几种:

DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS框架的事务管理;
HibernateTransactionManager:位 于org.springframework.orm.hibernate3或者hibernate4包中,提供对单个 org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持 Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器;

Spring不仅提供这些事务管理器,还提供对如JMS事务管理的管理器
两个不依赖于应用服务器的开源JTA事务实现组件:JOTM和Atomikos Transactions Essentials
具体用法参考http://jinnianshilongnian.iteye.com/blog/1439900

这篇博客讲解了对于JDBC和JTA分别用PlatformTransactionManager实现和使用TransactionTemplate实现编程式事务管理:http://jinnianshilongnian.iteye.com/blog/1441271

编程式实现事务
Spring提供两种编程式事务支持:直接使用PlatformTransactionManager实现和使用TransactionTemplate模板类,用于支持逻辑事务管理。
如果采用编程式事务推荐使用TransactionTemplate模板类。

Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如Hibernate)通过实现策略接口 PlatformTransactionManager,从而能支持各种数据访问框架的事务管理,PlatformTransactionManager 接口定义如下:

public interface PlatformTransactionManager {
//返回一个已经激活的事务或创建一个新的事务
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}

 

关于TransactionDefinition接口和TransactionStatus接口:
http://jinnianshilongnian.iteye.com/blog/1439900

Spring声明式事务
在日常开发中,用的最多的就是声明式事务了,下面将介绍SpringJdbc的声明式事务的配置方法:

<context:component-scan base-package="com.chou.spring.jdbc"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClass" value="${db.driverClass}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />

<!-- Connection Pooling Info -->
<property name="initialPoolSize" value="1" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="15" />
<property name="maxIdleTime" value="1800" />
<property name="maxStatements" value="0" />
</bean>

<bean id="jdbcTemplateDao" class="com.chou.spring.jdbc.dao.JdbcTemplateDao" >
<property name="dataSource" ref="dataSource" />
</bean>

<!-- JDBC事务管理器 -->
<bean id="jdbctTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--事务通知-->
<tx:advice id="txAdvice" transaction-manager="jdbctTxManager">
<tx:attributes>
<tx:method name="domain*"/>
</tx:attributes>
</tx:advice>
<!--事务切面-->
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.chou.spring.jdbc.service.JdbcTemplateService.*(..))"/>
</aop:config> 

public class JdbcTemplateDao extends JdbcDaoSupport{

public void save() {
String insertSql = "insert into tab_item values(?,?,?)";
Assert.isTrue(getJdbcTemplate().update(insertSql, new Object[]{6, "HP", "PT540"}) == 1, "插入失败");
}

public void delete() {
String deleteSql = "delete tab_item where id = ?";
Assert.isTrue(getJdbcTemplate().update(deleteSql, new Object[]{6}) == 1, "删除失败");
}

public void update() {
String updateSql = "update tab_item set itemno = ?, itemname = ? where id = ?";
Assert.isTrue(getJdbcTemplate().update(updateSql, new Object[]{"HP", "PT555", 6}) == 1, "修改失败");
}
}

 

/**
*
* @author Chou
* @since 2012-9-9
* 把事务定义在Service层是为了避免报错:
* All calls to this method via a proxy will be routed directly to the proxy.
* 这是是事务转移问题,你如果在控制层加入事务就不会有提示了,也没有警告,
* 一般很多人在final DAO里加入事务那是有警告的,
* 如果配置文件定义了AOP获取代理对象是proxy-target-class="true"即采用CGLIB方式
* 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,
* 通过修改其字节码生成子类并继承你写的类,然后在你的基础上加事物管理,
* 而JdbcDaoSupport中的setDataSource是final的他继承不了
* 当然你可以无视它,也没有问题。
*/
@Service
public class JdbcTemplateService {

@Autowired
private JdbcTemplateDao jdbcTemplateDao;

public void domain(){
jdbcTemplateDao.save();
int i = 2/0;//这里出错了,事务就会回滚,之前的save就无效了
jdbcTemplateDao.update();
jdbcTemplateDao.delete();
}
}

//main方法
String[] configLocations = new String[] {"applicationContext.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);
JdbcTemplateService j = ctx.getBean(JdbcTemplateService.class);
j.domain();

<tx:advice/>配置详解

<tx:advice id="……" transaction-manager="……">
<tx:attributes>
<tx:method name="*"
propagation="REQUIRED"
isolation="DEFAULT"
timeout="-1"
read-only="true"
no-rollback-for=""
rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>

<!-- 最常用的配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="put*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="count*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config> 

 


XML形式的事务配置<tx:method >的属性详解
属性

类型

默认值

说明
propagation Propagation枚举 REQUIRED 事务传播属性
isolation isolation枚举 DEFAULT(所用数据库默认级别) 事务隔离级别
readOnly boolean false 是否才用优化的只读事务
timeout int -1 超时(秒)
rollbackFor Class[] {} 需要回滚的异常类
rollbackForClassName String[] {} 需要回滚的异常类名
noRollbackFor Class[] {} 不需要回滚的异常类
noRollbackForClassName String[] {} 不需要回滚的异常类名

 

readOnly
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高,所以这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。

timeout
在属性中还有定义“timeout”值的选项,指定事务超时为几秒。一般不会使用这个属性。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。

Isolation Level(事务隔离等级)的5个枚举值
为什么事务要有Isolation Level这个属性?先回顾下数据库事务的知识:
第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。
虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。
不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。

具体关于事务机制可以看我以前的博客:http://zhou137520.iteye.com/admin/blogs/1638574

当遇到以上这些情况时我们可以设置isolation下面这些枚举值:
DEFAULT:采用数据库默认隔离级别
SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大;
REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
READ_COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

关于propagation属性的7个传播行为
REQUIRED:指定当前方法必需在事务环境中运行,如果当前有事务环境就加入当前正在执行的事务环境,如果当前没有事务,就新建一个事务。这是默认值。
SUPPORTS:指定当前方法加入当前事务环境,如果当前没有事务,就以非事务方式执行。
MANDATORY:指定当前方法必须加入当前事务环境,如果当前没有事务,就抛出异常。
REQUIRES_NEW:指定当前方法总是会为自己发起一个新的事务,如果发现当前方法已运行在一个事务中,则原有事务被挂起,我自己创建一个属于自己的事务,直我自己这个方法commit结束,原先的事务才会恢复执行。
NOT_SUPPORTED:指定当前方法以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,等我以非事务的状态运行完,再继续原来的事务。
NEVER:指定当前方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常,只有没关联到事务,才正常执行。
NESTED:指定当前方法执行时, 如果已经有一个事务存在,则运行在这个嵌套的事务中.如果当前环境没有运行的事务,就新建一个事务,并与父事务相互独立,这个事务拥有多个可以回滚的保证 点。就是指我自己内部事务回滚不会对外部事务造成影响,只对DataSourceTransactionManager事务管理器起效。

注解形式@Transactional实现事务管理
注意@Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能。

默认情况下,一个有事务的方法,遇到RuntiomeException时会回滚。遇到受检查的异常是不会回滚的,要想所有异常都回滚,要加上属性rollbackFor={Exception.class}

<!-- 事务管理器配置 -->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="txManagercashier" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="cashierDataSource"></property>
</bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />

transaction-manager:指定事务管理器名字,默认为transactionManager,当使用其他名字时需要明确指定;
proxy-target-class:默认false表示使用JDK代理,如果为true将使用CGLIB代理
order:定义事务通知顺序,默认Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给AOP来处理。

建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制是没问题,因为其使用基于接口的代理;而使用使用CGLIB代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”。
http://jinnianshilongnian.iteye.com/blog/1508018这篇博客讲解了基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

@Transactional//放在这里表示所有方法都加入事务管理
public class AnnotationUserServiceImpl implements IUserService {
private IUserDao userDao;
private IAddressService addressService;

@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
public void save(UserModel user) {
userDao.save(user);
user.getAddress().setUserId(user.getId());
addressService.save(user.getAddress());
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=true,
isolation=Isolation.READ_COMMITTED)
public int countAll() {
return userDao.countAll();
}
//setter...
}


总结


编程式事务是不推荐的,即使有很少事务操作,Spring发展到现在,没有理由使用编程式事务,只有在为了深入理解Spring事务管理才需要学习编程式事务使用。
推荐使用声明式事务,而且强烈推荐使用<tx:tags>方式的声明式事务,因为其是无侵入代码的,可以配置模板化的事务属性并运用到多个项目中。
而@Transaction注解事务,不过如果一个项目模块太多,service方法太多导致每个方法都要手动去加注解,是不是很麻烦,也容易出错。如果一个项目结构清晰,分层明确,那么标签形式的配置将是最直观和方便的办法。
总之,能保证项目正常工作的事务配置就是最好的。

posted @ 2017-12-03 14:59  苍天一穹  阅读(406)  评论(0编辑  收藏  举报