什么是事务
事务是一系列操作组成的工作单元,该工作单元具有不可分割性,一损俱损。满足ACID(原子性,一致性,隔离性,持久性)
事务按分布式划分可以分为本地事务,和分布式事务
分别由JDBC事务和JTA事务与其对应。
Transaction其实在某些具体业务上,是相当实用的利器。但是我在工作之前对他的认识只是停留在概念的层面,现在想想还是很有必要好好总结一下的。
事务最经典的例子就是银行转账问题,A用户转账5000元给B用户,如果在不发生任何意外的情况下,那么是一点问题没有的,但是如果这两部操作中间出现了意外(例如发生了异常),很有可能这500元只转出了,并没有转入。那么这个问题的根本原因是两个操作在代码层面来看是相互独立的,并不具备原子性导致的。Spring又是怎么解决这个问题的呢?
再来通过代码层面分析一下这个问题,转出的时候,我们通过DataSource拿到一个Connection对象,当执行没有异常的时候,直接提交事务,代码并不知道还有转入操作的存在。
所以spring针对这一点,如果在Service层的一个方法开启了事务,那么会关闭在这个方法中调用Dao方法自动提交事务的属性,等到整个service执行后再做提交,具体的步骤如下:
- 获取DataSource对象
- 通过DataSource对象获取对应的Connection对象
- 关闭事务的自动提交机制,在Connection对象中
- 把Connection对象绑定到当前线程中
- 在Dao中通过取得当前线程的Connection然后执行操作
- 如果整个Service都ok则Commit,否则进行rollback
事务的隔离机制
数据库的并发的问题,应运而生:例如说脏读,虚读,第一类丢失更新,第二类丢失更新。
解决的办法就是通过不同的隔离机制,进行隔离:
- Read Uncommited
- Read Commited
- Repeatable Read
- Serializable
Oracle 默认使用Read Comited, Mysql默认使用 Repeatable Read。
隔离机制越高,性能越差。
事务的传播规则
在一个事务方法中,调用了别的事务,应该按照什么规则进行传递。
传播规则一共分为七种:
现在有这样一种情况A方法调用了B方法。
- required:必须存在一个事务,如果有事务,则加入到该事务,如果没有则新建。解读:A如果有事务,B就用A的事务,如果A没有事务,则B新建一个事务
- supports:如果有事务,则用。没有则不用。解读:A如果有事务,B就用A的,A如果没有,B则不用事务。
- Mandatory:必须存在事务,当前如果有事务,则用。没有则直接报异常。解读:A如果有事务,B就用A的事务,如果没有,则直接报错。
- required_new: 不管当前是否存在事务,都会创建一个新的,这个在平常比较多。
- not_supports: 以非事务方式执行,如果当前存在事务,则将当前事务挂起 解读:A有自己的事务,B不使用A的事务,B不参与A事务的管理。
- never:不支持事务,当前如果存在事务,则抛出异常。
- nested:寄生事务。如果内部事务进行回滚,不会影响到外部事务,如果外部事务回滚了,内部事务会被影响。
Spring对事务的支持
Spring的事务管理一定要在业务层上的
-
PlatformTransactionManager 根据TransactionBefination提供的事务信息,进行配置。是多种事务管理器的基类。Hibernate使用的是HibernateTransactionManager,Mybatis/JDBC使用的是DataSourceTransactionManager。PlatformTransactionManager 一共拥有三个方法:
- getTransaction(TransactionDefination),在当前环境中取得一个事务,如果不存在,则新建。有点像是一种缓存机制
- commit:提交事务
- rollback:回滚事务
- TransactionDefination:封装了事务隔离级别,超时时间等。
- TransactionStatus:封装了事务具体运行的状态,是否是新开的事务,是否已经提交事务
Xml方式进行配置:
下方是Spring官网给的例子
//业务接口:
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
<!--xml文件关于事务的配置-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 首先将刚刚的业务类注入进容器中 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 配置数据库连接池,因为连接池会作为属性注入到TransactionManager中 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- 配置PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置transaction 具体的一些配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- 如果是方法名以get作为开头的,说明是查询方法,那么配置只读操作-->
<tx:method name="get*" read-only="true"/>
<!-- 其他的增和改操作,就是用默认的即可-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 使用Aop把transactionManager作为对业务逻辑的增强操作 -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- other <bean/> definitions here -->
</beans>
在<tx:advice/>
中的一些详细配置,官网也给出了相应的一些说明,如下图:
Attribute | Required? | Default | Description |
---|---|---|---|
name | Yes | 事务管理的方法名称,并且支持通配符,例如 get* , handle* , on*Event , 等等). |
|
propagation | No | REQUIRED | Transaction propagation behavior. |
isolation | No | DEFAULT | 事务的隔离级别,当传递规则为 REQUIRED or REQUIRES_NEW 才可以设置,当是默认值default的时候,指的是使用数据库隔离级别。其他四种都是Spring 通过代码模拟出来的 |
timeout | No | -1 | 事务超时时间 (seconds),当传递规则为 REQUIRED or REQUIRES_NEW 才可以设置,默认值-1代表使用数据库本身的值,一般情况下,不需要进行修改。 |
read-only | No | false | 一般对查询进行设置只读,可以提升事务的效率。只应用于 REQUIRED or REQUIRES_NEW . |
rollback-for | No | java.lang.RunTimeException | 遇到什么异常需要做事务的回滚,例如,com.foo.MyBusinessException,ServletException. |
no-rollback-for | No | 遇到什么异常不做回滚,com.foo.MyBusinessException,ServletException. |
Java注解方式
首先我们需要在配置类上,开启对事务的支持,使用@EnableTransactionManagement
官网的例子:
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
@Transactional 注解可以用来放在实现类上,也可以放在接口上,最好是放在实现类上。如果加在了实现类上,那么也就是说对这个类里的所有方法都支持开启事务。如果有哪个类需要一些定制化的属性,只需要在方法上再加上这个注解并且贴上定制的属性即可。
@Transactional可以使用的属性:
Property | Type | Description |
---|---|---|
value | String |
|
propagation | enum : Propagation |
|
isolation | enum : Isolation |
隔离级别的设置,用于传递属性为 REQUIRED or REQUIRES_NEW . |
timeout | int (in seconds of granularity) |
事务超时时间用于传递属性为REQUIRES_NEW . |
readOnly | boolean |
是否为只读. 用于传递属性为 REQUIRES_NEW . |
rollbackFor | Array of Class objects, which must be derived from Throwable. |
|
rollbackForClassName | Array of class names. The classes must be derived from Throwable. |
哪些异常类处罚会导致回滚(使用异常类名) |
noRollbackFor | Array of Class objects, which must be derived from Throwable. |
哪些异常类处罚不会导致回滚(使用异常类) |
noRollbackForClassName | Array of String class names, which must be derived from Throwable. |
哪些异常类处罚不会导致回滚(使用异常类名) |
可以看出来这些属性与xml配置的大同小异。