一.Spring对编程式事务的支持

Spring中的事务分为物理事务和逻辑事务;
物理事务:就是底层数据库提供的事务支持,如JDBC或JTA提供的事务;
逻辑事务:是Spring管理的事务,不同于物理事务,逻辑事务提供更丰富的控制,而且如果想得到Spring事务管理的好处,必须使用逻辑事务,因此在Spring中如果没特别强调一般就是逻辑事务;

逻辑事务即支持非常低级别的控制,也有高级别解决方案:
低级别解决方案:使用工具类获取连接(会话)和释放连接(会话),如Spring中使用DataSourceUtils ,Hibernate中使用SessionFactoryUtils,JPA中使用EntityManagerFactoryUtils
高级别解决方案:使用Spring提供的模板类,如JdbcTemplate、HibernateTemplate和JpaTemplate模板类等,而这些模板类内部其实是使用了低级别解决方案中的工具类来管理连接或会话

二.使用PlatformTransactionManager
  • applicationContext.xml
<!-- 加载jdbc.property -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 数据源配置, 使用DBCP数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- Connection Pooling Info -->
<property name="maxActive" value="3"/>
<property name="defaultAutoCommit" value="false"/>
<!-- 连接Idle一个小时后超时 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<property name="minEvictableIdleTimeMillis" value="3600000"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
  • 使用低级别解决方案来进行事务管理器测试:
@Test
public void testPlatformTransactionManager() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
Connection connection = DataSourceUtils.getConnection(dataSource);
try{
connection.prepareStatement(CREATE_TABLE_SQL).execute();
PreparedStatement pstmt = connection.prepareStatement(INSERT_SQL);
pstmt.setString(1, "test");
pstmt.execute();
connection.prepareStatement(DROP_TABLE_SQL).execute();
txManager.commit(status);
}catch(Exception ex){
status.setRollbackOnly();
txManager.rollback(status);
}finally{
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
  • 使用高级别方案JdbcTemplate****来进行事务管理器测试
@Test
public void testPlatformTransactionManager() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try{
jdbcTemplate.execute(CREATE_TABLE_SQL);
jdbcTemplate.update(INSERT_SQL, "test");
}catch(Exception ex){
txManager.rollback(status);
}
txManager.commit(status);
}
三.使用TransactionTemplate

TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
TransactionTemplate模板类使用的回调接口:
TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。

  • TransactionTemplate 模板类使用
@Test
public void testTransactionTemplate(){
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
jdbcTemplate.execute(CREATE_TABLE_SQL);
jdbcTemplate.update(INSERT_SQL, "test");
}
});
String COUNT_ALL = "select count(*) from test";
Number count = jdbcTemplate.queryForInt(COUNT_ALL);
Assert.assertEquals(1, count.intValue()); jdbcTemplate.execute(DROP_TABLE_SQL);
}
三.实际应用
  • 模型对象
public class User implements java.io.Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Address address;
private Integer id;
private String name;
//省略get,set
}
public class Address implements java.io.Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String city;
private Integer id;
private String province;
private String street;
private Integer userId;
//省略get,set
}
  • Dao层接口及实现
public interface UserDao {
public void save(User user);
public int countAll();
}
package transaction.dao;
import java.util.HashMap;
import java.util.Map;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import transaction.bean.User;
public class UserDaoImp extends NamedParameterJdbcDaoSupport implements UserDao{
private final String INSERT_SQL = "insert into user(name) values(:name)";
private final String COUNT_ALL_SQL = "select count(*) from user";
@Override
public void save(final User user) {
Map<String,String> para = new HashMap<String,String>();
para.put("name", user.getName());
getNamedParameterJdbcTemplate().update(INSERT_SQL, para);
}
@Override
public int countAll() {
return getJdbcTemplate().queryForInt(COUNT_ALL_SQL);
}
}
public interface AddressDao {
public void save(Address address);
public int countAll();
}
public class AddressDaoImp extends NamedParameterJdbcDaoSupport implements AddressDao{
private final String INSERT_SQL = "insert into address(province, city, street, user_id)" + "values(:province, :city, :street, :userId)";
private final String COUNT_ALL_SQL = "select count(*) from address";
@Override
public void save(Address address){
KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
/*SqlParameterSource source = new BeanPropertySqlParameterSource(address);
getNamedParameterJdbcTemplate().update(INSERT_SQL, source, generatedKeyHolder);*/
Map<String,Object> para = new HashMap<String,Object>();
para.put("province", address.getProvince());
para.put("city", address.getCity());
para.put("street", address.getStreet());
para.put("userId", address.getUserId());
getNamedParameterJdbcTemplate().update(INSERT_SQL, para);
}
@Override
public int countAll() {
return getJdbcTemplate().queryForInt(COUNT_ALL_SQL);
}
}
  • Service****层接口及实现
