您的位置:首页 > 编程语言 > Java开发

Spring 事务管理原理探究

2010-08-22 22:12 429 查看
本博中关于Spring的文章:Spring IOC和AOP原理Spring事务原理探究Spring配置文件属性详解Spring中的代理模式

 

Spring的事务管理机制实现的原理,就是通过这样一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的 方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。Spring中的AOP实 现更为复杂和灵活,不过基本原理是一致的。

统观spring事务,围绕着两个核心PlatformTransactionManager和TransactionStatus 

spring提供了几个关于事务处理的类: 

TransactionDefinition //事务属性定义

TranscationStatus //代表了当前的事务,可以提交,回滚。

PlatformTransactionManager这个是spring提供的用于管理事务的基础接口,其下有一个实现的抽象类AbstractPlatformTransactionManager,我们使用的事务管理类例如DataSourceTransactionManager等都是这个类的子类。

一般事务定义步骤:

TransactionDefinition td = new TransactionDefinition();
TransactionStatus ts = transactionManager.getTransaction(td);
try
{ //do sth
  transactionManager.commit(ts);
}
catch(Exception e){transactionManager.rollback(ts);}
 

spring提供的事务管理可以分为两类:编程式的和声明式的。编程式的,比较灵活,但是代码量大,存在重复的代码比较多;声明式的比编程式的更灵活。

