引入spring事务管理

1.1 为什么用spring事务管理?

在没有spring事务管理器之前,java开发者通常有两种事务管理器可供选择:本地事务和全局事务。本地事务针对的是某一个具体资源(例如JDBC),使用起来也比较方便,但当程序需要处理多个事务资源时,本地事务则无法胜任了。全局事务解决了这个问题,一般都会采用JTA(JAVA transaction API)来实现全局事务。但全局事务的限制也很多,如必须使用JNDI数据源、需要支持XA协议的数据库、依赖于容器、性能低下、只支持单应用多数据源,不支持多应用多数据源等。
针对不同环境、不同事务策略,spring提供了一个事务管理器的抽象层,开发者通过这个抽象事务管理器进行管理事务,之后配置一个具体的事务实现层,就可以实现代码写一次,可以适用于所有的具体事务管理器API。

1.2 理解spring抽象事务管理器

spring抽象事务管理器采用的是策略模式,理解策略模式的关键点就是看它的策略。org.springframework.transaction.PlatformTransactionManager接口定义了相关方法

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

PlatformTransactionManager提供了3个方法,getTransaction根据TransactionDefinition中定义的相关信息创建或者返回一个已经存在的事务。返回的TransactionStatus代表着一个事务。commit和rollback分别表示提交事务和回滚事务。TransactionDefinition细节如下:

public interface TransactionDefinition {
    int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    String getName();
}

getPropagationBehavior获取事务的传播特性,可选值如下:

传播特性 解释
PROPAGATION_REQUIRED 0 支持当前事务环境,如果没有则创建一个
PROPAGATION_SUPPORTS 1 支持当前事务,没有不创建事务
PROPAGATION_MANDATORY 2 支持当前事务,没有则报错
PROPAGATION_REQUIRES_NEW 3 挂起当前事务,新建一个事务
PROPAGATION_NOT_SUPPORTED 4 如果存在事务则挂起
PROPAGATION_NEVER 5 如果存在事务则报错
PROPAGATION_NESTED 6 开启一个嵌套事务,很多数据库都不支持

事务的七种传播特性中,需要注意的一个点:用了事务管理器之后连接将由事务管理器管理了,例如用了PROPAGATION_NOT_SUPPORTED之后,第一次操作会获取一个新的connection,但之后的操作都是基于已获取的connection,如果用的是AbstractRoutingDataSource,在NOT_SUPPORTED传播特性的方法内切换数据源是无效的,因为选择数据源是在getConnection的时候确定的。

-- 切换数据源后,只有获取连接才会调用determineTargetDataSource使切换后的数据源生效
public Connection getConnection(String username, String password) throws SQLException {
  return determineTargetDataSource().getConnection(username, password);
}

getIsolationLevel获取事务的隔离级别,可选值如下:

隔离级别 解释
ISOLATION_DEFAULT -1 使用数据源提供的隔离级别
ISOLATION_READ_UNCOMMITTED 1 允许一个事务读取另一个事务还没提交的数据
ISOLATION_READ_COMMITTED 2 禁止脏读
ISOLATION_REPEATABLE_READ 4 禁止不可重读
ISOLATION_SERIALIZABLE 8 禁止幻读

一般数据库的默认级别都会设置成READ_COMMITTED,即禁止脏读。不可重读的意思是当一个事务内两次查询同一条数据,在第二次查询数据之前有个事务更改了这条数据并提交了事务,则第二次查询的数据是更改之后的数据,与第一次查询的不一致,即为不可重复读。设置成REPEATABLE_READ之后,更新那条数据的事务会等待查询那条数据的事务提交后才可以提交。幻读的意思是当 一个事务两次查询一个范围的数据, 在第二次查询之前有个事务插入了一条符合查询条件的事务,则会造成两次查询结果不一致。设置成禁止幻读之后,插入符合第一个查询条件的事务必须等查询事务提交之后,才可以被提交。可以看到,禁止不可重读和禁止幻读会对性能带来极大影响,一般数据库默认隔离级别都设置成禁止脏读。
getTimeout方法获取事务超时值,超过这个时间之后将会被回滚。isReadOnly标明这个事务是不是只读事务,如果有写操作会抛出异常。getName返回事务名字,便于在开发事务管理后台的时候区分事务,默认会设置成类全路径.方法名。
TransactionStatus像其它事务接口一样,提供了一些简单方式来控制事务执行和查询事务状态,详细如下:

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
1.3 声明spring事务管理器

无论使用声明式事务还是编程式事务,都需要指定具体的事务管理器实现类,一般我们采用依赖注入来指定具体的事务管理器。通常根据我们项目工作的环境来选择具体的事务管理器。
使用JDBC数据源的步骤:


    
    
    
    


    

使用JTA数据源的步骤:




    
    

使用hibernate数据源


    
    
        
            org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml
        
    
    
        
            hibernate.dialect=${hibernate.dialect}
        
    



    

1.4 使用spring事务管理器

声明式事务需要与AOP相结合才可以使用,AOP会给要进行事务管理的方法生成代理,在TransactionInterceptor的invoke方法中,选择具体的PlatformTransactionManager 。可以在xml中配置事务管理器要代理的类:



    
        
        
    
    
        
            
            
            
            
        
    

XML配置的一般是业务方法的默认事务行为。通常对于特殊方法,会采用@Transactional注解来配置事务的属性,需要使用tx:annotation-driven来启用事务注解支持。



    
     

    
        
    

属性名 默认值 解析
transaction-manager transactionManager 事务管理器的名字
mode proxy 代理模式,还可以是aspectj
proxy-target-class false 仅在mode="proxy"时生效,即默认使用jdk接口代理,true的话则使用类代理
order Ordered.LOWEST_PRECEDENCE 事务委托器的优先级

默认的代理模式是运行时织入,所以如果你用注解的方法不是public的,或者你不是通过代理对象而是直接方法内部调用,及时你用了@Transaction注解,也是不会生效的。Transaction注解有个比较重要的属性value,当有多个事务管理器时,可以通过设置这个属性选择具体的事务管理器

public class TransactionalService {
    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}



        ...
    



        ...
    

1.5 手动获取connection连接

如果某些特殊场景下,你需要用connection直接执行些sql,可以采取下面的方法。如果当前是在一个事务环境,则会返回对应的connection,否则会为你创建一个connection。针对JPA和Hibernate使用的是EntityManagerFactoryUtils和SessionFactoryUtils两个类。

Connection conn = DataSourceUtils.getConnection(dataSource);
1.6 编码方式使用事务管理器

如果你的应用只有几个方法需要用到事务,为了避免使用代理带来了性能损耗,可以采用编码方式来管理事务

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

你可能感兴趣的:(引入spring事务管理)