public interface UserService {
public void save(User user);
public int countAll();
}
@Override
public void save(final User user){
TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
userDao.save(user);
user.getAddress().setUserId(user.getId());
try {
addressService.save(user.getAddress());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
@Override
public int countAll() {
return userDao.countAll();
}
public interface AddressService {
public void save(Address address) throws Exception;
public int countAll();
}
public class AddressServiceImp implements AddressService{
private AddressDao addressDao;
private PlatformTransactionManager txManager;
public void setAddressDao(AddressDao addressDao) {
this.addressDao = addressDao;
}
public void setTxManager(PlatformTransactionManager txManager) {
this.txManager = txManager;
}
@Override
public void save(final Address address){
TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
addressDao.save(address);
}
});
}
@Override
public int countAll() {
return addressDao.countAll();
}
}
  • applicationContext.xml
<!-- 加载jdbc.property -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 数据源配置, 使用DBCP数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- Connection Pooling Info -->
<property name="maxActive" value="3"/>
<property name="defaultAutoCommit" value="false"/>
<!-- 连接Idle一个小时后超时 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<property name="minEvictableIdleTimeMillis" value="3600000"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean> <bean id="abstractDao" abstract="true">
<property name="dataSource" ref="dataSource"/>
</bean> <bean id="userDao" class="transaction.dao.UserDaoImp" parent="abstractDao"/>
<bean id="addressDao" class="transaction.dao.AddressDaoImp" parent="abstractDao"/> <bean id="userService" class="transaction.service.UserServiceImp">
<property name="addressService" ref="addressService"/>
<property name="userDao" ref="userDao"/>
<property name="txManager" ref="transactionManager"/>
</bean> <bean id="addressService" class="transaction.service.AddressServiceImp">
<property name="addressDao" ref="addressDao"/>
<property name="txManager" ref="transactionManager"/>
</bean>
  • TransactionTemplateUtils类
public class TransactionTemplateUtils {
private static TransactionTemplate getTransactionTemplate(
PlatformTransactionManager txManager, int propagationBehavior,
int isolationLevel) {
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.setPropagationBehavior(propagationBehavior);
transactionTemplate.setIsolationLevel(isolationLevel);
return transactionTemplate;
}
public static TransactionTemplate getDefaultTransactionTemplate(PlatformTransactionManager txManager) {
return getTransactionTemplate(txManager,
TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.ISOLATION_READ_COMMITTED);
}
}
三.事务属性

事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读。
Spring提供TransactionDefinition接口默认实现DefaultTransactionDefinition,可以通过该实现类指定这些事务属性

  • 事务隔离级别:用来解决并发事务时出现的问题,其使用TransactionDefinition中的静态变量来指定:
    <b>ISOLATION_DEFAULT:</b>默认隔离级别,即使用底层数据库默认的隔离级别;
    <b> ISOLATION_READ_UNCOMMITTED:</b>未提交读;
    <b>ISOLATION_READ_COMMITTED:</b>提交读,一般情况下我们使用这个;
    <b>ISOLATION_REPEATABLE_READ:</b>可重复读;
    <b>ISOLATION_SERIALIZABLE:</b>序列化

  • 事务传播行为:Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为
    Required:必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务
    RequiresNew:创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)
    Supports:支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行
    NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行
    Mandatory:必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务
    Never:不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)
    Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚

NestedRequiresNew的区别:
1、 RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
2、 Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
3、 Nested使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。
使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要我们来开启

  • 事务超时:设置事务的超时时间,单位为秒,默认为-1表示使用底层事务的超时时间;
  • 事务只读:将事务标识为只读,只读事务不修改任何数据;

编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理)

Spring实现编程式事务,依赖于2大类,分别是上篇文章提到的PlatformTransactionManager,与模版类TransactionTemplate(推荐使用)。下面分别详细介绍Spring是如何通过该类实现事务管理。
1)PlatformTransactionManager,上篇文章已经详情解说了该类所拥有的方法,不记得可以回看上篇文章。
事务管理器配置

