1.说到数据库事务,人们脑海里天然不天然的就会浮现出事务的四大特性、四大隔离级别、七大传播特性。四大还好说,问题是七大传播特性是哪儿来的?是Spring在当前线程内,处理多个数据库操做方法事务时所作的一种事务应用策略。事务自己并不存在什么传播特性,不要混淆事务自己和Spring的事务应用策略。(固然,找工做面试时,仍是能够巧妙的描述传播特性的)
java
2.一说到事务,人们可能又会想起create、begin、commit、rollback、close、suspend。可实际上,只有commit、rollback是实际存在的,剩下的create、begin、close、suspend都是虚幻的,是业务层或数据库底层应用语意,而非JDBC事务的真实命令。mysql
create(事务建立):不存在。程序员
begin(事务开始):姑且认为存在于DB的命令行中,好比Mysql的start transaction命令,以及其余数据库中的begin transaction命令。JDBC中不存在。面试
close(事务关闭):不存在。应用程序接口中的close()方法,是为了把connection放回数据库链接池中,供下一次使用,与事务毫无关系。sql
suspend(事务挂起):不存在。Spring中事务挂起的含义是,须要新事务时,将现有的connection1保存起来(它还有还没有提交的事务),而后建立connection2,connection2提交、回滚、关闭完毕后,再把connection1取出来,完成提交、回滚、关闭等动做,保存connection1的动做称之为事务挂起。在JDBC中,是根本不存在事务挂起的说法的,也不存在这样的接口方法。数据库
所以,记住事务的三个真实存在的方法,不要被各类事务状态名词所迷惑,它们分别是:conn.setAutoCommit()、conn.commit()、conn.rollback()。apache
conn.close()含义为关闭一个数据库链接,这已经再也不是事务方法了。编程
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
有了文章开头的分析,当你再次看到close()方法时,千万别再认为是关闭一个事务了,而是关闭一个conn链接,或者是把conn链接放回链接池内。
网络
事务类层次结构图:
session
(Made In Intellij Idea IDE)
JdbcTransaction:单独使用Mybatis时,默认的事务管理实现类,就和它的名字同样,它就是咱们常说的JDBC事务的极简封装,和编程使用mysql-connector-java-5.1.38-bin.jar事务驱动没啥差异。其极简封装,仅是让connection支持链接池而已。
ManagedTransaction:含义为托管事务,空壳事务管理器,皮包公司。仅是提醒用户,在其它环境中应用时,把事务托管给其它框架,好比托管给Spring,让Spring去管理事务。
org.apache.ibatis.transaction.jdbc.JdbcTransaction.java部分源码。
@Override public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } }
面对上面这段代码,咱们不由好奇,connection.close()以前,竟然调用了一个resetAutoCommit(),含义为重置autoCommit属性值。connection.close()含义为销毁conn,既然要销毁conn,为什么还画蛇添足的调用一个resetAutoCommit()呢?消失以前多喝口水,真的没有必要。
其实,缘由是这样的,connection.close()不意味着真的要销毁conn,而是要把conn放回链接池,供下一次使用,既然还要使用,天然就须要重置AutoCommit属性了。经过生成connection代理类,来实现重回链接池的功能。若是connection是普通的Connection实例,那么代码也是没有问题的,双重支持。
顾名思义,一个生产JdbcTransaction实例,一个生产ManagedTransaction实例。两个毫无实际意义的工厂类,除了new以外,没有其余代码。
<transactionManager type="JDBC" />
mybatis-config.xml配置文件内,可配置事务管理类型。
不管是SqlSession,仍是Executor,它们的事务方法,最终都指向了Transaction的事务方法,即都是由Transaction来完成事务提交、回滚的。
配一个简单的时序图。
(Made In Visual Paradigm)
代码样例:
public static void main(String[] args) { SqlSession sqlSession = MybatisSqlSessionFactory.openSession(); try { StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = new Student(); student.setName("yy"); student.setEmail("email@email.com"); student.setDob(new Date()); student.setPhone(new PhoneNumber("123-2568-8947")); studentMapper.insertStudent(student); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); } finally { sqlSession.close(); } }
注:Executor在执行insertStudent(student)方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),须要像上面的代码同样,手动显示调用commit()、rollback()、close()等方法。
所以,后续在分析到相似insert()、update()等方法内部时,须要忘记事务的存在,不要试图在insert()等方法内部寻找有关事务的任何方法。
// 执行了connection.setAutoCommit(false),并返回 SqlSession sqlSession = MybatisSqlSessionFactory.openSession(); try { StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = new Student(); student.setName("yy"); student.setEmail("email@email.com"); student.setDob(new Date()); student.setPhone(new PhoneNumber("123-2568-8947")); studentMapper.insertStudent(student); // 提交 sqlSession.commit(); studentMapper.insertStudent(student); // 屡次提交 sqlSession.commit(); } catch (Exception e) { // 回滚,只能回滚当前未提交的事务 sqlSession.rollback(); } finally { sqlSession.close(); }
对于JDBC来讲,autoCommit=false时,是自动开启事务的,执行commit()后,该事务结束。以上代码正常状况下,开启了2个事务,向数据库插入了2条数据。JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了几回,数据库就会有几条记录,切勿混淆。而rollback(),只能回滚当前未提交的事务。
try { studentMapper.insertStudent(student); } finally { sqlSession.close(); }
就像上面这样的代码,没有commit(),执拗的程序员老是好奇这样的特例。
insert后,close以前,若是数据库的事务隔离级别是read uncommitted,那么,咱们能够在数据库中查询到该条记录。
接着执行sqlSession.close()时,通过SqlSession的判断,决定执行rollback()操做,因而,事务回滚,数据库记录消失。
下面,咱们看看org.apache.ibatis.session.defaults.DefaultSqlSession.java中的close()方法源码。
@Override public void close() { try { executor.close(isCommitOrRollbackRequired(false)); dirty = false; } finally { ErrorContext.instance().reset(); } }
事务是否回滚,依靠isCommitOrRollbackRequired(false)方法来判断。
private boolean isCommitOrRollbackRequired(boolean force) { return (!autoCommit && dirty) || force; }
在上面的条件判断中,!autoCommit=true(取反固然是true了),force=false,最终是否回滚事务,只有dirty参数了,dirty含义为是不是脏数据。
@Override public int insert(String statement, Object parameter) { return update(statement, parameter); } @Override public 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(); } }
源码很明确,只要执行update操做,就设置dirty=true。insert、delete最终也是执行update操做。
只有在执行完commit()、rollback()、close()等方法后,才会再次设置dirty=false。
@Override public 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(); } }
所以,得出结论:autoCommit=false,可是没有手动commit,在sqlSession.close()时,Mybatis会将事务进行rollback()操做,而后才执行conn.close()关闭链接,固然数据最终也就没能持久化到数据库中了。
studentMapper.insertStudent(student);
干脆,就这一句话,即不commit,也不close。
结论:insert后,jvm结束前,若是事务隔离级别是read uncommitted,咱们能够查到该条记录。jvm结束后,事务被rollback(),记录消失。经过断点debug方式,你能够看到效果。
这说明JDBC驱动实现,已经Kao虑到这样的特例状况,底层已经有相应的处理机制了。这也超出了咱们的探究范围。
可是,一万个屌丝程序员会对你说:Don't do it like this. Go right way。
警告:请按正确的try-catch-finally编程方式处理事务,若不从,本人概不负责后果。
注:无参的openSession()方法,会自动设置autoCommit=false。
总结:Mybatis的JdbcTransaction,和纯粹的Jdbc事务,几乎没有差异,它仅是扩展支持了链接池的connection。另外,须要明确,不管你是否手动处理了事务,只要是对数据库进行任何update操做(update、delete、insert),都必定是在事务中进行的,这是数据库的设计规范之一。读完本篇文章,是否颠覆了你心中目前对事务的理解呢?欢迎点评。
版权提示:文章出自开源中国社区,若对文章感兴趣,可关注个人开源中国社区博客(http://my.oschina.net/zudajun)。(通过网络爬虫或转载的文章,常常丢失流程图、时序图,格式错乱等,仍是看原版的比较好)