spring对事务的支持

1 spring对事务管理的支持

采用传统的JDBC方式操作数据库时候,发现和事务相关的代码穿插在具体的业务逻辑代码中,在具体处理逻辑时,不得不考虑事务的提交和回滚操作。

那么Spring如何时对事务实现管理和支持?

Spring为事务管理提供了统一的事务管理模版类TransactionTemplate,也就是说对传统的JDBC进行了再次包装。持久层不管采用的是什么技术,都可以通过该事务管理模版类来管理事务。通过TransactionTemplate和TransactionCallback的配合指定持久化操作,就可以通过编程的方式实现事务管理而无需关心资源的获取,释放,复用,事务同步以及异常处理。

2 Spring事务管理核心类


TransactionDefinition:用于定义事务的基本属性,将事务封装成一个java对象,定义了事务涉及的属性:超时时间,事务隔离级别以及事务传播行为等

事务的隔离级别:和Connection中描述的事务隔离级是一致的。此外还定义了一个默认的隔离级别,表示采用底层数据库设置的事务隔离级别。其实就是采用的Connection中定义的事务隔离级别

/**
 * Use the default isolation level of the underlying datastore.
 * All other levels correspond to the JDBC isolation levels.
 * @see java.sql.Connection
 */
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

超时时间:规定了事务必须在给定的时间内完成。如果超时事务将会被回滚

事务传播行为:规定了事务的传播行为。

只读事务:该事务只能读取数据,不能更新数据,在特定场合使用可以提高效率

PlatformTransactionManager:使用具体的TransactionDefinition创建事务并用TransactionStatus描述具体激活事务的状态,是对事务操作(提交,回滚)的封装

public interface PlatformTransactionManager {

	//获取事务状态,如果事务存在返回当前事务状态,如果不存在则新建
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

	//根据事务状态提交事务,如果事务被标记为roolback-only,该方法会执行回滚操作
	void commit(TransactionStatus status) throws TransactionException;

	//根据事务状态回滚事务,如果commit发生异常,该方法隐式调用
	void rollback(TransactionStatus status) throws TransactionException;

}

TransactionStatus:激活事务具体的状态,事务管理器通过该状态判断当前事务的执行情况,可以通过该接口简介回滚事务

public interface TransactionStatus extends SavepointManager, Flushable
public interface SavepointManager {
	//创建保存点
	Object createSavepoint() throws TransactionException;

	//回滚到对应的保存点
	void rollbackToSavepoint(Object savepoint) throws TransactionException;

	//释放保存点,成功提交后自动释放,
	void releaseSavepoint(Object savepoint) throws TransactionException;
}
public interface TransactionStatus extends SavepointManager, Flushable {

	//当前事务是否为一个新建事务
	boolean isNewTransaction();

	//当前事务是否含有保存点
	boolean hasSavepoint();

	//设置当前事务为RollbackOnly,通过该标识只能将当前事务回滚,
	//事务管理器将显示调用回滚方法或者抛出异常的方式回滚事务
	void setRollbackOnly();

	//事务是否被设置为RollbackOnly
	boolean isRollbackOnly();

	//事务是否完成
	boolean isCompleted();

}

3 Spring和mybatis

DataSourceTransactionManager就是Spring事务管理器的默认实现了,在使用Spring JDBC或Mybatis等基于datasource数据源持久化时使用该事务管理器,Spring提供的事务管理器默认实现还有很多eg:支持JTA事务管理器,支持Jdo事务管理器,支持Hibernate事务管理器等

 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
         <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="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
 </bean>

4 事务同步管理器

在传统的JDBC中使用Connection,Hibernate中的Session是不能够共享的。为了让DAO,Service做到单例,Spring提供事务同步管理器TransactionSynchronizationManager,底层采用ThreadLocal让不同的现成之间可以同时使用JDBC中使用Connection,Hibernate中的Session的变量副本。

在某些情况下我们需要获取Connection或者Session,Spring为获取这些资源提供对应的工具了。如获取Connection可以使用DataSourceUtils