[java] view plain copy

 
  1. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  2. <property name="jdbcUrl" value="${db.jdbcUrl}" />
  3. <property name="user" value="${user}" />
  4. <property name="password" value="${password}" />
  5. <property name="driverClass" value="${db.driverClass}" />
  6. <!--连接池中保留的最小连接数。 -->
  7. <property name="minPoolSize">
  8. <value>5</value>
  9. </property>
  10. <!--连接池中保留的最大连接数。Default: 15 -->
  11. <property name="maxPoolSize">
  12. <value>30</value>
  13. </property>
  14. <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
  15. <property name="initialPoolSize">
  16. <value>10</value>
  17. </property>
  18. <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
  19. <property name="maxIdleTime">
  20. <value>60</value>
  21. </property>
  22. <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
  23. <property name="acquireIncrement">
  24. <value>5</value>
  25. </property>
  26. <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。  如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
  27. <property name="maxStatements">
  28. <value>0</value>
  29. </property>
  30. <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
  31. <property name="idleConnectionTestPeriod">
  32. <value>60</value>
  33. </property>
  34. <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
  35. <property name="acquireRetryAttempts">
  36. <value>30</value>
  37. </property>
  38. <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false -->
  39. <property name="breakAfterAcquireFailure">
  40. <value>true</value>
  41. </property>
  42. <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default: false -->
  43. <property name="testConnectionOnCheckout">
  44. <value>false</value>
  45. </property>
  46. </bean>
  47. <!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 -->
  48. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  49. <property name="dataSource" ref="dataSource" />
  50. </bean>

业务中使用代码(以测试类展示)

[java] view plain copy

 
  1. import java.util.Map;
  2. import javax.annotation.Resource;
  3. import javax.sql.DataSource;
  4. import org.apache.log4j.Logger;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.jdbc.core.JdbcTemplate;
  8. import org.springframework.test.context.ContextConfiguration;
  9. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  10. import org.springframework.transaction.PlatformTransactionManager;
  11. import org.springframework.transaction.TransactionDefinition;
  12. import org.springframework.transaction.TransactionStatus;
  13. import org.springframework.transaction.support.DefaultTransactionDefinition;
  14. @RunWith(SpringJUnit4ClassRunner.class)
  15. @ContextConfiguration(locations = { "classpath:spring-public.xml" })
  16. public class test {
  17. @Resource
  18. private PlatformTransactionManager txManager;
  19. @Resource
  20. private  DataSource dataSource;
  21. private static JdbcTemplate jdbcTemplate;
  22. Logger logger=Logger.getLogger(test.class);
  23. private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)";
  24. private static final String COUNT_SQL = "select count(*) from testtranstation";
  25. @Test
  26. public void testdelivery(){
  27. //定义事务隔离级别,传播行为,
  28. DefaultTransactionDefinition def = new DefaultTransactionDefinition();
  29. def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  30. def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  31. //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
  32. TransactionStatus status = txManager.getTransaction(def);
  33. jdbcTemplate = new JdbcTemplate(dataSource);
  34. int i = jdbcTemplate.queryForInt(COUNT_SQL);
  35. System.out.println("表中记录总数:"+i);
  36. try {
  37. jdbcTemplate.update(INSERT_SQL, "1");
  38. txManager.commit(status);  //提交status中绑定的事务
  39. } catch (RuntimeException e) {
  40. txManager.rollback(status);  //回滚
  41. }
  42. i = jdbcTemplate.queryForInt(COUNT_SQL);
  43. System.out.println("表中记录总数:"+i);
  44. }
  45. }

2)使用TransactionTemplate,该类继承了接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模板类定义,主要是通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。

TransactionTemplate模板类使用的回调接口:

  • TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
  • TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
还是以测试类方式展示如何实现
[java] view plain copy

 
  1. @Test
  2. public void testTransactionTemplate(){
  3. jdbcTemplate = new JdbcTemplate(dataSource);
  4. int i = jdbcTemplate.queryForInt(COUNT_SQL);
  5. System.out.println("表中记录总数:"+i);
  6. //构造函数初始化TransactionTemplate
  7. TransactionTemplate template = new TransactionTemplate(txManager);
  8. template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  9. //重写execute方法实现事务管理
  10. template.execute(new TransactionCallbackWithoutResult() {
  11. @Override
  12. protected void doInTransactionWithoutResult(TransactionStatus status) {
  13. jdbcTemplate.update(INSERT_SQL, "饿死");   //字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
  14. }}
  15. );
  16. i = jdbcTemplate.queryForInt(COUNT_SQL);
  17. System.out.println("表中记录总数:"+i);
  18. }

