java笔记:自己动手写javaEE框架(四)Spring事务管理学习

  最近工作里碰到了事务的难题,不过还好,一切问题都已经解决了,但是这个过程让我明白自己对事务的理解还是不够透彻,特别是Spring事务管理。

  这个让我惊醒起来,我记得以前找工作经常被人问到事务的相关知识,我所回答的都是不太准确,或者是比较肤浅,因此我想在我写的框架里面加入我学习Spring事务管理的代码,Spring事务管理会有两篇博文,这两篇博文里我都会上传自己代码,方便和童鞋们的交流。我首先从基础知识讲起。

第一章 事务管理基础知识

1.事务管理概念:它是一系列任务组成的工作单元,在这个工作单元里,所有任务必须同时执行。它们只有两种可能的执行结果,要么所有任务全部成功执行,要么全部执行失败

2.衡量事务质量的ACID特性:

原子性(atomic)(atomicity)

事务必须是原子的,在事务结束的时候,事务中的所有任务必须全部成功完成,否则全部失败,事务回滚到事务开始之间的状态。

一致性(consistent)(consistency)

事务必须保证和数据库的一致性,即数据库中的所有数据和现实保持一致。如果事务失败数据必须返回到事务执行之前的状态,反之修改数据和现实的同步。

隔离性(insulation)(isolation)

隔离性是事务与事务之间的屏障,每个事务必须与其他事务的执行结果隔离开,直到该事务执行完毕,它保证了事务的访问的任何数据不会受其他事务执行结果的影响。

持久性 (Duration)(durability)

如果事务成功执行,无论系统发生任何情况,事务的持久性都必须保证事务的执行结果是永久的。

 3.事务之间的缺陷

  在事务管理中违反ACID特性的3个问题:脏读取、不可重复读和幻影行。如果存在多个并发的事务在运行,而这些事务操作了同一个数据来完成他们的任务,就会导致3个问题的存在。要解决它们,就必须在事务之间定义合适的隔离级别。事务之间存在的3个问题是:

脏读取 (Dirty read)

当一个事务读取了另一个事务尚未提交的更新,就叫脏读取。在另一个事务回滚的情况下,当前事务所读取的另一个事务的数据就是无效的。

不可重复读取(Nonrepeatable read)

在一个事务中执行多次同样的查询操作,但每次查询的结果都不一样,就叫做不可重复读取,通常这种情况是由于数据在二次查询之间被另一个并发的事务所修改。

幻影行(Phantom rows)

这是对事务危害最小的一个问候,它类似不可重复读取,也是一个事务的更新结果影响到另一个事务问题。但是它不仅影响另一个事务查询结果,而且还会使查询语句返回一些不同的行录行。

这3个问题危害程度依次为:脏读取最大、不可重复读取其次、幻影行最小。

4.事务的属性:事务的属性是事务的策略应用到程序里所定义的事务属性,它包括事务的传播行为、事务的隔离级别、事务的只读和超时属性。

第二章  详解Spring里的事务的属性

1.事务的传播行为:

  事务应用到程序里往往会精确到某一个方法或是一堆方法,而传播行为是事务应用到方法的边界,当方法被调用事务的传播行为定义里面事务的建立、暂停等行为属性。在Spring里一共定义了7种事务传播行为,如下表:

传播行为
说明
PROPAGATION_MANDATORY 规定了方法必须在事务中运行,否则会抛出异常
PROPAGATION_NESTED 使方法运行在嵌套事务中,否则这个属性和PROPAGATION_REQUIRED属性的义相同
PROPAGATION_NEVER 使当前方法永远不在事务中运行,否则抛出异常
PROPAGATION_NOT_SUPPORTED 定义为当前事务不支持的方法,在该方法运行期间正在运行的事务会被暂停
PROPAGATION_REQUIRED 【默认值】 规定当前的方法必须在事务中,如果没有事务就创建一个新事务,一个新事务和方法一同开始,随着方法的返回或抛出异常而终止
PROPAGATION_REQUIRED_NEW 当前方法必须创建新的事务来运行,如果现存的事务正在运行就暂停它
PROPAGATION_SUPPORTS 规定当前方法支持当前事务处理,但如果没有事务在运行就使用非事务方法执行

 2.事务的隔离级别:

  为解决事务之间的3个缺陷,必须在事务之间建立隔离关系来保证事务的完整性。Spring里的事务隔离级别如下:

隔离级别说明
ISOLATION_DEFAULT 【默认值】 使用数据库默认的隔离级别
ISOLATION_COMMITTED 允许读取其他并发事务已经提交的更新(防此脏读)
ISOLATION_READ_UNCOMMITTED 允许读取其他并发事务还未提交的更新,会导致事务之间的3个缺陷发生,这是速度最快的一个隔离级别,但同 时它的隔离级别也是最低
ISOLATION_REPEATABLE_READ 除非事务自身修改了数据,否则规定事务多次重复读取数据必须相同(防此脏读,不可重复读)
ISOLATION_SERIALIZABLE 这是最高的隔离级别,它可以防此脏读,不可重复读和幻读等问题,但因其侵占式的数据记录完全锁定,导致 它影响事务的性能,成为隔离级别中最展慢的一个。

 3.事务的只读属性:

  在对数据库的操作中,查询是使用最频繁的操作,每次执行查询时都要从数据库中重新读取数据,有时多次读取的数据都是相同的,这样的数据操作不仅浪费了系统资源,还影响了系统速度。对访问量大的程序来说,节省这部分资源可以大大提升系统速度。 
      如果将事务声明为只读的,那么数据库可以根据事务的特性优化事务的读取操作。事务的只读属性需要配合事务的传播行为共同设置。例如:

<prop key="query*">PROPAGATION_REQUIRED,readOnly</prop> 

4.事务的超时属性

  这个属性和事务的只读属性一样需要搭配事务的传播行为共同设置,它设置了事务的超时时间,事务本身可能会因某种原因很长没有回应,在这期间事务可能锁定了数据库的表格,这样会出现严重的性能问题。通过设置事务的超时时间,从开始执行事务起,在规定的超时时间内如果没有事务就将它回滚。事务的超时属性以timeout_为前缀和一个整型数字定义,例如: 

<prop key="query*">PROPAGATION_REGUIRED,timeout_5,readOnly</prop>

  在spring里事务管理有两种实现方式,一个是编程式事务,一个是声明式事务。我们平时工作中大部分使用到的是声明式事务,下面我先从编程式事务开始讲起。  

 第三章  编程式事务--使用PlatformTransactionManager接口实现事务

1.首先还是我java工程的目录结构:

2.首先是applicationContext.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:context
="http://www.springframework.org/schema/context"
xmlns:aop
="http://www.springframework.org/schema/aop"
xmlns:tx
="http://www.springframework.org/schema/tx"
xsi:schemaLocation
="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<!-- 扫描该路径下的spring组件 -->
<context:component-scan base-package="cn.com.sharpxiajun" />

<!-- 读取资源文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:conf/constants.properties</value>
</list>
</property>
</bean>

<!-- 配置数据源 -->
<!-- <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driverClass}"/>
<property name="jdbcUrl" value="${db.jdbcUrl}"/>
<property name="user" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean>
-->

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driverClass}"/>
<property name="url" value="${db.jdbcUrl}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean>

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>classpath:conf/SqlMapConfig.xml</value>
</property>
<property name="dataSource" ref="myDataSource"/>
</bean>

<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient">
<ref local="sqlMapClient"/>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="myDataSource"/>
</property>
</bean>

<!-- 将我自己定义的拦截器生成bean -->
<bean id="methodServiceAdvisor" class="cn.com.sharpxiajun.common.aop.MethodServiceAdvisor"/>