public abstract class TransactionSynchronizationManager {
//保存每个线程当前事务资源
private static final ThreadLocal<Map<Object, Object>> resources =
		new NamedThreadLocal<>("Transactional resources");
//保存每个线程当前事务的名称
private static final ThreadLocal<String> currentTransactionName =
		new NamedThreadLocal<>("Current transaction name");
//保存每个线程当前事务read-only状态
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
		new NamedThreadLocal<>("Current transaction read-only status");
//保存每个线程当前事务隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
		new NamedThreadLocal<>("Current transaction isolation level");
//保存每个线程当前事务是否为激活
private static final ThreadLocal<Boolean> actualTransactionActive =
		new NamedThreadLocal<>("Actual transaction active");
}

5 事务传播行为

TransactionDefinition中不仅仅定义了事务的隔离级别,同时还定义了事务的传播行为

事务传播行为类型说明

PROPAGATION_REQUIRED

当前没有事务新建事务,已经存在加入到当前事务中,该中方式是最常见传播行为

PROPAGATION_SUPPORTS

支持当前事务,如果当前存在事务以事务方式运行,不存在以非事务方式运行

PROPAGATION_MANDATORY

使用当前事务,如果不存在抛出异常

PROPAGATION_REQUIRES_NEW

新建事务,当前存在事务就把当前事务刮起

PROPAGATION_NOT_SUPPORTED

以非事务方式运行,如果当前存在事务将当前事务挂起

PROPAGATION_NEVER 

不支持事务以非事务方式运行,如果当前存在事务,抛出异常

PROPAGATION_NESTED

当前存在事务,则在嵌套事务内运行,如果当前不存在事务就新建事务,

此中传播方式会发生事务的嵌套

6 编程式事务管

Spring为编程式事务管理提供了对应的模版类TransactionTemplate,该模版类也是线程安全的,我们可以在多个业务类中一共享的方式使用该模版类,该模版类有两个主要的方法

//设置事务管理器,可以根据持久层采用的技术设置不同事务管理器
public TransactionTemplate(PlatformTransactionManager transactionManager) {…}
//在TransactionCallback回调接口中定义需要以事务方式访问数据的逻辑代码
//如果该回调接口中发生异常,那么会自动回滚事务
public <T> T execute(TransactionCallback<T> action) throws TransactionException {…}
@Service
public class OrderService {

    @Resource
    private OrderDao orderDao;

    @Resource
    private TransactionTemplate transactionTemplate;

    public void queryOrder() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    //访问DAO层的API
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

当前我们可以采用默认Spring提供的TransactionCallbackWithoutResult同时我们也可以实现自己的TransactionCallback。采用编程式事务管理的方式Spring的侵入比较强,需要实现Spring事务管理对应的接口

7 声明式事务管理

该中方式下Spring对代码对逻辑代码的侵入性非常小,通常该中模式也是最常用的模式。Spring声明式事务管理是基于Spring AOP实现的。通过事务声明时的信息将对应的增强织入的目标方法中,织入的逻辑包含了资源的获取(Connection,Session),开始事务,提交/回滚事务以及异常转换等相关功能。

7.1 基于TransactionProxyFactoryBean(不推荐)

如下是对应的service类

@Service(value = "userService")
public class UserServiceImpl implements IUserService {

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

    private UserDao userDao;

    public int saveUser(UserEntity userEntity) throws Exception {
        try {
            return userDao.saveUser(userEntity);
        } catch (Exception e) {
            throw e;
        }
    }

    public int queryUser(String userName) throws Exception {
        try{
            return userDao.queryUserIsexist(userName);
        } catch (Exception e) {
            throw e;
        }
    }

    public UserEntity queryUser(String userName, String password) throws Exception {
        try{
            return userDao.queryUserIsexist(userName, password);
        } catch (Exception e) {
            throw e;
        }
    }
}

在xml配置步骤如下

1.声明事务管理器

2.配置目标对象依赖资源以及目标对象

3.配置事务代理工厂提供事务增强

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!--配置目标依赖bean以及目标对象-->
<bean id="userDao" class="com.mark.blog.dao.impl.UserDaoImpl"></bean>
<bean id="userServiceTarget" class="com.mark.blog.service.impl.UserServiceImpl">
   <property name="userDao" ref="userDao"></property>
</bean>

<!--配置事务代理工厂提供事务增强-->
<bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="optimize" value="true"></property>
    <property name="target" ref="userService"></property>
    <property name="transactionManager" ref="transactionManager"></property>