3.声明式事务:可知编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现。

声明式事务实现方式主要有2种,一种为通过使用Spring的<tx:advice>定义事务通知与AOP相关配置实现,另为一种通过@Transactional实现事务管理实现,下面详细说明2种方法如何配置,已经相关注意点
1)方式一,配置文件如下
[java] view plain copy

 
  1. <!--
  2. <tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法
  3. <tx:method>拦截方法,其中参数有:
  4. name:方法名称,将匹配的方法注入事务管理,可用通配符
  5. propagation:事务传播行为,
  6. isolation:事务隔离级别定义;默认为“DEFAULT”
  7. timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
  8. read-only:事务只读设置,默认为false,表示不是只读;
  9. rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
  10. no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
  11. -->
  12. <tx:advice id="advice" transaction-manager="transactionManager">
  13. <tx:attributes>
  14. <!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
  15. <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
  16. <!-- 支持,如果有就有,没有就没有 -->
  17. <tx:method name="*" propagation="SUPPORTS"/>
  18. </tx:attributes>
  19. </tx:advice>
  20. <!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义  -->
  21. <aop:config>
  22. <aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
  23. <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
  24. <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
  25. </aop:config>
关于事务传播行为与隔离级别,可参考http://blog.csdn.net/liaohaojian/article/details/68488150
注意点:
  1. 事务回滚异常只能为RuntimeException异常,而Checked Exception异常不回滚,捕获异常不抛出也不会回滚,但可以强制事务回滚:TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
  2. 解决“自我调用”而导致的不能设置正确的事务属性问题,可参考http://www.iteye.com/topic/1122740

2)方式二通过@Transactional实现事务管理

[java] view plain copy

 
  1. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  2. <property name="dataSource" ref="dataSource"/>
  3. </bean>
  4. <tx:annotation-driven transaction-manager="txManager"/> //开启事务注解
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED),具体参数跟上面<tx:method>中一样
Spring提供的@Transaction注解事务管理,内部同样是利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注意点:
  1. 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;
  2. 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;

2.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理)

Spring实现编程式事务,依赖于2大类,分别是上篇文章提到的PlatformTransactionManager,与模版类TransactionTemplate(推荐使用)。下面分别详细介绍Spring是如何通过该类实现事务管理。
1)PlatformTransactionManager,上篇文章已经详情解说了该类所拥有的方法,不记得可以回看上篇文章。
事务管理器配置

[java] view plain copy

 
  1. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  2. <property name="jdbcUrl" value="${db.jdbcUrl}" />
  3. <property name="user" value="${user}" />
  4. <property name="password" value="${password}" />
  5. <property name="driverClass" value="${db.driverClass}" />
  6. <!--连接池中保留的最小连接数。 -->
  7. <property name="minPoolSize">
  8. <value>5</value>
  9. </property>
  10. <!--连接池中保留的最大连接数。Default: 15 -->
  11. <property name="maxPoolSize">
  12. <value>30</value>
  13. </property>
  14. <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
  15. <property name="initialPoolSize">
  16. <value>10</value>
  17. </property>
  18. <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
  19. <property name="maxIdleTime">
  20. <value>60</value>
  21. </property>
  22. <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
  23. <property name="acquireIncrement">
  24. <value>5</value>
  25. </property>
  26. <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。  如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
  27. <property name="maxStatements">
  28. <value>0</value>
  29. </property>
  30. <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
  31. <property name="idleConnectionTestPeriod">
  32. <value>60</value>
  33. </property>
  34. <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
  35. <property name="acquireRetryAttempts">
  36. <value>30</value>
  37. </property>
  38. <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false -->
  39. <property name="breakAfterAcquireFailure">
  40. <value>true</value>
  41. </property>
  42. <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default: false -->
  43. <property name="testConnectionOnCheckout">
  44. <value>false</value>
  45. </property>
  46. </bean>
  47. <!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 -->
  48. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  49. <property name="dataSource" ref="dataSource" />
  50. </bean>

业务中使用代码(以测试类展示)

