Spring笔记 - 事务管理

1. 事务管理

1.1 概述

事务的属性,ACID

  • 原子性Atomic,事务由一个或多个活动组成,这些活动只能全部成功或全部失败,任何一个活动失败,都将导致事务回滚

  • 一致性Consistent,事务的完成,不论成功或失败,业务必须处于一致的状态

  • 隔离性Isolation,多个并行的事务之间保持隔离

  • 持久性Durable,事务完成后,事务的结果需要持久化


1.2 JDBC事务管理

JDBC基于连接进行事务管理,默认开启自动提交。缺点在于不能跨数据库;且在使用连接池时,难以保证线程安全,可能出现死锁等问题;如果不使用连接池,又容易出现效率问题。

try {
    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/demoDb", username, userpassword);
    if(conn.getAutoCommit())
        conn.setAutoCommit(false); // 禁止自动提交,设置回滚点
    stmt = conn.createStatement();
    stmt.executeUpdate("alter table …"); //数据库更新操作1
    stmt.executeUpdate("insert into table …"); //数据库更新操作2
    conn.commit(); //事务提交
}catch(Exception ex) {
    ex.printStackTrace();
    try {
        conn.rollback(); //操作不成功则回滚
    } catch(Exception ex) {
        ex.printStackTrace();
    }
}


1.3 JTA事务管理

[参考]

- Java Transaction API,Java事务API,支持分布式事务服务。即在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。 

- 如果使用 JTA 界定事务,就需要有一个实现 javax.sql.XADataSource 、javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。XAConnections 是参与 JTA 事务的 JDBC 连接。需要用应用服务器的管理工具设置 XADataSource 。从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。J2EE 应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。 

- XA 连接与非 XA 连接不同。XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() ,而不能对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() 。

- JTA事务管理缺点在于实现复杂,侵入性大


1.4 Java EE容器事务

-  容器事务主要是Java EE应用服务器提供的,大多基于JTA,需要JNDI的支持,实现复杂。

- 相对编码实现JTA 事务管理,可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由Java EE应用服务器提供,可以简单的指定将哪个方法加入事务,一旦指定,容器将负责事务管理任务。通过这种方式可以将事务代码排除在逻辑编码之外,同时将所有困难交给 Java EE容器去解决。

- 使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上必须使用EJB。


1.5 Spring事务管理

1.5.1 优点

- 为不同的事务API提供统一的编程模型,事务API包括JTA、JDBC、Hibernate、JPA、JDO

- 支持声明式事务管理

- 提供比JTA更简单的编程式事务管理API

- 天然集成Spring数据访问抽象

1.5.2 Spring事务管理基础

- Spring并不直接管理事务,而是把事务管理的职责委托给JTA或其他持久化机制提供的平台相关的事务实现。

- PlatformTransactionManager是Spring事务的基础,可通过其实现管理事务,其实现例如:DataSourceTransactionManager、HibernateTransactionManager、JpaTransactionManager、JtaTransactionManager、JmsTransactionManager

public interface PlatformTransactionManager {
    TransactionStatus /* 线程状态设置和读取 */
        getTransaction(TransactionDefinition definition /* 定义txn的隔离、传播、超时、只读参数 */) throws TransactionException; // 非检查型的Exception
    void commit(TransactionStatus status) throws TransactionException; // 提交
    void rollback(TransactionStatus status) throws TransactionException; // 回滚
}

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
<!-- 配置各种TxnManager -->
<!-- JDBC数据源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!-- Hibernate数据源 -->
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- JTA数据源 -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
// 使用TxnManager
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
    // 业务逻辑代码 ...
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

1.5.3 使用事务同步资源

1.5.3.1 低层次方法

- 使用DataSourceUtils  (JDBC),  EntityManagerFactoryUtils  (JPA), SessionFactoryUtils (Hibernate), PersistenceManagerFactoryUtils (JDO)

// 当前线程中只会取得一个连接
// 而dataSource.getConnection()不同,每次可能取得不同的连接,视数据源例如连接池而定
Connection conn = DataSourceUtils.getConnection(dataSource);
DataSourceUtils.releaseConnection(conn, dataSource);

- 使用TransactionAwareDataSourceProxy,它是DataSource的代理,类似于JNDI dataSource。是非常底层的实现,不建议使用

1.5.3.2 高层次方法

- 使用基于Spring数据访问模板例如JdbcTemplate的持久化集成API

- 使用事务感知工厂Bean或代理来调用ORM自身的API

