这次的内容不多,但是希望阅读的朋友能先能理解代理模式、动态代理的实现、AOP 思想、 Spring AOP 配置以及事务的相关基础。倒不是说不明白这些就看不懂、用不了本篇的内容,而是如此接受下来这个知识在各位身上会是我最不希望见到的情况——无本之木。
OK,相信能接着看的各位对其上提到的点都有一定了解了,那开始正文。
事务是什么?在这懒得说了(不知道事务看个啥的配置)。首先说下 Spring 对于事务的管理策略,其不是直接对事务操作的,而是提供事务管理器接口给相关实现事务的框架,让它们为其来操作事务。那么 Spring 是如何定义事务管理器的呢?也懒得说了——开玩笑的,org.springframework.transaction 这里有其事务管理的核心接口,接下来慢慢说。
先谈其事务管理器的核心接口 PlatformTransactionManager:
public abstract interface PlatformTransactionManager
{
//获取事务状态
public abstract TransactionStatus getTransaction(TransactionDefinition paramTransactionDefinition)
throws TransactionException;
public abstract void commit(TransactionStatus paramTransactionStatus)
throws TransactionException;
public abstract void rollback(TransactionStatus paramTransactionStatus)
throws TransactionException;
}
如上,十分简洁明了,咱们只说第一个方法 getTransaction,这里获取了一个 TransactionStatus 对象,其是说明事务状态的,看看源码
public abstract interface TransactionStatus
extends SavepointManager
{
public abstract boolean isNewTransaction();
public abstract boolean hasSavepoint();
public abstract void setRollbackOnly();
public abstract boolean isRollbackOnly();
public abstract void flush();
public abstract boolean isCompleted();
}
这里很明显提供了一系列的事务处理操作和状态返回,如设置只回滚、清除会话,查询是否有保存点、是否事务已完成等等。那么事务状态是怎么来的呢?回到 getTransaction 方法,我们发现传入了一个 TransactionDefinition 对象,瞅瞅
public abstract interface TransactionDefinition
{
public static final int PROPAGATION_REQUIRED = 0;
public static final int PROPAGATION_SUPPORTS = 1;
public static final int PROPAGATION_MANDATORY = 2;
public static final int PROPAGATION_REQUIRES_NEW = 3;
public static final int PROPAGATION_NOT_SUPPORTED = 4;
public static final int PROPAGATION_NEVER = 5;
public static final int PROPAGATION_NESTED = 6;
public static final int ISOLATION_DEFAULT = -1;
public static final int ISOLATION_READ_UNCOMMITTED = 1;
public static final int ISOLATION_READ_COMMITTED = 2;
public static final int ISOLATION_REPEATABLE_READ = 4;
public static final int ISOLATION_SERIALIZABLE = 8;
public static final int TIMEOUT_DEFAULT = -1;
//返回事务传播行为
public abstract int getPropagationBehavior();
//返回事务隔离级别
public abstract int getIsolationLevel();
public abstract int getTimeout();
public abstract boolean isReadOnly();
public abstract String getName();
}
现在到重点了,Spring 关于事务属性隔离级别与传播行为的参数配置,这里我把他们一一对应放下面:
隔离级别
TransactionDefinition.ISOLATION_DEFAULT:
默认值,表示使用底层数据库的默认隔离级别。大部分数据库是下面这个TransactionDefinition.ISOLATION_READ_COMMITTED。
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。可以防止脏读。TransactionDefinition.ISOLATION_READ_UNCOMMITTED :
该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。TransactionDefinition.ISOLATION_REPEATABLE_READ:
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。可以防止脏读和不可重复读。TransactionDefinition.ISOLATION_SERIALIZABLE:
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。将严重影响程序的性能。通常情况下不会用到该级别。
传播行为
TransactionDefinition.PROPAGATION_REQUIRED:
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。默认值。TransactionDefinition.PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_SUPPORTS:
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。TransactionDefinition.PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于。
说明一下事务传播行为,Spring 以其 AOP 的方式支持各种事务声明,根据事务属性来织入事务。
好了,Spring 对事务的基本策略问题说道这里,那么其到底是如何实现对事务的支持呢?可分为编码与声明两类。
编码呢其实就是自己实现 Spring 事务管理策略的 PlatformTransactionManager 或者说使用 Spring 提供的一些模板(Template),在你的业务代码中嵌入事务管理,虽然可以把事务做的非常细致,但是非常复杂,并且不解耦,超级恶心,只适合研究,本人也没怎么用过,就不细说了。
其实编码式事务还是需要看看的,只是不好用,各位有兴趣的话看看这个
OK,继续咱们的交流,声明式事务:
我们知道,tomcat 加载配置文件的顺序(ssm)是从 web.xml 开始的,然后加载 Spring 配置文件(xxxContext.xml),再加载 Spring 延展(模块)配置文件(如xxxContext-mybatis.xml、xxxContext-quartz.xml),最后是 SpringMVC 配置文件(xxxContext-servlet.xml),所以当 xxxContext.xml 加载的时候,Controller 会先进行扫描装配,但此时 service 还没有进行事务增强处理,得到的将是原样的 Service,所以 xxxContext.xml 得把 controler 下面的 controller去除掉(配置事务放在 service 层,放 dao 的话各种业务调用,恶心的一匹)
<context:orgponent-scan base-package="org.xxx">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
context:orgponent-scan>
然后在是 xxxContext-servlet.xml 里配置不扫描 service ,原因怎么说呢?其实是 context 父子容器的冲突问题,这里是子容器,也会装配扫描 @Service 的实例,但其应由父容器进行初始化以保证事务的增强处理,这样说能明白吧,大概
<context:orgponent-scan base-package="org.xxx">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
context:orgponent-scan>
接下来说事务管理器配置,公司用的 mybatis(JdbcTemplate),所以这里配置的是 DataSourceTransactionManager,可以在 xxxContext.xml 里配,也可以在其数据源模块配(如xxxContext-mybatis.xml)
id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
最后是配置 AOP 参与事务和配置事务属性,同样可以选择配置在 Spring 配置文件(xxxContext.xml)或者是模块配置文件(如 xxxContext-modelA.xml)中
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* org.xxx.*.service.impl.*.*(..))"/>
aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method
-- 关联事务属性的通配方法名 -->
name="sava*"
propagation="REQUIRED"
isolation="READ_COMMITTED"
timeout="-1"
read-only="false"
//指定不回滚异常,可以","分割
no-rollback-for="org.xxx.businessException.xxxException"
rollback-for="Exception"
/>
<tx:method name="find*" read-only="true"/>
<tx:method name="load*" propagation="REQUIRED" read-only="true"/>
<tx:method name="*" rollback-for="Throwable"/>
tx:attributes>
tx:advice>
这部分语法方面相信各位最少也能看个大概吧(听话先去补前面的知识)?我详细说几点:
本文最后,介绍下 Spring 事务管理最简单的方式。其实说到 Spring 整合一切框架、应用,最便捷的方式各位都能猜到,对,就是注解
先说配置,有关扫描装配以及引入数据源的地方不变(之前忘记说,各位要是用的也是 mybatis 你们引用的数据源得和 sqlSessionFactory 的引用一致),把 AOP 参与事务和事务属性去了,改成
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
tx:annotation-driven 标签会让 Spring 自动识别 @Transactional 注解指定的事务属性,说下这个标签的相关属性
@Transactional 注解可以放在方法前、类前以及接口前(只能 public),优先级递减,官方建议放在实现类上,我也给出同样的建议,原因是如果是 CGLib 实现代理,那么其没有代理到接口,其次注解是不能继承的,大大滴可能出问题。如果你注解了的实现类的哪个方法不想参与事务,这样 @Transactional(propagation=Propagation.NOT_SUPPORTED),管与类似的应用,灵活使用参数就成。OK,本篇就到这里结束了,末尾放 @Transactional 的部分参数。