[java] view plain copy

 
  1. import java.util.Map;
  2. import javax.annotation.Resource;
  3. import javax.sql.DataSource;
  4. import org.apache.log4j.Logger;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.jdbc.core.JdbcTemplate;
  8. import org.springframework.test.context.ContextConfiguration;
  9. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  10. import org.springframework.transaction.PlatformTransactionManager;
  11. import org.springframework.transaction.TransactionDefinition;
  12. import org.springframework.transaction.TransactionStatus;
  13. import org.springframework.transaction.support.DefaultTransactionDefinition;
  14. @RunWith(SpringJUnit4ClassRunner.class)
  15. @ContextConfiguration(locations = { "classpath:spring-public.xml" })
  16. public class test {
  17. @Resource
  18. private PlatformTransactionManager txManager;
  19. @Resource
  20. private  DataSource dataSource;
  21. private static JdbcTemplate jdbcTemplate;
  22. Logger logger=Logger.getLogger(test.class);
  23. private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)";
  24. private static final String COUNT_SQL = "select count(*) from testtranstation";
  25. @Test
  26. public void testdelivery(){
  27. //定义事务隔离级别,传播行为,
  28. DefaultTransactionDefinition def = new DefaultTransactionDefinition();
  29. def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  30. def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  31. //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
  32. TransactionStatus status = txManager.getTransaction(def);
  33. jdbcTemplate = new JdbcTemplate(dataSource);
  34. int i = jdbcTemplate.queryForInt(COUNT_SQL);
  35. System.out.println("表中记录总数:"+i);
  36. try {
  37. jdbcTemplate.update(INSERT_SQL, "1");
  38. txManager.commit(status);  //提交status中绑定的事务
  39. } catch (RuntimeException e) {
  40. txManager.rollback(status);  //回滚
  41. }
  42. i = jdbcTemplate.queryForInt(COUNT_SQL);
  43. System.out.println("表中记录总数:"+i);
  44. }
  45. }

2)使用TransactionTemplate,该类继承了接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模板类定义,主要是通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。

TransactionTemplate模板类使用的回调接口:

  • TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
  • TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。

还是以测试类方式展示如何实现

[java] view plain copy

 
  1. @Test
  2. public void testTransactionTemplate(){
  3. jdbcTemplate = new JdbcTemplate(dataSource);
  4. int i = jdbcTemplate.queryForInt(COUNT_SQL);
  5. System.out.println("表中记录总数:"+i);
  6. //构造函数初始化TransactionTemplate
  7. TransactionTemplate template = new TransactionTemplate(txManager);
  8. template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  9. //重写execute方法实现事务管理
  10. template.execute(new TransactionCallbackWithoutResult() {
  11. @Override
  12. protected void doInTransactionWithoutResult(TransactionStatus status) {
  13. jdbcTemplate.update(INSERT_SQL, "饿死");   //字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
  14. }}
  15. );
  16. i = jdbcTemplate.queryForInt(COUNT_SQL);
  17. System.out.println("表中记录总数:"+i);
  18. }

3.声明式事务:可知编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现。

声明式事务实现方式主要有2种,一种为通过使用Spring的<tx:advice>定义事务通知与AOP相关配置实现,另为一种通过@Transactional实现事务管理实现,下面详细说明2种方法如何配置,已经相关注意点
1)方式一,配置文件如下
[java] view plain copy

 
  1. <!--
  2. <tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法
  3. <tx:method>拦截方法,其中参数有:
  4. name:方法名称,将匹配的方法注入事务管理,可用通配符
  5. propagation:事务传播行为,
  6. isolation:事务隔离级别定义;默认为“DEFAULT”
  7. timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
  8. read-only:事务只读设置,默认为false,表示不是只读;
  9. rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
  10. no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
  11. -->
  12. <tx:advice id="advice" transaction-manager="transactionManager">
  13. <tx:attributes>
  14. <!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
  15. <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
  16. <!-- 支持,如果有就有,没有就没有 -->
  17. <tx:method name="*" propagation="SUPPORTS"/>
  18. </tx:attributes>
  19. </tx:advice>
  20. <!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义  -->
  21. <aop:config>
  22. <aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
  23. <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
  24. <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
  25. </aop:config>
关于事务传播行为与隔离级别,可参考http://blog.csdn.net/liaohaojian/article/details/68488150
注意点:
  1. 事务回滚异常只能为RuntimeException异常,而Checked Exception异常不回滚,捕获异常不抛出也不会回滚,但可以强制事务回滚:TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
  2. 解决“自我调用”而导致的不能设置正确的事务属性问题,可参考http://www.iteye.com/topic/1122740

2)方式二通过@Transactional实现事务管理

[java] view plain copy

 
  1. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  2. <property name="dataSource" ref="dataSource"/>
  3. </bean>
  4. <tx:annotation-driven transaction-manager="txManager"/> //开启事务注解
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED),具体参数跟上面<tx:method>中一样
Spring提供的@Transaction注解事务管理,内部同样是利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注意点:
  1. 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;
  2. 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;

