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* |
propagation | 否 | REQUEST | 事务传播行为 |
isolation | 否 | DEFAULT | 事务隔离级 别默认采用底层数据库的事务隔离级别 |
timeout | 否 | -1 | 事务超时时间 默认情况下超时时间采用 底层数据库的超时时间 |
read-only | 否 | false | 事务只读性设置 |
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; } } }
属性名 | 默认值 | 说明 | 举例 |
---|---|---|---|
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>