spring---transaction(5)---事务的体系
1.写在前面
事务的模型为3中:
本地事务模式。
编程事务模式。
声明事务模式。
例子1:本地事务模式
Connection conn=jdbcDao.getConnection();
PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)");
ps.setString(1,user.getName());
ps.setInt(2,user.getAge());
ps.execute();
案例2:编程事务模式
Connection conn=jdbcDao.getConnection();
conn.setAutoCommit(false);
try {
PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)");
ps.setString(1,user.getName());
ps.setInt(2,user.getAge());
ps.execute();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
}finally{
conn.close();
}
InitialContext ctx = new InitialContext();
UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction");
try {
txn.begin();
//业务代码
txn.commit();
} catch (Exception up) {
txn.rollback();
throw up;
}
案例3:声明事务模式
@Transactional
public void save(User user){
jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge());
}
我认为他们各自的特点在于:谁在管理着事务的提交和回滚等操作?
这里有三个角色:数据库、开发人员、spring(等第三方)
- 对于案例1:开发人员不用知道事务的存在,事务全部交给数据库来管理,数据库自己决定什么时候提交或回滚,所以数据库是事务的管理者
- 对于案例2、3:事务的提交和回滚操作完全交给开发人员,开发人员来决定事务什么时候提交或回滚,所以开发人员是事务的管理者
- 对于案例4:开发人员完全不用关心事务,事务的提交和回滚操作全部交给Spring来管理,所以Spring是事务的管理者
2.编程式事务
编程式事务:即通过手动编程方式来实现事务操作,大部分情况,都是类似于上述案例2情况,开发人员来管理事务的提交和回滚,但也可能是Spring自己来管理事务,如Spring的TransactionTemplate。
Spring的TransactionTemplate 封装了对于数据库的操作(使用jdbc操作事务,编程非常麻烦,老是需要写一套模板式的try catch代码)
TransactionTemplate template=new TransactionTemplate();
template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
template.setTransactionManager(transactionManager);
template.execute(new TransactionCallback<User>() {
@Override
public User doInTransaction(TransactionStatus status) {
//可以使用DataSourceUtils获取Connection来执行sql
//jdbcTemplate.update(sql2); //可以使用SessionFactory的getCurrentSession获取Session来执行
//hibernateTemplate.save(user1) //可以使用myBatis的sqlSessionTemplate
//simpleTempalte.insert(Statement.getStatement(TempOrderMapper.class, MapperMethod.INSERT), table);
return null;
}
});
如果使用的是DataSourceTransactionManager,你就可以使用jdbc对应的JdbcTemplate或者myBatis对应的simpleTempalte来执行业务逻辑;或者直接使用Connection,但是必须使用DataSourceUtils来获取Connection
如果使用的是HibernateTransactionManager,就可以使用HibernateTemplate来执行业务逻辑,或者则可以使用SessionFactory的getCurrentSession方法来获取当前线程绑定的Session
- TransactionTemplate继承了DefaultTransactionDefinition,有了默认的事务定义,也可以自定义设置隔离级别、传播属性等
- TransactionTemplate需要一个PlatformTransactionManager事务管理器,来执行事务的操作
- TransactionTemplate在TransactionCallback中执行业务代码,try catch的事务模板代码,则被封装起来,包裹在业务代码的周围,详细见TransactionTemplate的execute方法,如下:
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
//由于TransactionTemplate继承了DefaultTransactionDefinition,所以使用PlatformTransactionManager事务管理器来根据TransactionTemplate来获取事务
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
//在TransactionCallback中的doInTransaction中执行相应的业务代码。回调
result = action.doInTransaction(status);
}
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
//如果业务代码出现异常,则回滚事务,没有异常则提交事务,回滚与提交都是通过PlatformTransactionManager事务管理器来进行的
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Exception ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
//由transactionManager关于事务的提交
this.transactionManager.commit(status);
return result;
}
}
事务代码和业务代码可以实现分离的原理
我们可以看到,使用TransactionTemplate,其实就做到了事务代码和业务代码的分离,分离之后的代价就是,必须保证他们使用的是同一类型事务。之后的声明式事务实现分离也是同样的原理,这里就提前说明一下。
1 如果使用DataSourceTransactionManager
- 1.1 事务代码是通过和当前线程绑定的ConnectionHolder中的Connection的commit和rollback来执行相应的事务,所以我们必须要保证业务代码也是使用相同的Connection,这样才能正常回滚与提交。
- 1.2 业务代码使用jdbcTemplate.update(sql)来执行业务,这个方法是使用的Connection从哪来的?是和上面的事务Connection是同一个吗?源码如下(jdbcTemplate在执行sql时,会使用DataSourceUtils从dataSource中获取一个Connection):
JdbcTemplate.java(jdbcTemplate在执行sql时,会使用DataSourceUtils从dataSource中获取一个Connection)
@Override
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//使用DataSourceUtils从dataSource中获取一个Connection
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//从当前线程中(TransactionSynchronizationManager管理器)中获取connection
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
}
也是先获取和当前线程绑定的ConnectionHolder(由于事务在执行业务逻辑前已经开启,已经有了和当前线程绑定的ConnectionHolder),所以会获取到和事务中使用的ConnectionHolder,这样就保证了他们使用的是同一个Connection了,自然就可以正常提交和回滚了。
如果想使用Connection,则需要使用DataSourceUtils从dataSorce中获取Connection,不能直接从dataSource中获取Connection。
- 1.3 业务代码使用myBatis管理的simpleTempalte.insert(Statement.getStatement(TempOrderMapper.class, MapperMethod.INSERT), table);来执行业务,所以我们必须要保证业务代码也是使用相同的sqlSession?源码如下:(详细见myBaits源代码系列文章)
由于myBatis的实际执行tempalte是simpleTempalte的代理对象,可以看到在SqlSessionInterceptor的invoke方法中是从SqlSessionUtils中获取sqlSession和
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取sqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
//也是从当前线程中(TransactionSynchronizationManager管理器)中获取SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
//如果没有获取到则, 创建已经绑定到TransactionSynchronizationManager
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session;
}
2 如果使用HibernateTransactionManager
- 2.1 事务代码是通过和当前线程绑定的SessionHolder中的Session中的Transaction的commit和rollback来执行相应的事务,详见上一篇文章说明事务管理器的事务分析,所以我们必须要保证业务代码也是使用相同的session
- 2.2业务代码就不能使用jdbcTemplate来执行相应的业务逻辑了,需要使用Session来执行相应的操作,换成对应的HibernateTemplate来执行。
HibernateTemplate在执行save(user)的过程中,会获取一个Session,方式如下:
session = getSessionFactory().getCurrentSession();
Hibernate定义了这样的一个接口:CurrentSessionContext,内容如下:
public interface CurrentSessionContext extends Serializable {
public Session currentSession() throws HibernateException;
}
上述SessionFactory获取当前Session就是依靠CurrentSessionContext的实现
在spring环境下,默认采用的是SpringSessionContext,它获取当前Session的方式如下:
也是先获取和当前线程绑定的SessionHolder(由于事务在执行业务逻辑前已经开启,已经有了和当前线程绑定的SessionHolder),所以会获取到和事务中使用的SessionHolder,这样就保证了他们使用的是同一个Session了,自然就可以正常提交和回滚了。
如果不想通过使用HibernateTemplate,想直接通过Session来操作,同理则需要使用SessionFactory的getCurrentSession方法来获取Session,而不能使用SessionFactory的openSession方法。
3.Spring的声明式事务
Spring可以有三种形式来配置事务拦截,不同配置形式仅仅是外在形式不同,里面的拦截原理都是一样的,所以先通过一个小例子了解利用AOP实现事务拦截的原理
利用AOP实现声明式事务的原理(简单的AOP事务例子)
@Repository
public class AopUserDao implements InitializingBean{ @Autowired
private UserDao userDao; private UserDao proxyUserDao; @Resource(name="transactionManager")
private PlatformTransactionManager transactionManager; @Override
public void afterPropertiesSet() throws Exception {
//使用代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
//设置代理的目标对象
proxyFactory.setTarget(userDao);
//引入spring的事务拦截器(详细见spring事务拦截器)
TransactionInterceptor transactionInterceptor=new TransactionInterceptor();
//设置事务管理器(详细见spring事务拦截器)
transactionInterceptor.setTransactionManager(transactionManager);
Properties properties=new Properties();
properties.setProperty("*","PROPAGATION_REQUIRED");
//设置事务的属性(详细见TransactionDefinition)
transactionInterceptor.setTransactionAttributes(properties);
//对代理对象加入拦截器
proxyFactory.addAdvice(transactionInterceptor);
proxyUserDao=(UserDao) proxyFactory.getProxy();
} public void save(User user){
proxyUserDao.save(user);
}
}
代码分析如下:
- 首先需要一个原始的UserDao,我们需要对它进行AOP代理,产生代理对象proxyUserDao,之后保存的功能就是使用proxyUserDao来执行
- 对UserDao具体的代理过程如下:
- 使用代理工厂,设置要代理的对象 proxyFactory.setTarget(userDao);
- 对代理对象加入拦截器
- 分成2种情况,一种默认拦截原UserDao的所有方法,一种是指定Pointcut,即拦截原UserDao的某些方法。
- 这里使用proxyFactory.addAdvice(transactionInterceptor);就表示默认拦截原UserDao的所有方法。
- 如果使用proxyFactory.addAdvisor(advisor),这里的Advisor可以简单看成是Pointcut和Advice的组合,Pointcut则是用于指定是否拦截某些方法。
设置好代理工厂要代理的对象和拦截器后,便可以创建代理对象。详细见spring的事务拦截器(TransactionInterceptor)
- proxyUserDao=(UserDao) proxyFactory.getProxy()
- 之后,我们在使用创建出的proxyUserDao时,就会首先进入拦截器,执行相关拦截器代码,因此我们可以在这里实现事务的处理
事务拦截器的原理分析
事务拦截器需要2个参数:事务配置的提供者、事务管理器PlatformTransactionManager
事务配置的提供者
用于指定哪些方法具有什么样的事务配置
可以通过属性配置方式,或者通过其他一些配置方式,如下三种方式都是为了获取事务配置提供者:
- 方式1:
<property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property
- 方式2:
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
</tx:attributes>
- 方式3:
@Transactional(propagation=Propagation.REQUIRED)
事务管理器PlatformTransactionManager
有了事务的配置,我们就可以通过事务管理器来获取事务了。
在执行代理proxyUserDao的save(user)方法时,会先进入事务拦截器中,具体的拦截代码如下:(很早之前有过分析这段代码,spring事务拦截器)
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable { // 第一步:首先获取所执行方法的对应的事务配置
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
//第二步:然后获取指定的事务管理器PlatformTransactionManager
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 第三步:根据事务配置,使用事务管理器创建出事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// 第四步:继续执行下一个拦截器,最终会执行到代理的原始对象的方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 第五步:一旦执行过程发生异常,使用事务拦截器进行事务的回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//第六步:如果没有异常,则使用事务拦截器提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
总结:
- 第一步:首先获取所执行方法的对应的事务配置
- 第二步:然后获取指定的事务管理器PlatformTransactionManager
- 第三步:根据事务配置,使用事务管理器创建出事务
- 第四步:继续执行下一个拦截器,最终会执行到代理的原始对象的方法
- 第五步:一旦执行过程发生异常,使用事务拦截器进行事务的回滚
- 第六步:如果没有异常,则使用事务拦截器提交事务
spring---transaction(5)---事务的体系的更多相关文章
- spring Transaction Propagation 事务传播
spring Transaction中有一个很重要的属性:Propagation.主要用来配置当前需要执行的方法,与当前是否有transaction之间的关系. 我晓得有点儿抽象,这也是为什么我想要写 ...
- Transaction Managament(事务管理二、Spring事务)
Transaction Managament(事务管理二.Spring事务) Spring事务框架的优势 Spring事务框架将开放过程中事务管理相关的关注点进行了分离,对这些关注点进行了抽象分离 ...
- Spring transaction事务之roll back回滚
转载自:http://blog.csdn.net/lovejavaydj/article/details/7635848 试验方法: 写一个单元测试,调用一个service层方法(发生对数据库进行写操 ...
- FlushMode属性与transaction(spring注入的事务)
一.参见hibernate的api http://tool.oschina.net/apidocs/apidoc?api=hibernate-3.6.10 http://tool.oschina.ne ...
- [spring transaction],service实现类中非事务方法直接调用自身事务方法导致事务无效的原因
首先,准备service接口,两个 public interface AccountService { public void createAccount(Account account, int t ...
- Spring @Transaction 注解是如何执行事务的?
前言 相信小伙伴一定用过 @Transaction 注解,那 @Transaction 背后的秘密又知道多少呢? Spring 是如何开启事务的?又是如何进行提交事务和关闭事务的呢? 画图猜测 在开始 ...
- Spring transaction事务 roll back各种回滚
Spring的AOP事务管理默认是针对unchecked exception回滚. 也就是默认对RuntimeException()异常极其子类进行事务回滚. Exception作为基类,下面还分ch ...
- spring声明式事务管理总结
事务配置 首先在/WEB-INF/applicationContext.xml添加以下内容: <!-- 配置事务管理器 --> <bean id="transactionM ...
- Spring学习8-Spring事务管理(编程式事务管理)
一.Spring事务的相关知识 1.事务是指一系列独立的操作,但在概念上具有原子性. 比如转账:A账号-100, B账号+100,完成.这两个操作独立是没问题的. 但在逻辑上,要么全部完成,要么一 ...
- Spring声明式事务配置管理方法
环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add ...
随机推荐
- cout如何输出十六进制
http://blog.csdn.net/okadler0518/article/details/4962340 cout<<hex<<i<<endl; //输出十 ...
- 十五、springboot集成定时任务(Scheduling Tasks)(二)之(线程配置)
配置类: /** * 定时任务线程配置 * */ @Configuration public class SchedulerConfig implements SchedulingConfigurer ...
- python基础-各模块文章导航
python基础学习日志day5-各模块文章导航 python基础学习日志day5---模块使用 http://www.cnblogs.com/lixiang1013/p/6832475.html p ...
- gbdt和xgboost api
class xgboost.XGBRegressor(max_depth=3, learning_rate=0.1, n_estimators=100, silent=True, objective= ...
- docker容器配置独立ip
一般安装docker后都会通过端口转发的方式使用网络,比如 “-p 2294:22” 就将2294抓发到22端口来提供sftp服务,这样使用起来没有问题.但端口号很难记忆,如果前边有nginx等抓发工 ...
- 解决ssh登陆过慢问题
我们经常会遇到的一个情况是telnet到server速度很快,但是ssh连接的时候却很慢,大概要等半分钟甚至更久.ping的速度也非常好,让人误以为是ssh连接不上. 下面说下如何解决这样的问题,最为 ...
- Python全栈开发之15、DOM
文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...
- Python全栈开发之12、html
从今天开始,本系列的文章会开始讲前端,从htnl,css,js等,关于python基础的知识可以看我前面的博文,至于python web框架的知识会在前端学习完后开始更新. 一.html相关概念 ht ...
- HDU 3530 单调队列
题目大意:给你n个数, 让你问你最长的满足要求的区间有多长,区间要求:MAX - MIN >= m && MAX - MIN <= k 思路:单调队列维护递增和递减,在加入 ...
- gitlab-针对API,获取私有令牌
Gitlab有一个强大的API系统,几乎所有的功能都可以在web中执行,当然也可以通过API来执行,为了使用API,需要从Gitlab中获取私有token. 执行步骤: 1. 登陆Gitlab服务器 ...