<aop:config>
<!--配置规则,满足以下规则的将拦截,第一个*表示所有返回类型,第二个表示service包下的所有class,第三个表示所有方法-->
<aop:pointcut id="baseServiceMethods" expression="execution(* cn.com.sharpxiajun.service.*.*(..))"/>
<!-- 符合上面规则的拦截器都会调用到methodServiceAdvisor -->
<aop:advisor advice-ref="methodServiceAdvisor" pointcut-ref="baseServiceMethods"/>
</aop:config>

</beans>

  配置文件里添加了<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">。。。事务管理的spring bean。

3.在cn.com.sharpxiajun.transactionstudy包下建接口SpringTransaction,代码如下:

package cn.com.sharpxiajun.transactionstudy;

public interface SpringTransaction {

public void transactionInsert();

}

4.在包cn.com.sharpxiajun.transactionstudy.impl下实现SpringTransaction接口,代码如下:

package cn.com.sharpxiajun.transactionstudy.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import cn.com.sharpxiajun.transactionstudy.SpringTransaction;

@SuppressWarnings("unchecked")
@Scope("prototype")
@Service("springTransaction")
public class SpringTransactionImpl implements SpringTransaction {

private final static Log log = LogFactory.getLog(SpringTransactionImpl.class);

@Autowired
@Qualifier("myDataSource")
private BasicDataSource myDataSource = null;

@Autowired
@Qualifier("transactionManager")
private PlatformTransactionManager transactionManager = null;

public void transactionInsert()
{
DefaultTransactionDefinition dtd = new DefaultTransactionDefinition();
dtd.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus ts = transactionManager.getTransaction(dtd);
Connection conn = DataSourceUtils.getConnection(myDataSource);

try {
Statement stmt = conn.createStatement();
stmt.execute("insert into users values('sharpxiajun','sharpxiajun',true)");
stmt.execute("insert into users values('java','java',true)");
transactionManager.commit(ts);
log.info("事务成功完成!");
} catch (SQLException e) {
transactionManager.rollback(ts);
log.info("事务执行失败");
System.out.println("原因:" + e.getMessage());
}
}

}

5.在cn.com.sharpxiajun.junittest.transactionstudy包下建立测试类SpringTransactionImplTest,代码如下:

package cn.com.sharpxiajun.junittest.transactionstudy;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;

import cn.com.sharpxiajun.transactionstudy.SpringTransaction;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:conf/applicationContext.xml" })
@TransactionConfiguration(defaultRollback = false)
public class SpringTransactionImplTest extends
AbstractTransactionalJUnit4SpringContextTests {

@Autowired
private SpringTransaction springTransaction = null;

public SpringTransactionImplTest()
{
System.out.println("初始化测试类....");
}

@Before
public void setUp() throws Exception
{
System.out.println("测试开始....");
}

@After
public void tearDown() throws Exception
{
System.out.println("测试结束!!");
}

@Test
public void testTransactionInsert()
{
springTransaction.transactionInsert();
}

}

运行测试类,结果如下:

log4j: Parsing for [root] with value=[INFO,CONSOLE,STDOUT].
log4j: Level token is [INFO].
log4j: Category root set to INFO
log4j: Parsing appender named "CONSOLE".
log4j: Parsing layout options for "CONSOLE".
log4j: Setting property [conversionPattern] to [%d{yyyy-MM-dd HH:mm:ss} %c{1} - %m%n].
log4j: End of parsing for "CONSOLE".
log4j: Parsed "CONSOLE" options.
log4j: Parsing appender named "STDOUT".
log4j:ERROR Could not find value for key log4j.appender.STDOUT
log4j: Parsing for [com.ibatis.common.jdbc.SimpleDataSource] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis.common.jdbc.SimpleDataSource set to DEBUG
log4j: Handling log4j.additivity.com.ibatis.common.jdbc.SimpleDataSource=[null]
log4j: Parsing for [java.sql.Connection] with value=[DEBUG].
log4j: Level token is [DEBUG].
log4j: Category java.sql.Connection set to DEBUG
log4j: Handling log4j.additivity.java.sql.Connection=[null]
log4j: Parsing for [com.ibatis] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis set to DEBUG
log4j: Handling log4j.additivity.com.ibatis=[null]
log4j: Parsing for [java.sql.Statement] with value=[DEBUG].
log4j: Level token is [DEBUG].
log4j: Category java.sql.Statement set to DEBUG
log4j: Handling log4j.additivity.java.sql.Statement=[null]
log4j: Parsing for [com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate set to DEBUG
log4j: Handling log4j.additivity.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=[null]
log4j: Parsing for [com.ibatis.common.jdbc.ScriptRunner] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis.common.jdbc.ScriptRunner set to DEBUG
log4j: Handling log4j.additivity.com.ibatis.common.jdbc.ScriptRunner=[null]
log4j: Parsing for [java.sql.PreparedStatement] with value=[DEBUG].
log4j: Level token is [DEBUG].
log4j: Category java.sql.PreparedStatement set to DEBUG
log4j: Handling log4j.additivity.java.sql.PreparedStatement=[null]
log4j: Finished configuring.
log4j:ERROR Could not instantiate appender named "STDOUT".
初始化测试类....
2011-10-19 22:35:11 XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [conf/applicationContext.xml]
2011-10-19 22:35:11 GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2bc3f5: startup date [Wed Oct 19 22:35:11 CST 2011]; root of context hierarchy
2011-10-19 22:35:11 PropertyPlaceholderConfigurer - Loading properties file from class path resource [conf/constants.properties]
2011-10-19 22:35:11 DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@19ba640: defining beans [usersDao,springTransaction,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,propertyConfigurer,myDataSource,sqlMapClient,sqlMapClientTemplate,transactionManager,methodServiceAdvisor,org.springframework.aop.config.internalAutoProxyCreator,baseServiceMethods,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0]; root of factory hierarchy
2011-10-19 22:35:13 TransactionalTestExecutionListener - Began transaction (1): transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@171194d]; rollback [false]
测试开始....
2011-10-19 22:35:13 SpringTransactionImpl - 事务成功完成!
测试结束!!
2011-10-19 22:35:13 TransactionalTestExecutionListener - Committed transaction after test execution for test context [[TestContext@1c5af2e testClass = SpringTransactionImplTest, locations = array<String>['classpath:conf/applicationContext.xml'], testInstance = cn.com.sharpxiajun.junittest.transactionstudy.SpringTransactionImplTest@1702c48, testMethod = testTransactionInsert@SpringTransactionImplTest, testException = [null]]]
2011-10-19 22:35:13 GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2bc3f5: startup date [Wed Oct 19 22:35:11 CST 2011]; root of context hierarchy
2011-10-19 22:35:13 DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@19ba640: defining beans [usersDao,springTransaction,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,propertyConfigurer,myDataSource,sqlMapClient,sqlMapClientTemplate,transactionManager,methodServiceAdvisor,org.springframework.aop.config.internalAutoProxyCreator,baseServiceMethods,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0]; root of factory hierarchy

数据库结果如下:

操作成功了!!!

6.程序讲解:

PlatformTransactionManager接口代码是:

// Compiled from PlatformTransactionManager.java (version 1.5 : 49.0, no super bit)
public abstract interface org.springframework.transaction.PlatformTransactionManager {

// Method descriptor #6 (Lorg/springframework/transaction/TransactionDefinition;)Lorg/springframework/transaction/TransactionStatus;
public abstract org.springframework.transaction.TransactionStatus getTransaction(org.springframework.transaction.TransactionDefinition arg0) throws org.springframework.transaction.TransactionException;

// Method descriptor #11 (Lorg/springframework/transaction/TransactionStatus;)V
public abstract void commit(org.springframework.transaction.TransactionStatus arg0) throws org.springframework.transaction.TransactionException;

// Method descriptor #11 (Lorg/springframework/transaction/TransactionStatus;)V
public abstract void rollback(org.springframework.transaction.TransactionStatus arg0) throws org.springframework.transaction.TransactionException;
}

commit():事务提交

rollback():事务回滚

getTransaction()方法用于获取当前线程中的一个事务的TransactionStatus对象,他可能代表一个新的事务,也可能代表一个存在的事务,TransactionStatus接口定义如下:

// Compiled from TransactionStatus.java (version 1.5 : 49.0, no super bit)
public abstract interface org.springframework.transaction.TransactionStatus extends org.springframework.transaction.SavepointManager {

// 判断是不是新事务
public abstract boolean isNewTransaction();

// Method descriptor #8 ()Z
public abstract boolean hasSavepoint();

// 设置事务回滚
public abstract void setRollbackOnly();

// 判断事务是否回滚
public abstract boolean isRollbackOnly();

// Method descriptor #11 ()V
public abstract void flush();

// 判断事务是否结束,即事务已成功提交或失败回滚
public abstract boolean isCompleted();
}

TransactionStatus代表事务本身,并提供了事务简单操作和状态查询方法。

TransactionDefinition也是一个接口,它主要是定义事务的传播行为、隔离级别、事务只读和事务超时属性,它的方法比较多,比较常有的有:

方法名说明
public final void setIsolationLevel(int isolationLevel); 设置事务的隔离级别。
public final void setReadOnly(boolean readOnly); 设置事务的只读属性
public final void setName(java.lang.String name); 设置事务名称,默认是NULL,没有名字
public final void setPropagationBehavior(int propagationBehavior); 设置事务的传播行为
public final void setTimeout(int timeout); 设置超时属性

 第四章 编程式事务--使用TransactionTemplate模板

1.工程结构目录不变,首先修改applicationContext.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:context
="http://www.springframework.org/schema/context"
xmlns:aop
="http://www.springframework.org/schema/aop"
xmlns:tx
="http://www.springframework.org/schema/tx"
xsi:schemaLocation
="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<!-- 扫描该路径下的spring组件 -->
<context:component-scan base-package="cn.com.sharpxiajun" />

<!-- 读取资源文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:conf/constants.properties</value>
</list>
</property>
</bean>

<!-- 配置数据源 -->
<!-- <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driverClass}"/>
<property name="jdbcUrl" value="${db.jdbcUrl}"/>
<property name="user" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean>
-->

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driverClass}"/>
<property name="url" value="${db.jdbcUrl}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean>

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>classpath:conf/SqlMapConfig.xml</value>
</property>
<property name="dataSource" ref="myDataSource"/>
</bean>

<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient">
<ref local="sqlMapClient"/>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="myDataSource"/>
</property>
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="propagationBehaviorName">
<value>PROPAGATION_REQUIRED</value>
</property>
</bean>

<!-- 将我自己定义的拦截器生成bean -->
<bean id="methodServiceAdvisor" class="cn.com.sharpxiajun.common.aop.MethodServiceAdvisor"/>

<aop:config>
<!--配置规则,满足以下规则的将拦截,第一个*表示所有返回类型,第二个表示service包下的所有class,第三个表示所有方法-->
<aop:pointcut id="baseServiceMethods" expression="execution(* cn.com.sharpxiajun.service.*.*(..))"/>
<!-- 符合上面规则的拦截器都会调用到methodServiceAdvisor -->
<aop:advisor advice-ref="methodServiceAdvisor" pointcut-ref="baseServiceMethods"/>
</aop:config>

</beans>

我在配置文件里添加了<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">的spring bean。

2.修改cn.com.sharpxiajun.transactionstudy包下的SpringTransaction接口,代码如下:

package cn.com.sharpxiajun.transactionstudy;

public interface SpringTransaction {

public void transactionInsert();

public void transactionTemplateInsert();

}

3.在包cn.com.sharpxiajun.transactionstudy.impl实现SpringTransaction接口,代码如下:

package cn.com.sharpxiajun.transactionstudy.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import cn.com.sharpxiajun.transactionstudy.SpringTransaction;

@SuppressWarnings("unchecked")
@Scope("prototype")
@Service("springTransaction")
public class SpringTransactionImpl implements SpringTransaction {

private final static Log log = LogFactory.getLog(SpringTransactionImpl.class);

@Autowired
@Qualifier("myDataSource")
private BasicDataSource myDataSource = null;

@Autowired
@Qualifier("transactionManager")
private PlatformTransactionManager transactionManager = null;

@Autowired
@Qualifier("transactionTemplate")
private TransactionTemplate transactionTemplate = null;

@Override
public void transactionInsert()
{
DefaultTransactionDefinition dtd = new DefaultTransactionDefinition();
dtd.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus ts = transactionManager.getTransaction(dtd);
Connection conn = DataSourceUtils.getConnection(myDataSource);

try {
Statement stmt = conn.createStatement();
stmt.execute("insert into users values('sharpxiajun','sharpxiajun',true)");
stmt.execute("insert into users values('java','java',true)");
transactionManager.commit(ts);
log.info("事务成功完成!");
} catch (SQLException e) {
transactionManager.rollback(ts);
log.info("事务执行失败");
System.out.println("原因:" + e.getMessage());
}
}

@Override
public void transactionTemplateInsert() {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status){
Connection conn = DataSourceUtils.getConnection(myDataSource);
try {
Statement stmt = conn.createStatement();
stmt.execute("insert into users values('test001','test001',true)");
stmt.execute("insert into users values('test002','test002',true)");
log.info("Template事务成功完成!");
} catch (SQLException e) {
log.info("Template事务执行失败");
System.out.println("Template原因:" + e.getMessage());
}
return null;
}
});
}

}

4.修改cn.com.sharpxiajun.junittest.transactionstudy里面的测试类SpringTransactionImplTest,代码如下:

package cn.com.sharpxiajun.junittest.transactionstudy;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;

import cn.com.sharpxiajun.transactionstudy.SpringTransaction;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:conf/applicationContext.xml" })
@TransactionConfiguration(defaultRollback = false)
public class SpringTransactionImplTest extends
AbstractTransactionalJUnit4SpringContextTests {

@Autowired
private SpringTransaction springTransaction = null;

public SpringTransactionImplTest()
{
System.out.println("初始化测试类....");
}

@Before
public void setUp() throws Exception
{
System.out.println("测试开始....");
}

@After
public void tearDown() throws Exception
{
System.out.println("测试结束!!");
}

/*@Test
public void testTransactionInsert()
{
springTransaction.transactionInsert();
}
*/

@Test
public void testTransactionTemplateInsert()
{
springTransaction.transactionTemplateInsert();
}

}

运行结果如下:

log4j: Parsing for [root] with value=[INFO,CONSOLE,STDOUT].
log4j: Level token is [INFO].
log4j: Category root set to INFO
log4j: Parsing appender named "CONSOLE".
log4j: Parsing layout options for "CONSOLE".
log4j: Setting property [conversionPattern] to [%d{yyyy-MM-dd HH:mm:ss} %c{1} - %m%n].
log4j: End of parsing for "CONSOLE".
log4j: Parsed "CONSOLE" options.
log4j: Parsing appender named "STDOUT".
log4j:ERROR Could not find value for key log4j.appender.STDOUT
log4j:ERROR Could not instantiate appender named "STDOUT".
log4j: Parsing for [com.ibatis.common.jdbc.SimpleDataSource] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis.common.jdbc.SimpleDataSource set to DEBUG
log4j: Handling log4j.additivity.com.ibatis.common.jdbc.SimpleDataSource=[null]
log4j: Parsing for [java.sql.Connection] with value=[DEBUG].
log4j: Level token is [DEBUG].
log4j: Category java.sql.Connection set to DEBUG
log4j: Handling log4j.additivity.java.sql.Connection=[null]
log4j: Parsing for [com.ibatis] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis set to DEBUG
log4j: Handling log4j.additivity.com.ibatis=[null]
log4j: Parsing for [java.sql.Statement] with value=[DEBUG].
log4j: Level token is [DEBUG].
log4j: Category java.sql.Statement set to DEBUG
log4j: Handling log4j.additivity.java.sql.Statement=[null]
log4j: Parsing for [com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate set to DEBUG
log4j: Handling log4j.additivity.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=[null]
log4j: Parsing for [com.ibatis.common.jdbc.ScriptRunner] with value=[debug].
log4j: Level token is [debug].
log4j: Category com.ibatis.common.jdbc.ScriptRunner set to DEBUG
log4j: Handling log4j.additivity.com.ibatis.common.jdbc.ScriptRunner=[null]
log4j: Parsing for [java.sql.PreparedStatement] with value=[DEBUG].
log4j: Level token is [DEBUG].
log4j: Category java.sql.PreparedStatement set to DEBUG
log4j: Handling log4j.additivity.java.sql.PreparedStatement=[null]
log4j: Finished configuring.
初始化测试类....
2011-10-20 01:13:30 XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [conf/applicationContext.xml]
2011-10-20 01:13:31 GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2bc3f5: startup date [Thu Oct 20 01:13:31 CST 2011]; root of context hierarchy
2011-10-20 01:13:31 PropertyPlaceholderConfigurer - Loading properties file from class path resource [conf/constants.properties]
2011-10-20 01:13:31 DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@19ba640: defining beans [usersDao,springTransaction,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,propertyConfigurer,myDataSource,sqlMapClient,sqlMapClientTemplate,transactionManager,transactionTemplate,methodServiceAdvisor,org.springframework.aop.config.internalAutoProxyCreator,baseServiceMethods,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0]; root of factory hierarchy
2011-10-20 01:13:32 TransactionalTestExecutionListener - Began transaction (1): transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@16b6c55]; rollback [false]
测试开始....
2011-10-20 01:13:32 SpringTransactionImpl - Template事务成功完成!
测试结束!!
2011-10-20 01:13:32 TransactionalTestExecutionListener - Committed transaction after test execution for test context [[TestContext@c8c7d6 testClass = SpringTransactionImplTest, locations = array<String>['classpath:conf/applicationContext.xml'], testInstance = cn.com.sharpxiajun.junittest.transactionstudy.SpringTransactionImplTest@1b7b407, testMethod = testTransactionTemplateInsert@SpringTransactionImplTest, testException = [null]]]
2011-10-20 01:13:32 GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2bc3f5: startup date [Thu Oct 20 01:13:31 CST 2011]; root of context hierarchy
2011-10-20 01:13:32 DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@19ba640: defining beans [usersDao,springTransaction,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,propertyConfigurer,myDataSource,sqlMapClient,sqlMapClientTemplate,transactionManager,transactionTemplate,methodServiceAdvisor,org.springframework.aop.config.internalAutoProxyCreator,baseServiceMethods,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0]; root of factory hierarchy

数据库结果如下:

操作成功了!!!

这个的讲解在下一章。

最后总结下:事务在开发里太重要了,但是也是不太容易掌握的知识,因为我们现在的框架都太强大,强大到我们实际编程里往往很少直接编写事务,这个其实不利于我们的技术成长。最近做项目才发现自己对事务的知识掌握的不够,我后面的文章会尽力把这块知识讲清楚。

原文地址:https://www.cnblogs.com/sharpxiajun/p/2218407.html