1.5.4 编程式事务管理

- 使用TransactionTemplate,更为推荐

public class UserService implements Service {
    private TransactionTemplate txnTempl;
    public UserService(PlatformTransactionManager txnMgr) {
        this.txnTempl = new TransactionTemplate(txnMgr);
    }
    
    // 有返回值的操作
    public Object someAction1() {
        return txnTempl.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        };
    }

    // 无返回值的操作
    public Object someAction2() {
        return txnTempl.execute(new TransactionCallbackWithoutResult() {
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                updateOperation2();
            }
        };
    }
}

- 使用PlatformTransactionManager,例子见1.5.2

1.5.5 声明式事务管理

- Spring声明式事务管理类似于EJB CMT,但既可以支持JTA事务,也可以支持JDBC、JPA、Hibernate等;可以应用于非EJB的类;提供声明式的回滚设置;还可以为指定异常类型配置回滚机制;不支持跨远程调用(用户线程)的事务管理

- Spring 2.0之后无需使用TransactionProxyFactoryBean

- 通过AOP Proxy实现

1.5.5.1 设置方法

<!-- XML设置方法 -->
<!-- 业务方法 -->
<bean id="stockService" class="x.y.z.service.DefaultStockService"/>
<!-- 数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务通知,这是xml设置方式,还支持tx注解,启用方法<tx:annotation-driven/> -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <!-- read-only,事务只读;rollback-for,默认为非检查型例如RuntimeException回滚,检查型不回滚 -->
        <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/> 
        <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
        <!-- propagation,传播性,默认为Required;timeout默认为-1即永不超时 -->
        <tx:method name="*" propagation="REQUIRED" timeout="30" />
    </tx:attributes>
</tx:advice>
<!-- 事务切面 -->
<aop:config>
    <aop:pointcut id="stockServiceOperation" expression="execution(* x.y.service.StockService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="stockServiceOperation"/>
</aop:config>
// 启用注解
@EnableTransactionManagement
@Configuration
public AppConfig {
    // ...
}

// 注解设置方法
// 在public方法上设置,AspectJ支持非public方法
@Transactional(propagation = Propagation.REQUIRED, timeout=30, isolation=Isolation.DEFAULT) 
public void updateStock() {
    // ...
}
// 推荐为具体类而非接口设置注解
@Transactional(readOnly = true)
public class QueryStockService implements Service {

}

1.5.5.2 多事务管理器支持

各方法运行在各自的事务管理器中

public class TransactionalService {
    @Transactional("order")
    public void setSomething(String name) { ... }
    @Transactional("account")
    public void doSomething() { ... }
}
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    <qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    <qualifier value="account"/>
</bean>

1.5.5.3 自定义注解快捷方式

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

@OrderTx
public void setSomething(String name) { ... }
@AccountTx
public void doSomething() { ... }

1.5.6 事务传播性Propagation

类型 说明
PROPAGATION_REQUIRED 如果存在一个事务,则在当前事务中运行。无则开启;Spring默认
PROPAGATION_SUPPORTS 如果存在一个事务,则在当前事务中运行。无则无事务运行
PROPAGATION_MANDATORY 如果存在一个事务,则在当前事务中运行。无则抛出异常
PROPAGATION_REQUIRES_NEW 如果存在一个事务,则挂起,并开启一个新事务运行
PROPAGATION_NOT_SUPPORTED 如果存在一个事务,则挂起,并无事务运行
PROPAGATION_NEVER 如果存在一个事务,则抛出异常
PROPAGATION_NESTED 如果存在一个事务,则开启一个嵌套的事务运行,嵌套事务不影响外部的事务;无则开启事务运行

1.5.7 隔离级别Isolation Level

- Spring框架提供程序级别的隔离特性,有别于数据库自身提供的隔离特性

 

Dirty Reads

脏读,读到了其他事务未提交的更新数据

Non-Repeatable Reads

不可重复读,在一次事务中,前后读取的数据不一致,通常是后面一次读取到了其他事务的更新数据

Phantom Reads

幻读,在一次事务中,前后读取的数据不一致,通常是后面一次读取到了其他事务的新增数据

Serializable N N N
Repeatable Read N N Y
Read Committed (Spring默认) N Y Y
Read Uncommitted Y Y Y

大多数数据库的默认隔离级别为Read Commited,如Sql Server、Oracle

少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎


你可能感兴趣的:(Spring笔记 - 事务管理)