事務的模型爲3中:html
本地事務模式。java
編程事務模式。spring
聲明事務模式。sql
例子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:聲明事務模式session
@Transactional public void save(User user){ jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge()); }
我認爲他們各自的特色在於:誰在管理着事務的提交和回滾等操做?app
這裏有三個角色:數據庫、開發人員、spring(等第三方)ide
編程式事務:即經過手動編程方式來實現事務操做,大部分狀況,都是相似於上述案例2狀況,開發人員來管理事務的提交和回滾,但也多是Spring本身來管理事務,如Spring的TransactionTemplate。ui
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
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
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。
因爲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
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方法。
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); } }
代碼分析以下:
事務攔截器須要2個參數:事務配置的提供者、事務管理器PlatformTransactionManager
事務配置的提供者
用於指定哪些方法具備什麼樣的事務配置
能夠經過屬性配置方式,或者經過其餘一些配置方式,以下三種方式都是爲了獲取事務配置提供者:
<property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property
<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>
@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; } }
總結: