1、说到数据库事务,人们脑海里自然不自然的就会浮现出事务的四大特性、四大隔离级别、七大传播特性。四大还好说,问题是七大传播特性是哪儿来的?是spring在当前线程内,处理多个数据库操作方法事务时所做的一种事务应用策略。事务本身并不存在什么传播特性,不要混淆事务本身和spring的事务应用策略。(当然,找工作面试时,还是可以巧妙的描述传播特性的)
2、一说到事务,人们可能又会想起create、begin、commit、rollback、close、suspend。可实际上,只有commit、rollback是实际存在的,剩下的create、begin、close、suspend都是虚幻的,是业务层或数据库底层应用语意,而非jdbc事务的真实命令。
create(事务创建):不存在。
begin(事务开始):姑且认为存在于db的命令行中,比如mysql的start transaction命令,以及其他数据库中的begin transaction命令。jdbc中不存在。
close(事务关闭):不存在。应用程序接口中的close()方法,是为了把connection放回数据库连接池中,供下一次使用,与事务毫无关系。
suspend(事务挂起):不存在。spring中事务挂起的含义是,需要新事务时,将现有的connection1保存起来(它还有尚未提交的事务),然后创建connection2,connection2提交、回滚、关闭完毕后,再把connection1取出来,完成提交、回滚、关闭等动作,保存connection1的动作称之为事务挂起。在jdbc中,是根本不存在事务挂起的说法的,也不存在这样的接口方法。
因此,记住事务的三个真实存在的方法,不要被各种事务状态名词所迷惑,它们分别是:conn.setautocommit()、conn.commit()、conn.rollback()。
conn.close()含义为关闭一个数据库连接,这已经不再是事务方法了。
mybaits中的事务接口transaction
public interface transaction {
connection getconnection() throws sqlexception;
void commit() throws sqlexception;
void rollback() throws sqlexception;
void close() throws sqlexception;
}
有了文章开头的分析,当你再次看到close()方法时,千万别再认为是关闭一个事务了,而是关闭一个conn连接,或者是把conn连接放回连接池内。
事务类层次结构图:
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实例,那么代码也是没有问题的,双重支持。
事务工厂transactionfactory
顾名思义,一个生产jdbctransaction实例,一个生产managedtransaction实例。两个毫无实际意义的工厂类,除了new之外,没有其他代码。
<transactionmanager type="jdbc" />
mybatis-config.xml配置文件内,可配置事务管理类型。
transaction的用法
无论是sqlsession,还是executor,它们的事务方法,最终都指向了transaction的事务方法,即都是由transaction来完成事务提交、回滚的。
配一个简单的时序图。
代码样例:
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()等方法内部寻找有关事务的任何方法。
你可能关心的有关事务的几种特殊场景表现(重要)
1、一个conn生命周期内,可以存在无数多个事务。
// 执行了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(),只能回滚当前未提交的事务。
2、autocommit=false,没有执行commit(),仅执行close(),会发生什么?
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()关闭连接,当然数据最终也就没能持久化到数据库中了。
3、autocommit=false,没有commit,也没有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。