编程式主要使用transactionTemplate。省略了部分的提交,回滚,一系列的事务对象定义,需注入事务管理对象.
void add()
{
    transactionTemplate.execute( new TransactionCallback(){
        pulic Object doInTransaction(TransactionStatus ts)
       { //do sth}
    }
}

声明式:
使用TransactionProxyFactoryBean:
<bean id="userManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

  <property name="transactionManager"><ref bean="transactionManager"/></property>

  <property name="target"><ref local="userManagerTarget"/></property>

  <property name="transactionAttributes">

   <props>

    <prop key="insert*">PROPAGATION_REQUIRED</prop>

    <prop key="update*">PROPAGATION_REQUIRED</prop>

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

   </props>

  </property>

 </bean>

围绕Poxy的动态代理 能够自动的提交和回滚事务

org.springframework.transaction.interceptor.TransactionProxyFactoryBeanz中的transactionAttributes属性的Props中一个为key为方法名,支持*,一个为Propagation传播行为类别:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 

PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。 

PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。 

PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。 

PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 

PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。 

PROPAGATION_NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
Isolation Level(事务隔离等级): 从名字上来所串行化,可重复读,提交读,未提交读

1、Serializable:最严格的级别,事务串行执行,资源消耗最大; 
2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。 
3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。 
4、Read Uncommitted:保证了读取过程中不会读取到非法数据。

隔离级别在于处理多事务的并发问题。 并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,这需要查看数据库教材的可串行化条件判断了。 

并发中可能发生的3中不讨人喜欢的事情 :
1: Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。 
2: non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。
即同一个事务读取数据的值是不一样的
3: phantom reads--幻象读数据,这个和non-repeatable
reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select
account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。 

事务的隔离等级与三种并发问题的杜绝: 以下四者形成了完美的对角线关系

              Dirty reads non-repeatable
reads phantom reads 

Serializable    不会        不会           不会 
Repetable READ  不会        不会           会  
READ Comminted  不会        会             会 
Read Uncommitted 会                 会                           会 

对于事务的隔离等级可以在Hibernate.properties文件中或cfg.xml中进行配置,数字分别对应以上的隔离等级类别

<property name=” hibernate.connection.isolation”>4</propert>


在Spring与与Hibernate整合时,可以使用声明式配置AOP来做事务处理org.springframework.orm.hibernate3.HibernateTransactionManager,如下

<!--事务管理器配置-->  

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  

   <property name="sessionFactory">  

    <ref local="sessionFactory"/>  

   </property>  

</bean>  

<!--AOP 事务配置-->  

<tx:advice id="txAdvice" transaction-manager="transactionManager">  

      <!-- the transactional semantics... -->  

       <tx:attributes>  

           <!-- all methods starting with 'get' are read-only -->  

           <tx:method name="get*" read-only="true"/>  

           <tx:method name="add*" read-only="false"/>  

           <tx:method name="insert*" read-only="false"/>  

           <tx:method name="update*" read-only="false"/>  

           <tx:method name="del*" read-only="false"/>  

           <tx:method name="audit*" read-only="false"/>  

  

           <!-- other methods use the default transaction settings (see below) -->  

           <tx:method name="*"/>  

        </tx:attributes>  

    </tx:advice>  

    <aop:config>  

        <aop:pointcut id="SysFileOperation" expression="execution(* com.biz.system.SysFilesBiz.*(..))"/>  

        <aop:advisor advice-ref="txAdvice" pointcut-ref="SysFileOperation"/>  

    </aop:config>  

Spring 事务管理创造性的解决了很多以前要用重量级的应用服务器才能解决的事务问题,那么其实现原理一定很深奥吧?可是如果读者仔细研究了Spring事务管理的代码以后就会发现,事务管理其实也是如此简单的事情。这也印证了在本书开头的一句话“重剑无锋、大巧不工”,Spring并没有使用什么特殊的API,它运行的原理就是事务的原理。下面是DataSourceTransactionManager的启动事务用的代码(经简化):

protected void doBegin(Object transaction, TransactionDefinition definition)
{
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try
{
if (txObject.getConnectionHolder() == null)
{
Connection newCon = this.dataSource.getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();

Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
if (con.getAutoCommit())
{
txObject.setMustRestoreAutoCommit(true);
con.setAutoCommit(false);
}
txObject.getConnectionHolder().setTransactionActive(true);
// Bind the session holder to the thread.
if (txObject.isNewConnectionHolder())
{
TransactionSynchronizationManager.bindResource(getDataSource(),txObject.getConnectionHolder());
}
}
catch (SQLException ex)
{
DataSourceUtils.releaseConnection(con, this.dataSource);
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}

在调用一个需要事务的组件的时候,管理器首先判断当前调用(即当前线程)有没有一个事务,如果没有事务则启动一个事务,并把事务与当前线程绑定。Spring使用TransactionSynchronizationManager的bindResource方法将当前线程与一个事务绑定,采用的方式就是ThreadLocal,这可以从TransactionSynchronizationManager类的代码看出。

public abstract class TransactionSynchronizationManager
{
……
private static final ThreadLocal currentTransactionName = new ThreadLocal();
private static final ThreadLocal currentTransactionReadOnly = new ThreadLocal();
private static final ThreadLocal actualTransactionActive = new ThreadLocal(); ……
}


从doBegin的代码中可以看到在启动事务的时候,如果Connection是自动提交的(也就是getAutoCommit()方法返回true)则事务管理就会失效,所以首先要调用setAutoCommit(false)方法将其改为非自动提交的。setAutoCommit(false)这个动作在有的JDBC驱动中会非常耗时,所以最好在配置数据源的时候就将“autoCommit”属性配置为true。

刚学spring,在spring的事务编程中有一些事务的原理问题不明白?

1,一般在单一数据库中,如果手动方式写事务的话,我对事务的理解就是

(1)首先在数据库连接中

con.setAutocommit(false)设置手动提交事务

(2)然后执行sql语句,最后提交。

stmt = con.createStatement();

stmt.executeUpdate("UPDATE user SET age = 18 WHERE id = 'erica'");

con.commit()

2,可是在spring的声明式的事务编程中,我有点不明白,举以下一个例子,但这个例子中,我没法体现上面的事务步骤?首先设置con.setAutocommit(false),然后提交

(1)假设数据源是基于绑定在容器的JNDI上面,并且只有一个数据库

在spring配置文件中,为了演示的方便,我简略了一些代码,有些代码没有写出来,

<bean id="dataSource"  class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>jdbc/sample</value>
</property>
</bean>
<bean id="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"/>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="transactionManager">
<ref local="transactionManager" />
</property>
</bean>


(2)在java的源文件中,是这样写的:
public class UserDAO {
private DataSource dataSource;
private PlatformTransactionManager transactionManager;
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insertUser() {
TransactionTemplate tt =new TransactionTemplate(getTransactionManager());
tt.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
JdbcTemplate jt = new JdbcTemplate(getDataSource());
jt.update("insert into users (username) values ('xiaxin');");
jt.update("insert into users (id,username) values(2,'erica');");
return null;
}
});
}
}


(3)其中我的问题是针对以上的java源代码,它如何体现了类似手动写代码时候的事务原理?

首先设置手动提交事务,然后提交?

产生这个问题的疑惑是:以下这段代码,Spring中的JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还

无法保证数据操作的原子性(要么全部生效,要么全部无效),

JdbcTemplate jt = new JdbcTemplate(getDataSource());

jt.update("insert into users (username) values ('xiaxin');");//执行这sql语句后,我认为应该马上提交,何来的事务?

jt.update("insert into users (id,username) values(2,'erica');");//执行这sql语句后,我认为应该马上提交,何来的事务?

return null;

我的问题在于:::::::::

虽然,我举个例子(UserDAO类中,它已经获得一个transactionManager,而且,这个transactionManager已经获得了一个dataSource(不知道这样做有什么用?),好像这个transactionManager在UserDAO的事务中不起什么作用,反而起作用的JdbcTemplate 又体现不了手动写事务时的事务原理?为什么,请大家解释一下吧

我们都知道事务管理是通过获得方法里的数据库连接实现的,Spring怎么就这么聪明,知道要用哪个连接来管理事务,其中的奥迷不是这么简单哦。。

通过研究Spring的代码,发现它在方法调用之前,把数据库连接放进了ThreadLocal里面,方法里面用HibernateTemplate的连接用的其实是早就在ThreadLocal里的。终于明白了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息