    <property name="transactionAttributes">
        <props>
            <!--只对query前缀的方法增强-->
            <!--传播行为,[[隔离级别] | [是否为只读事务] | [-发生异常回滚] | [+忽略异常正常提交]]
            中括号内的选项是可选的,多个选型之间采用逗号分割
            -Exception:在发生异常时候回滚
            +Exception:放生异常时忽略异常正常提交
            不管是+/-修饰的异常我们都采用设置规则,eg:+tion,凡是一tion结束的异常
            -->
            <prop key="query*">PROPAGATION_REQUEST, ISOLATION_READ_COMMIT,readOnly,-DaoExction,+BizException</prop>
            <!--匹配目标类的所有方法-->
            <prop key="*">PROPAGATION_REQUEST></prop>
        </props>
    </property>
</bean>

通过事务代理工厂提供事务的增强有明显的缺点,

1 事务代理工厂需要一个目标对象,一个目标对象(事务业务对象)需要一个代理工厂,如果存在多个目标对象,需要配置的工作量就加大可很多。

2 在指定事务方法时只能利用方法名称,没有办法使用方法其他的签名信息

7.2 基于命名空间配置事务管理器(推荐)

1.声明事务管理器

2.配置事务传播属性以及其他属性

3.定义切点以及引用事务增强

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 事务增强 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="query*" read-only="true" />
    </tx:attributes>
</tx:advice>

<!-- 使用事务增强 -->
<aop:config>
    <aop:pointcut id="method" expression="execution(* com.mark.blog.*impl.*.*(..))"></aop:pointcut>
    <aop:advisor advice-ref="transactionAdvice" pointcut-ref="method"/>
</aop:config>

步骤2通过<tx:method>中的属性定义事务的诸多属性都在该处进行了定义eg:事务隔离级别,传播行为,只读性等。通过该中方式将具体的事务和某中方法进行了绑定。

属性是否必需默认值描述
name和事务管理的方法名模式,需要自定义无默认值get*,query*For*
propagationREQUEST事务传播行为
isolationDEFAULT

事务隔离级

别默认采用底层数据库的事务隔离级别

timeout-1

事务超时时间

默认情况下超时时间采用

底层数据库的超时时间

read-onlyfalse事务只读性设置
rallback-for所有运行期异常回滚

触发事务回滚异常类型

多个异常之间采用逗号分隔

no-rallback-for所有异常不回滚事务,正常提交不回滚事务异常类型,多个异常采用逗号分隔

7.3 注解配置声明式事务

@Transactional(value = "transactionManager")
public class UserServiceImpl implements IUserService {

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

//    @Resource
    private IUserDao userDao;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
    public int saveUser(UserEntity userEntity) throws Exception {
        try {
            return userDao.saveUser(userEntity);
        } catch (Exception e) {
            throw e;
        }
    }

    @Transactional(timeout = 200, readOnly = true, rollbackFor = {SQLException.class,IllegalArgumentException.class},
            rollbackForClassName={"BizException", "DaoException"})
    public int queryUser(String userName) throws Exception {
        try{
            return userDao.queryUserIsexist(userName);
        } catch (Exception e) {
            throw e;
        }
    }
    
