深入理解MyBatis(七)—MyBatis事务
来源:互联网 发布:php log打印到日志 编辑:程序博客网 时间:2024/06/09 23:20
深入理解MyBatis(七)—MyBatis事务
MyBatis可以通过XML配置文件设定是否进行事务管理, 事务管理主要包括事务的提交,回滚等;
本文主要介绍了事务的入口,MyBatis事务操作对数据库SELECT操作和UPDATE操作的影响等;
个人主页:tuzhenyu’s page
原文地址:深入理解MyBatis(七)—MyBatis事务
(0) MyBatis事务遇到的问题
- 如果开启MyBatis事务管理,则需要手动进行事务提交,否则事务会回滚到原状态;
String resource = "mybatis/config.xml";InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);SqlSession session = sessionFactory.openSession();User user = new User();user.setName("liuliu");user.setPassword("123123");user.setScore("88");String statement = "mybatis.mapping.UserMapper.insertUser";session.insert(statement,user);session.close();
如果在具体操作执行完后不通过sqlSession.commit()方法提交事务,事务在sqlSession关闭时会自动回滚到原状态;只有执行了commit()事务提交方法才会真正完成操作;
如果不执行sqlSession.commit()操作,直接执行sqlSession.close(),则会在close()中进行事务回滚;
如果不执行sqlSession.commit()操作也不手动关闭sqlSession,在程序结束时关闭数据库连接时会进行事务回滚;
String resource = "mybatis/config.xml";InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);SqlSession session = sessionFactory.openSession();User user = new User();user.setName("liuliu");user.setPassword("123123");user.setScore("88");String statement = "mybatis.mapping.UserMapper.insertUser";session.insert(statement,user);session.commit();session.close();
(1) 事务管理入口
在XML配置文件中定义事务工厂类型,JDBC或者MANAGED分别对应JdbcTransactionFactory.class和ManagedTransactionFactory.class;
如果type=”JDBC”则使用JdbcTransactionFactory事务工厂则MyBatis独立管理事务,直接使用JDK提供的JDBC来管理事务的各个环节:提交、回滚、关闭等操作;
如果type=”MANAGED”则使用ManagedTransactionFactory事务工厂则MyBatis不在ORM层管理事务而是将事务管理托付给其他框架,如Spring;
<environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> //相当于<transactionManager type="JdbcTransactionFactory.class" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="BIUBIU" /> </dataSource> </environment></environments>
- 在从SessionFactory中获取sqlSession时候会根据environment配置获取相应的事务工厂TransactionFactory,并从中获取事务实例当做参数传递给Executor,用来从中获取Connection数据库连接;
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}
- getTransactionFactoryFromEnvironment()方法根据XML配置获取JdbcTransactionFactory或者ManagedTransactionFactory;ManagedTransactionFactory类中的commit()方法和rollback()方法都为空,事务相关操作不发挥作用;
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}
(2) 事务对UPDATE操作的影响
事务的提交
- 在sqlSession中执行了UPDATE操作,需要执行sqlSession.commit()方法提交事务,不然在连接关闭时候会自动回滚;
@Overridepublic void commit() { commit(false);}@Overridepublic void commit(boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}
- exector.commit()事务提交方法归根到底是调用了transaction.commit()事务的提交方法;这里的transaction就是根据配置对应的JdbcTransaction或者ManagedTransaction;
public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); }}
- 如果是JdbcTransaction的commit()方法,通过调用connection.commit()方法通过数据库连接实现事务提交;
public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); }}
- 如果是ManagedTransaction的commit()方法,则为空方法不进行任何操作;
@Overridepublic void commit() throws SQLException { // Does nothing}@Overridepublic void rollback() throws SQLException { // Does nothing}
事务的回滚
sqlSession执行close()关闭操作时,如果close()操作之前进行了UPDATE操作未进行commit()事务提交则会进行事务回滚然后再关闭会话;如果update后执行了commit则直接关闭会话;
- 在DefaultSqlSession类中如果执行了UPDATE操作则会将标志位dirty赋值为true
@Overridepublic int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}
- 在事务提交时会将dirty赋值为false;
@Overridepublic void commit(boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}
- 在关闭会话时会判断dirty是否为true,如果为true则需要进行事务回滚操作,否则直接关闭会话
@Overridepublic void close() { try { executor.close(isCommitOrRollbackRequired(false)); closeCursors(); dirty = false; } finally { ErrorContext.instance().reset(); }}
- 判断dirty标志是否为true
private boolean isCommitOrRollbackRequired(boolean force) { return (!autoCommit && dirty) || force;}
- 如果dirty为true则判定forceRollback为true,执行回滚操作;
@Overridepublic void close(boolean forceRollback) { try { try { rollback(forceRollback); } finally { if (transaction != null) { transaction.close(); } } } catch (SQLException e) { // Ignore. There's nothing that can be done at this point. log.warn("Unexpected exception on closing transaction. Cause: " + e); } finally { transaction = null; deferredLoads = null; localCache = null; localOutputParameterCache = null; closed = true; }}
(3) 事务对SELECT的影响
事务对select操作的影响主要体现在对缓存的影响上,主要包括一级缓存和二级缓存
一级缓存
- 因为一级缓存是Session级别的,事务的提交回滚对MyBatis的一级缓存没有影响;一级缓存放在BaseExector中的PerpectualCache类型的localCache中;
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list;}
二级缓存
- 在Mapper.xml文件解析时会根据文件中的标签或者创建Cache实例,并将该实例放入每一个MappedStatement中,在MappedStatement执行select操作时候会获取该cache;
@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}
查询二级缓存
- 根据Statement所在的Mapper的cache缓存对象和根据statement生成的cacheKey,从正式缓存中取缓存数据
List<E> list = (List<E>) tcm.getObject(cache, key);
- 根据Statement所在的Mapper的cache缓存对象在TransactionManager中定位对应的TransactionCache,TransactionCache中保存这正式缓存delegate和临时未提交缓存entiryToAddOnCommit;
public Object getObject(Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key);}
- 获取缓存时直接从正式缓存delegate中查询
@Overridepublic Object getObject(Object key) { // issue #116 Object object = delegate.getObject(key); if (object == null) { entriesMissedInCache.add(key); } // issue #146 if (clearOnCommit) { return null; } else { return object; }}
存储二级缓存
如果从二级缓存中未命中缓存,则需要从数据库中查取,再将查询结果放入二级缓存中;查询结果首先放入二级缓存临时缓存中,只有执行了commit()事务提交才正式转移到正式缓存中;也就是说只有执行了commit()方法的缓存才被下次查询使用,不然仍会执行数据库查询任务并覆盖上次的临时缓存;
- 根据Statement所在的Mapper的cache缓存对象在TransactionManager中定位对应的TransactionCache
public void putObject(Cache cache, CacheKey key, Object value) { getTransactionalCache(cache).putObject(key, value);}
- 将数据放入TransactionCache中的临时缓存entiryToAddOnCommit中
public void putObject(Object key, Object object) { entriesToAddOnCommit.put(key, object);}
提交二级缓存
- 执行事务提交时候会调用TransactionCacheManager内的commit()方法提交缓存,每一个cachingExecutor对应一个TransactionCacheManager,也就是一个SqlSeesion对应一个TransactionCacheManager,因此sqlSession.commit()是提交了当前会话的所以二级缓存的临时缓存;每个sqlSession对每一个Mapper的cache都有一个临时缓存,多个sqlSession共享一个Mapper的cache的正式缓存;
public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); }}
public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset();}
- 将临时缓存entiryToAddOnCommit中的数据转移到正式缓存delegate中
private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } }}
回滚二级缓存
如果之前未执行UPDATE操作,dirty标志为false,在关闭会话之前没有进行事务提交则进行数据库回滚和缓存提交;
如果执行了UPDATE操作,dirty标志为true,在关闭会话之前没有进行事务提交则进行数据库回滚和缓存回滚;
可以调用session.close(true)关闭会话时强行回滚缓存;
@Overridepublic void close(boolean forceRollback) { try { if (forceRollback) { tcm.rollback(); } else { tcm.commit(); } } finally { delegate.close(forceRollback); }}
总结
MyBatis可以通过XML配置是否独立处理事务,可以选择不单独处理事务,将事务托管给其他上层框架如spring等;
如果MyBatis选择处理事务则事务会对数据库操作产生影响
对UPDATE操作的影响主要表现在UPDATE操作后如果没有进行事务提交则会子啊会话关闭时进行数据库回滚;
对SELECT操作的影响主要表现在二级缓存上,执行SELECT操作后如果未进行事务提交则缓存会被放在临时缓存中,后续的SELECT操作无法使用该缓存,直到进行commit()事务提交,临时缓存中的数据才会被转移到正式缓存中;
- 深入理解MyBatis(七)—MyBatis事务
- 深入理解MyBatis(一)—MyBatis基础
- 深入理解MyBatis(二)—MyBatis初始化
- 深入理解MyBatis(五)—MyBatis的插件机制
- 深入理解MyBatis(六)—MyBatis的缓存机制
- 深入理解MyBatis(八)—Spring和MyBatis集成
- Mybatis深入了解(七)----延迟加载
- Mybatis深入了解(七)----延迟加载
- 深入理解mybatis
- 《深入理解mybatis原理》
- 深入理解mybatis
- 深入理解Mybatis
- 深入理解mybatis原理
- 深入理解mybatis
- 《深入理解mybatis原理(七)》 MyBatis的架构设计以及实例分析
- 深入理解mybatis原理(七) MyBatis的架构设计以及实例分析
- 深入理解MyBatis框架——SqlSession
- 深入理解MyBatis(三)—MyBatis的Update操作执行流程
- vb 用BULK批量上传TXT数据到SQL服务器
- [Linux] MySQL 主从配置
- 关于Java集合最被关注的10 个问题
- Linux中grep命令的12个实践例子
- js 字符串转成json格式
- 深入理解MyBatis(七)—MyBatis事务
- objdump命令的使用[转载]
- 球面点三维坐标到纹理二维坐标的转换
- Linux查看物理CPU个数、核数、逻辑CPU个数
- sandbox
- Ubuntu下安装Opencv2.4.9 及实现python接口
- android打造酷炫自定义ProgressBar
- 【Java并发之】BlockingQueue
- json读取数据:ValueError: Extra data: line 77 column 2