spring的声明式的事物管理和编程事务管理的区别的更多相关文章

  1. spring aop 声明式事务管理

    一.声明式事务管理的概括 声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一. Spring的声明式事务顾名思义就是采用声明 ...

  2. 使用注解实现Spring的声明式事务管理

    使用注解实现Spring的声明式事务管理,更加简单! 步骤: 1) 必须引入Aop相关的jar文件 2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类 3)在需要添加事务控制 ...

  3. Spring AOP声明式事务异常回滚(转)

    转:http://hi.baidu.com/iduany/item/20f8f8ed24e1dec5bbf37df7 Spring AOP声明式事务异常回滚 近日测试用例,发现这样一个现象:在业务代码 ...

  4. @Transactional、Spring的声明式事务

    传送门 一.Spring的声明式事务 需要在xml文件中配置 <!--配置事务管理器类--> <bean id="transactionManager" clas ...

  5. Spring(四)Spring JdbcTemplate&声明式事务

    JdbcTemplate基本使用 01-JdbcTemplate基本使用-概述(了解) JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装.spr ...

  6. 保护亿万数据安全,Spring有“声明式事务”绝招

    摘要:点外卖时,你只需考虑如何拼单:选择出行时,你只用想好目的地:手机支付时,你只需要保证余额充足.但你不知道这些智能的背后,是数以亿计的强大数据的支持,这就是数据库的力量.那么庞大数据的背后一定会牵 ...

  7. Spring之声明式事务

    在讲声明式事务之前,先回顾一下基本的编程式事务 编程式事务: //1.获取Connection对象 Connection conn = JDBCUtils.getConnection(); try { ...

  8. 【Spring】——声明式事务配置详解

    项目中用到了spring的事务: @Transactional(rollbackFor = Exception.class, transactionManager = "zebraTrans ...

  9. Spring添加声明式事务

    一.前言 Spring提供了声明式事务处理机制,它基于AOP实现,无须编写任何事务管理代码,所有的工作全在配置文件中完成. 二.声明式事务的XML配置方式 为业务方法配置事务切面,需要用到tx和aop ...

随机推荐

  1. ImportError: cannot import name wordnet

    ubuntu安装好nltk,调用时,出现问题: 解决: Install Setuptools: http://pypi.python.org/pypi/setuptools Install Pip: ...

  2. 深入redis内部--内存管理

    1. Redis内存管理通过在zmalloc.h和zmalloc.c中重写c语言对内存的管理来完成的. redis内存管理 c内存管理 原型 作用 zmalloc malloc void *mallo ...

  3. Linux 线程实现机制分析--转

    http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 一.基础知识:线程和进程 按照教科书上的定义,进程是资源管理的最小单位,线程是程 ...

  4. sql 递归 STUFF

    select distinct fm_id, ,,'') AS SO_Nums from [dbo].[t_BADItems] its 表内一对多 的关系查询

  5. ​Error -4075: File not found. An error occurred merging module <MODULENAME> for feature <FEATURENAME>.

    利用Install Shield2010制作安装包的时候一直报这样的错误,原以为是我自己安装包制作流程的问题,又重新做了2个,但是还是出现问题. 解决办法: 查找资料发现是Install Shield ...

  6. Python基础学习总结(六)

    8.函数 函数是带名字的代码块,用于完成具体的工作.def函数定义,指出函数名.定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了.对于函数的调用者来说,只需要知道如何传递正确的参 ...

  7. Navicat 连接MySQL 8.0.11 出现2059错误

    错误 使用Navicat Premium 连接MySQL时出现如下错误: 原因 mysql8 之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是cach ...

  8. [错误记录_C] 还未给指针变量正确赋值的情况下,就使用它的值

    错误的代码: 错误的结果:  错误原因分析: 在使用(1) 将pB,pC的值赋给pA的lchild和rchild时: 还未给指针变量pB和pC赋值,现在pB和pC中存的是个垃圾值 Note: (2)- ...

  9. jmeter简单使用示例

    1.下载后解压,运行bin目录下的jmeter.bat 2.add ThreadGroup 3.add request 4.add listener

  10. 前端(五):JavaScript面向对象之内建对象

    一.数据类型 js中数据类型分为两种,原始数据累次能够和引用数据类型. 1.原始数据类型 Undefined.Null.Boolean.Number.String是js中五种原始数据类型(primit ...