    @Transactional(noRollbackFor = {NullPointerException.class,FileNotFoundException.class},
            noRollbackForClassName={"SSTException", "SSDException"})
    public UserEntity queryUser(String userName, String password) throws Exception {
        try{
            return userDao.queryUserIsexist(userName, password);
        } catch (Exception e) {
            throw e;
        }
    }
}
@Transactional属性汇总
属性名默认值说明举例
value““

系统中存在多个事务管理器时用来指定当前事务使用的事务管理器

@Transactional(value = "transactionManager")

transactionManager

““

系统中存在多个事务管理器时用来指定当前事务使用的事务管理器

@Transactional(transactionManager= "transactionManager")

propagation

Propagation.REQUIRED

事务传播行为

@Transactional(propagation = Propagation.REQUIRED)

isolation

Isolation.DEFAULT

事务隔离级别,默认采用物理数据库事务隔离级别

@Transactional(isolation = Isolation.REPEATABLE_READ)

timeout

-1事务超时时间,默认采用物理数据库的事务超时时间

@Transactional(timeout = 200)

readOnly

false事务可读性

@Transactional(readOnly = true)

rollbackFor

{}触发事务回滚异常类型,多个类型使用逗号分隔

@Transactional(rollbackFor = {SQLException.class,IllegalArgumentException.class})

rollbackForClassName

{}触发事务回滚异常类名,多个类名使用逗号分隔

@Transactional(rollbackForClassName={"BizException", "DaoException"})

noRollbackFor

{}触发事务不回滚异常类型,多个类型使用逗号分隔

@Transactional(noRollbackFor = {NullPointerException.class,FileNotFoundException.class})

noRollbackForClassName

{}触发事务不回滚异常类名,多个类名使用逗号分隔

@Transactional(noRollbackForClassName={"SSTException", "SSDException"})

采用注解方式声明事务必须要注意注解使用的范围,@Transactional可以被使用在接口,接口方法,类以及类方法。但是注解不能被继承,所以接口范围类使用该注解实现类依然不会运行在事务环境中。不能在类的private,static,protected方法上使用。

如果在类的范围上设置了@Transactional的属性,在方法的范围上也设置了@Transactional相同的属性,那么后者会覆盖前者设置的值

采用注解声明事务的方式需要开启注解扫描

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

该中方式对事务的管理分散在各个java文件,方便阅读,不方便统一管理

8 Spring中使用Connection

在实际过程中有时候需使用原始的JDBC方式访问数据库比如Spring整合第三方系统或者比较旧的系统,那么我们如何通过Spring获取数据源的Connection.配置JdbcTemplate bean.在需要使用的类中采用注解的方式注入。可以使用jdbcTemplate.getDataSource().getConnection()获取数据源的Connection。采用该中方式获取的Connection一定要及时关闭。否则会造成数据库连接泄漏

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

<!-- 事务传播特性配置 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="query*" read-only="true" />
    </tx:attributes>
</tx:advice>

<!-- 配置拦截service -->
<aop:config>
    <aop:pointcut id="method" expression="execution(* com.mark.blog.*impl.*.*(..))"></aop:pointcut>
    <aop:advisor advice-ref="transactionAdvice" pointcut-ref="method"/>
</aop:config>

Spring提供了DataSourceUtils用来回去数据源的Connnection,同时提供了释放Connection的方法。所以我们可以直接使用DataSourceUtils。及时采用该中方式获取Connection也需要及时的关闭连接

在使用Spring中提供的JdbcTemplate操作数据时,并没有显式的获得连接以及释放连接的操作,它是如何做实现连接获取以及关闭的操作的。JdbcTemplate提供的方法比较多。分析如下的方法可以看出:JdbcTemplate采用DataSourceUtils获取数据源连接,完成数据源库操作后关闭了Connection。其实就是对JDBC的封装。

public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");
    Connection con = DataSourceUtils.getConnection(this.getDataSource());
    Statement stmt = null;

    Object var7;
    try {
        Connection conToUse = con;
        if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
            conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
        }

        stmt = conToUse.createStatement();
        this.applyStatementSettings(stmt);
        Statement stmtToUse = stmt;
        if (this.nativeJdbcExtractor != null) {
            stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
        }

        T result = action.doInStatement(stmtToUse);
        this.handleWarnings(stmt);
        var7 = result;
    } catch (SQLException var11) {
        JdbcUtils.closeStatement(stmt);
        stmt = null;
        DataSourceUtils.releaseConnection(con, this.getDataSource());
        con = null;
        throw this.getExceptionTranslator().translate("StatementCallback", getSql(action), var11);
    } finally {
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, this.getDataSource());
    }

    return var7;
}

9 TransactionAwareDataSourceProxy的使用

通过方法名称就可以知道,代理数据源。同样的我们也可以使用代理数据源获得对应的Connection

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSourceProxy" />
</bean>

<bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
    <property name="targetDataSource" ref="dataSource"></property>
</bean>

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

<!-- 事务传播特性配置 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="query*" read-only="true" />
    </tx:attributes>
</tx:advice>

<!-- 配置拦截service -->
<aop:config>
    <aop:pointcut id="method" expression="execution(* com.mark.blog.*impl.*.*(..))"></aop:pointcut>
    <aop:advisor advice-ref="transactionAdvice" pointcut-ref="method"/>
</aop:config>



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值