`

spring日记(七):声明式事务机制

阅读更多

数据库事务必须满足四个特性:

*  原子性(Atomic):表示一个事务的多个数据库操作时一个整体,要么都成功提交,要么都撤销

*  一致性(Consistency):事务操作成功后,数据库状态和它的业务状态一致,数据不会被破坏

*  隔离性(Isolation):在并发数据操作时,不同事务拥有各自的数据空间,它们的操作彼此不影响。数据库有多种隔离级别,隔离级别越高,数据越安全

*  持久性(Durability):一旦事务提交后,所有数据操作都必须被持久化到数据库中,即使事务提交后,数据库马上崩溃,在重启时,也必须保证能通过某种机制恢复。

幻读是指读到了其他已经提交事务的新增数据(insert),而不可重复读是指读到其他已经提交事务的更改数据(update或者delete)

>> spring的事务管理器实现类

spring将事务管理委托给底层具体的持久化实现框架完成。因此,spring为不同的持久化框架提供了PlatformTransactionManager接口的实现类:

org.springframework.orm.jpa.JpaTransactionManager:使用JPA进行持久化

org.springframework.orm.hibernate3.HibernateTransactionManager:使用Hibernate3.0版本进行持久化

org.springframework.orm.jdo.JdoTransactionManager:使用JDO进行持久化

org.springframework.jdbc.datasource.DataSourceTransactionManager:使用spring JDBC 或iBatis等基于DataSource数据源的持久化技术

org.springframework.transaction.jta.JtaTransactionMnager:具有多个数据源的全局事务使用该事务管理器(不管采用何种持久化技术)

下面看一下几个常见的事务管理器的配置:

* Spring JDBC 和 iBatis

<context:property-placeholder location="classpath:jdbc.properties"/>
 
<bean id="dataSource"
  class="org.apache.commons.dbcp.BasicDataSource"
  destroy-method="close"
  p:driverClassName="${jdbc.driverClassName}"
  p:url="${jdbc.url}"
  p:username="${jdbc.username}"
  p:password="${jdbc.password}"/>
 
<!-- 基于数据源的事务管理器 -->
<bean id="txManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
  p:dataSource-ref="dataSource"/>

spring的事务同步管理器为:TransactionSynchronizationManager,spring框架为不同的持久化技术提供了一套从TransactionSynchronizationManager中获取对应线程绑定资源的工具类:

org.springframework.jdbc.datasource.DataSourceUtils、org.springframework.orm.hibernate3.SessionFactoryUtils等。如果使用模板类则不需要这些,如果手工的话就应该使用这些工具类获取connection或者session等这些非线程安全的资源了。内部使用ThreadLocal技术实现的。

>> spring的事务传播机制

spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时候事务如何进行传播,如下:

* PROPAGATION_REQUIRED:如果当前没有事务,新建一个事务,如果已经存在于一个事务中,加入这个事务中。这个是最常见的选择

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

* PROPAGATION_MANDATORY:使用当前事务,如果当前没有事务,抛出异常

* PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,将当前事务挂起

* PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,把当前事务挂起

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

* PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似操作

有两个类:UserService(有login()和updateLastLoginTime()方法)、ScoreService(有addScore()方法),login方法调用updateLastLoginTime()后再调用ScoreService的addScore方法,而两个类均设置了事务管理,当内部方法调用的时候,代码自动合并,不会发生任何的传播行为,而当调用其他类的方法的时候,会通过上面的7种定义的传播机制进行事务传播行为,默认是PROPAGATION_REQUIRED,也就是说这三个方法工作在同一个事务中,一损俱损,一荣俱荣。

还有一个要注意的:在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中,而如果这些相互嵌套调用的方法工作于不同的线程中,则不同的线程下的事务方法工作在各自独立的事务中。

下面基于aop/tx命名空间配置一个事务管理器:

package com.springzoo.service;
 
import com.springzoo.domain.Forum;
import com.springzoo.domain.Topic;
 
public interface BbtForum {
    void addTopic(Topic topic) throws Exception;
    void updateForum(Forum forum);
    Forum getForum(int forumId);
    int getForumNum();
}

下面是接口的实现:

package com.springzoo.service.impl;
 
import com.springzoo.dao.ForumDao;
import com.springzoo.dao.PostDao;
import com.springzoo.dao.TopicDao;
import com.springzoo.domain.Forum;
import com.springzoo.domain.Topic;
import com.springzoo.service.BbtForum;
 
public class BbtForumImpl implements BbtForum {
    private ForumDao forumDao;
    private TopicDao topicDao;
    private PostDao postDao;
    public void addTopic(Topic topic) throws Exception {
        topicDao.addTopic(topic);
        if(true) throw new PessimisticLockingFailureException("fail");
        postDao.addPost(topic.getPost());
    }
    public Forum getForum(int forumId) {
        return forumDao.getForum(forumId);
    }
    public void updateForum(Forum forum) {
        forumDao.updateForum(forum);
    }
    public int getForumNum() {
        return forumDao.getForumNum();
    }
     
    public void setForumDao(ForumDao forumDao) {
        this.forumDao = forumDao;
    }
    public void setPostDao(PostDao postDao) {
        this.postDao = postDao;
    }
    public void setTopicDao(TopicDao topicDao) {
        this.topicDao = topicDao;
    }
}

下面是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:p="http://www.springframework.org/schema/p"
    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-3.0.xsd
    //http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    //http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <import resource="classpath:applicationContext-dao.xml" />
    <bean id="bbtForum"
        class="com.springzoo.service.impl.BbtForumImpl"
        p:forumDao-ref="forumDao"
        p:topicDao-ref="topicDao"
        p:postDao-ref="postDao"/>
 
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="dataSource"/>
 
    <aop:config>
        <aop:pointcut id="serviceMethod"
            expression="execution(* com.springzoo.service.*Forum.*(..))" />
        <aop:advisor pointcut-ref="serviceMethod"
            advice-ref="txAdvice" />
    </aop:config>
    <tx:advice id="txAdvice" >
        <tx:attributes> 
            <tx:method name="get*" read-only="true"/>
            <tx:method name="add*" rollback-for="PessimisticLockingFailureException"/>
            <tx:method name="update*"/>         
        </tx:attributes>
    </tx:advice>
</beans>

<tx:method>元素属性表:

属性名 | 是否必须 | 默认值 | 描述

name | 是 | 与事务属性关联的方法名,可以使用通配符* | 如get*、handle*、on*Event等

propagation | 否 | REQUIRED | 事务传播行为,可选值为REQUIRED、SUPPORTS、MANDATORY等

isolation | 否 | DEFAULT | 事务隔离级别,可选值为DEFAULT、READ_UNCOMMITTED等

timeout | 否 | -1 | 事务超时时间秒,如果设置为-1,事务超时时间由底层事务系统决定

read-only | 否 | false | 事务是否只读

rollback-for | 否 | 所有RuntimeException回滚 | 触发事务回滚的Exception,用异常名称的片段进行匹配

no-rollback-for | 否 | 所有CheckedException不回滚 | 不触发回滚的Exception,用异常名称的片段匹配

>> 基于注解的事务管理

只需在子类上加入@Transactional即可,最好是放在业务实现类上,因为注解是不能被继承的,然后在配置文件中加一行代码:

<import resource="classpath:applicationContext-dao.xml" />
 
<bean id="bbtForum"
     class="com.springzoo.service.impl.BbtForumImpl"
     p:forumDao-ref="forumDao"
     p:topicDao-ref="topicDao"
     p:postDao-ref="postDao" />
      
<bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    p:dataSource-ref="dataSource"/>
 
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />

关于@Transactional的属性:

* propagation:事务传播行为,例如:@Transactional(propagation=Propagation.REQUIRES_NEW)

* isolation:事务隔离级别,例如:@Transactional(isolation=Isolation.READ_COMMITTED)

* readOnly:事务读写性,boolean型

* timeout:超时时间秒

* rollbackFor:一组异常类,遇到就回滚类型为:Class<? extends Throwable>[],默认为{},例如:@Transactional(rollbackFor={SQLException.class, IOException.lcass})

* rollbackForClassNmae:一组异常类名字

* noRollbackFor:一组异常类,遇到不回滚

* noRollbackForClassName:一组异常类型的名字,遇到不回滚

>> 多线程的困惑

单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。而一个类能够以单实例的方式运行的前提是无状态,也就是说该类不能有状态化的成员变量。我们知道传统的DAO必须持有一个Connection,而Connection即使状态化的对象。但spring通过ThreadLocal将有状态的变量Connection本地线程化,达到另一个层面上的线程无关从而实现线程安全。

>> 不能被spring AOP事务增强的方法:

基于接口的动态代理了:public之外的所有方法(还包括public static方法)

基于CGLib的动态代理:private、static、final(这个要特别注意了)

 

本人博客已搬家,新地址为:http://yidao620c.github.io/

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics