Spring的声明式事务管理是通过Spring AOP实现的,默认情况下,Spring事务只在遇见RuntimeException时才会回滚,可以通过配置来设置其他类型异常。
概念上来说,在事务代理上调用方法的工作过程看起来像这样:
基于@Transactional注解的事务方式
首先配置Spring容器:
<!-- 激活annotation功能 --> <context:annotation-config /> <!-- 开启使用@Transactional注解方式 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> <context:component-scan base-package="com.sohu.tv.crm" scoped-proxy="targetClass"> </context:component-scan>--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- DataSource --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test"/> <property name="user" value="root"/> <property name="password" value="root"/> <!-- 指定连接池里最小连接数 --> <property name="minPoolSize" value="2"/> <!-- 指定连接池里最大连接数 --> <property name="maxPoolSize" value="5"/> <!-- 连接最大空闲时间,超过时间将被丢弃,单位是秒 --> <property name="maxIdleTime" value="60"/> <!-- 当连接池里面的连接用完的时候,C3P0一次获取的新的连接数 --> <property name="acquireIncrement" value="3"/> <!-- 指定连接池里最大缓存多少个Statement对象 --> <property name="maxStatements" value="10"/> <!-- 初始创建连接的数量 --> <property name="initialPoolSize" value="2"/> <!-- 每隔XX秒检查连接池里的空闲连接 ,单位是秒 --> <property name="idleConnectionTestPeriod" value="900"/> <property name="numHelperThreads" value="10" /> <property name="preferredTestQuery" value="select 1 from dual" /> </bean> <!-- DataSourceTransactionManager是用于JDBC、ibatis/MyBatis类型的Spring内置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> <property name="rollbackOnCommitFailure" value="true" /> </bean>
配置中的<tx:annotation-driven/>属性proxy-target-class决定为那些使用了@Transactional注解的类创建何种事务代理。 如果 "proxy-target-class" 属性被设为 "true", 那么基于类的代理就会被创建。即使用CGLib创建增强的代理,如果 "proxy-target-class"属性被设为"false" 或者没设,那么基于接口的标准JDK代理就会被创建。
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。 然而,请注意只是使用@Transactional注解并不会启用事务行为, 它仅仅 是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的beans所使用。其实是<tx:annotation-driven/>元素的出现 开启了事务行为。
Spring团队的建议是你只在具体的类上使用@Transactional注解, 而不要注解在接口上。我在实际项目中一般只在操作数据库的方法上使用,而不是在整个类上使用。
@Service public class BlogServiceImpl implements BlogService { @Resource private BlogDao blogDao; @Override @Transactional public void insert(Blog blog) { // 一些其他逻辑... blogDao.insert(blog); // 一些其他逻辑... } @Override @Transactional public void update(Blog blog) { // 一些其他逻辑... blogDao.update(blog); // 一些其他逻辑... } @Override @Transactional public void insertOrUpdate(Blog blog) { if (blog.getId() == null) { insert(blog); } else { update(blog); } } @Override public Blog get(Integer id) { return blogDao.get(id); } }
这样在调用BlogService方法的时候,带有@Transactional的方法就会在事务的控制下。但是这里需要注意和Spring AOP一样的场景,即方法调用同一个类中的另一个方法时不能被拦截的问题。从上面来看,即insertOrUpdate方法中调用了insert()和update()方法,则insert()或者update()方法使用的实际是insertOrUpdate()方法的事务,如果insertOrUpdate()方法没有@Transactional注解,则调用该方法时,内部的insert()方法的事务是不起作用的,具体原因参见 http://my.oschina.net/mushui/blog/161387.
@Transactional注解属性
propagation | 枚举型:Propagation | 可选的传播性设置 |
isolation | 枚举型:Isolation | 可选的隔离性级别(默认值:ISOLATION_DEFAULT) |
readOnly | 布尔型 | 读写型事务 vs. 只读型事务 |
timeout | int型(以秒为单位) | 事务超时 |
rollbackFor | 一组Class类的实例,必须是Throwable的子类 | 一组异常类,遇到时 必须 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。 |
rollbackForClassname | 一组Class类的名字,必须是Throwable的子类 | 一组异常类名,遇到时 必须 进行回滚 |
noRollbackFor | 一组Class类的实例,必须是Throwable的子类 | 一组异常类,遇到时 必须不 回滚。 |
noRollbackForClassname | 一组Class类的名字,必须是Throwable的子类 | 一组异常类,遇到时 必须不 回滚 |
事务传播行为
Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为:
Required:默认的事务传播行为,表示必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务:
RequiresNew:创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)因此外部事务可以不受内部事务回滚状态的影响独立提交或者回滚。
Supports:支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行。
NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行。
Mandatory:使用PROPAGATION_MANDATORY指定,如果当前有事务,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException)。
Never:不支持事务,如果当前存在是事务则抛出IllegalTransactionStateException异常,使用PROPAGATION_NEVER指定。
Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。
Nested和RequiresNew的区别:
- RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
- Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
- Nested使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。
实际应用中一般使用默认的事务传播行为,偶尔会用到RequiresNew和Nested方式。
基于XML配置的事务
个人比较偏爱使用@Transactional注解的方式,但是很大一部分项目是使用XML方式的,使用XML方式只需要把上面配置中的
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
去掉,然后添加如下配置即可:
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.sohu.tv.crm.service.impl.*.*(..))" /> </aop:config>