首先,什么是事务?
事务就是业务上的一个逻辑单元,它能够保证其中对数据所有的操作,要么成功,要么失败。
其次,事务的特性有哪些?
1.原子性。
例如,转账,A账户减少,B账户增加。虽然是两条 DML语句,但是被当做是一个整体,一次事务。两条语句只能同时成功或者同时失败。
2.一致性。
账户A和B,要么都是转账前的状态,要么都是转账后的状态。(不能A账户的钱减少了但是B账户的钱没有增加)。
3.隔离性。
虽然在某个时间段很多人都在转账,但是每个人的转账都是在一个自己的事务中,彼此不会影响。
4.持久性。
事务提交成功后,数据修改永远生效。
在考虑事务的隔离级别之前,需要认识到如果不考虑事务的隔离性,会发生的异常情况:
1.脏读
一个事务读取了另外一个事务未提交的数据。(会对系统的并发处理带来很大的隐患)
2.不可重复读
在同一个事务内,多次读同一个数据时,发现该数据已经 被另一个已经提交的事务修改。(在一个事务内两次读到的数据时是不一样的。)
3.幻读
一个事务根据相同的查询条件,重新执行查询,返回的记录中包含与前一次执行查询返回的记录不同的行。
以上这三种 情况都是同时进行的几个事务对相同的数据进行读取时造成的。
如何处理这几种异常呢?
ANSI SQL-92标准中定义了以下几种事务隔离级别:
1.Read Uncommitted
最低等级的事务隔离,仅仅保证读取过程中不会读到非法数据。(三种异常情况均可能发生)
2.Read Committed
避免了“脏读”。一个select查询只能查看到查询开始之前提交的数据。在查询执行时,其他事务修改或者提交的数据它看不到(数据库默认的事务隔离级别)
3.Repeatable Read
避免了“脏读”和“不可重复读”。一个事务不可能更新由另一个事务读取但是没有提交的数据。应用并不广泛。因为它可能出现幻读。同时带来更多性能损失。
4.Serializable
ORACLE数据库只支持Read Committed 和Serializable两种隔离级别。另外自定义了一个Read Only的隔离级别,只允许读,不允许改。避免不可重复读和幻读
三种都能避免。所有事务串行,而不是并行。
Spring事务的隔离级别
1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。
3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读。
使用Spring AOP实现声明式事务管理
1.基于XML配置(使用较多)
(1)配置事务管理类
<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
在spring的配置中配置数据源(dataSource)、事务管理器,事务管理器使用不同的orm框架事务管理器类就不同,mybatis 是
org.springframework.jdbc.datasource.DataSourceTransactionManager 。而hibernate事务管理器为org.springframework.orm.hibernate3.HibernateTransactionManager
(2)配置事务属性
<!-- 配置事务的属性 -->
<tx:advice id="TestAdvice" transaction-manager="transactionManager">
<!--配置事务传播性,隔离级别以及超时回滚等问题 -->
<tx:attributes>
<tx:method name="search*" propagation="REQUIRED" read-only="true" isolation="DEFAUT" TIMEOUT="-1" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
事务属性在<tx:method>中进行设置,Spring支持对不同的方法设置不同的事务属性,所以可以为一个<tx:advice>设置多个<tx:method>,其中name属性指定匹配的方法(这里需要对这些方法名进行约定,如果事务切入点在service上,则最好和Dao的方法命名区分开,也不要使用get set关键字,防止和属性的getter setter发生混淆)
事务有以下几个常用属性:
a.read-only:设置该事务中是否允许修改数据。(对于只执行查询功能的事务,设置为TRUE可以提高事务的执行速度)
b.propagation:事务的传播机制。一般设置为required。可以保证在事务中的代码只在当前事务中运行,防止创建多个事务。
c.isolation:事务隔离级别。不是必须的。默认值是default。
d.timeout:允许事务运行的最长时间,以秒为单位。
e.rollback-for:触发回滚的异常。
f.no-rollback-for:不会触发回滚的异常。
***实际开发中,对于只执行查询功能的事务,要设置read-only为TRUE,其他属性一般使用默认值即可。
(3)配置事务的AOP切入点
<aop:config>
<!--配置事务切点 -->
<aop:pointcut id="services"
expression="execution(public* com.pb.service.*.*(..))" />
<aop:advisor pointcut-ref="services" advice-ref="TestAdvice" />
</aop:config>
该设置的含义是:对于com.pb.service.impl包及子包下的所有类的所有公共方法进行切入。(被切入的 方法经过<tx:method>筛选)web应用程序最合适的事务切入点是Service的方法上。
----通过以上三个步骤设置好声明式事务后,当Service中 的业务方法被调用之前,Spring会获取事务对象并启动事务。并使用try-catch-finally来处理异常。业务方法执行成功则会提交事务,默认情况下如果抛出了RuntimeException 或者Rrror 对象就会回滚事务。(注意: 这里注意一下,在tx:method中配置了rollback_for 中配置的Exception 这个是运行时的异常才会回滚不然其他异常是不会回滚的!)
2.使用annotation配置
*1.在事务管理的dao实现类之前标注@Transactional
*2.在要进行事务管理的方法前加上@Transactional(propagation= Propagation.REQUIRED)
*3.在配置文件中指定驱动:<tx:annotation-driven transaction-manager="transactionManager" />
package demo.spring.dao;
import java.util.Iterator;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import demo.spring.entity.Person;
@Transactional//将此类进行事务管理
public class PersonDaoImpl implements PersonDao {
private JdbcTemplate jt;
public void setDataSource(DataSource dataSource){
jt = new JdbcTemplate(dataSource);
}
@Override
public void insert(long id, String name, int age) {
jt.update("insert into person values('"+id+"','"+name+"','"+age+"')");
}
@Transactional(propagation= Propagation.REQUIRED)//定义要事务管理的方法,指定传播行为
public void batchInsert(List persons) {
for(Iterator it = persons.iterator(); it.hasNext(); ){
Person p = (Person) it.next();
insert(p.getId(),p.getName(),p.getAge());
}
}
}