声明式事务的事务属性:
一:传播行为
二:隔离级别
三:只读提示
四:事务超时间隔
传播行为:
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
spring的事务传播规则:
传播行为 | 意义 |
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行; 如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。 |
这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起 内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套 子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。
隔离级别:
隔离级别是指若干个并发的事务之间的隔离程度。
spring的事务隔离级别:
隔离级别 | 含义 |
ISOLATION_DEFAULT | 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是ISOLATION_READ_COMMITTED。 |
ISOLATION_READ_UNCOMMITTED | 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。 |
ISOLATION_READ_COMMITTED | 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。 |
ISOLATION_REPEATABLE_READ | 该隔离级别表示一个事务在整个过程中可以多次重复执 行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。 |
ISOLATION_SERIALIZABLE | 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 |
只读提示:
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
事务超时间隔:
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
声明式事务使用范例:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="userservice" class="com.sunflower.yuan.serviceimp.UserServiceImp">
<property name="jdbcTemplate">
<ref bean="jdbcTemplate"/>
</property>
</bean>
<bean id="mydatasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mytest"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="jdbcManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="mydatasource"/>
</property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="mydatasource"/>
</property>
</bean>
<bean id="transactionAttributeSource" class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="getMoney">
PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-NoMoneyException
</prop>
</props>
</property>
</bean>
<!-- 声明式事务管理的代理类 -->
<bean id="userTransaction" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>com.sunflower.yuan.servicedao.UserService</value>
</list>
</property>
<property name="target">
<ref bean="userservice"/>
</property>
<property name="transactionManager">
<ref bean="jdbcManager"/>
</property>
<property name="transactionAttributeSource">
<ref bean="transactionAttributeSource"/>
</property>
</bean>
</beans>
第12~17行是配置DataSource,第19~23行是配置事务JDBC事务管理器,关于JDBC事务管理,可以查看:Spring中使用JDBC 。
第44~61行可以看出,声明式事务管理也是用动态代理模式来实现的,思想和AOP是一样的。主要是配置org.springframework.transaction.interceptor.TransactionProxyFactoryBean类。这个配置和AOP的配置非常相似,可以查看:Spring AOP(创建切面)
第57~58行是配置事务的属性,就是上文所列出的事务属性。可以将事务属性单独写在org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource中,然后再用<property name="transactionAttributeSource">引入,也可以直接用<property name="transactionAttributes">配置事务属性。
第34~40行是事务属性的配置方法,<prop key="getMoney">标签中的key="getMoney"表示对getMoney方法进行事务管理,也可使用通配符get*,表示对所有get前缀的方法进行事务管理。
事务属性的配置用","隔开,如下所示:
PROPAGATION, ISOLATION, readonly, -Exception, +Exception
PROPAGATION: 传播行为
ISOLATION: 隔离级别
readonly: 是否为只读事务(可选)
(+/-)Exception: 回滚规则。 如果是"-",遇到指定的Exception,事务回滚,如果是"+",事务不回滚
UserService.java:
public interface UserService { public void getMoney(int money, int id) throws NoMoneyException; }
package com.sunflower.yuan.serviceimp;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import com.sunflower.yuan.Exception.NoMoneyException;
import com.sunflower.yuan.servicedao.UserService;
/**
* @author Caihanyuan
* @time 2012-10-20 下午08:23:58
*/
public class UserServiceImp implements UserService {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void getMoney(int getMoney, int id) throws NoMoneyException {
String sql = "select money from user where id=?";
Object[] params = new Object[] { new Integer(id) };
MyRowCallbackHandler rowCallBack = new MyRowCallbackHandler();
jdbcTemplate.query(sql, params, rowCallBack);
if (rowCallBack.getMoney() > getMoney) {
sql = "update user set money=? where id=?";
Object[] params2 = new Object[] {
rowCallBack.getMoney() - getMoney, new Integer(id) };
jdbcTemplate.update(sql, params2);
throw new NoMoneyException("余额不足");
}
}
class MyRowCallbackHandler implements RowCallbackHandler {
private int money = 0;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public void processRow(ResultSet rs) throws SQLException {
this.money = rs.getInt("money");
}
}
}
这里要注意一点,要进行事务管理的方法,必须在方法外进行异常的抛出,这样事务管理器才能接收到,然后进行事务的回滚。如果用try-catch处理异常,将不会进行事务回滚。我在这个问题上纠结了很久,可以参考:SPRING事务回滚问题
package com.sunflower.yuan.Exception;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sunflower.yuan.servicedao.UserService;
/**
* @author Caihanyuan
* @time 2012-10-20 下午08:15:15
*/
public class Test {
@org.junit.Test
public void getMoney() throws NoMoneyException
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)context.getBean("userTransaction");
userService.getMoney(10, 1);
}
}