Mybatis的运行过程分为两大步:

  • 第1步,读取配置文件缓存到Configuration对象,用于创建SqlSessionFactory;
  • 第2步,SqlSession的执行过程。相对而言,SqlSessionFactory的创建还算比较容易理解,而SqlSession的执行过程就不那么简单了,它包括许多复杂的技术,要先掌握反射技术和动态代理,这里主要用到的是JDK动态代理.

一个简单使用的例子

public class TestMybatis {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(2);
System.out.println("name:" + user.getName());
session.close();
SqlSession session1 = sqlSessionFactory.openSession();
List<User> users = session1.selectList("findAll");
session1.commit();
System.out.println("allSize:" + users.size());
session1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

MyBatis的主要构件及其相互关系

从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:

  • SqlSession              作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor                  MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler   封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler   负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler    负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler            负责java数据类型和jdbc数据类型之间的映射和转换
  • MappedStatement   MappedStatement维护了一条<select|update|delete|insert>节点的封装,
  • SqlSource                负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql                 表示动态生成的SQL语句以及相应的参数信息
  • Configuration            MyBatis所有的配置信息都维持在Configuration对象之中。

构建SqlSessionFactory过程

构建主要分为2步:

  • 通过XMLConfigBuilder解析配置的XML文件,读出配置参数,包括基础配置XML文件和映射器XML文件;
  • 使用Configuration对象创建SqlSessionFactory,SqlSessionFactory是一个接口,提供了一个默认的实现类DefaultSqlSessionFactory。

说白了,就是将我们的所有配置解析为Configuration对象,在整个生命周期内,可以通过该对象获取需要的配置。

SqlSession运行过程

我们与mybatis交互主要是通过配置文件或者配置对象,但是我们最终的目的是要操作数据库的,所以mybatis为我们提供了sqlSession这个对象来进行所有的操作,也就是说我们真正通过mybatis操作数据库只要对接sqlSession这个对象就可以了。那么问题来了,我们怎么样通过sqlSession来了操作数据库的呢?

问题1:如何获取sqlSession?

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }

由上面代码我们可知我们可以通过SqlSessionFactory的openSession去获取我们的sqlSession,只要我们实例化了一个SqlSessionFactory,就能得到一个DefaultSqlSession对象。

问题2:Mapper对象怎么来的?

  @Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

通过调用DefaultSqlSession的getMapper方法并且传入一个类型对象获取,底层呢调用的是配置对象configuration的getMapper方法,configuration对象是我们在加载DefaultSqlSessionFactory时传入的。
然后我们再来看下这个配置对象的getMapper,传入的是类型对象(补充一点这个类型对象就是我们平时写的DAO层接口,里面是一些数据库操作的接口方法。),和自身也就是DefaultSqlSession。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

我们看到这个configuration的getMapper方法里调用的是mapperRegistry的getMapper方法,参数依然是类型对象和sqlSession。这里呢,我们要先来看下这个MapperRegistry即所谓Mapper注册器是什么。

public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap(); public MapperRegistry(Configuration config) {
this.config = config;
}
....
}

从这里我们可以知道其实啊这个MapperRegistry就是保持了一个Configuration对象和一个HashMap,而这个HashMap的key是类型对象,value呢是MapperProxyFactory。我们这里先不管MapperProxyFactory是什么东西,我们现在只需要知道MapperRegistry是这么一个东西就可以了。这里有人会问MapperRegistry对象是怎么来的,这里呢是在初始化Configuration对象时初始化了这个MapperRegistry对象的,代码大家可以去看,为了避免混乱,保持贴出来的代码是一条线走下来的,这里就不贴出来了。接下来我们继续看下这个MapperRegistry的getMapper方法。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

这里我们可以看到从knownMappers中获取key为类型对象的MapperProxyFactory对象。然后调用MapperProxyFactory对象的newInstance方法返回,newInstance方法传入sqlSession对象。到这里我们可能看不出什么端倪,那我们就继续往下看这个newInstance方法做的什么事情吧。

  public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

这里我们可以看到MapperProxyFactory直接new了一个MapperProxy对象,然后调用另外一重载的newInstance方法传入MapperProxy对象。这里我们可以看出一些东西了,通过调用Proxy.newProxyInstance动态代理了我们的mapperProxy对象!这里的mapperInterface即我们的dao层(持久层)接口的类型对象。
所以总结下就是我们通过sqlSesssion.getMapper(clazz)得到的Mapper对象是一个mapperProxy的代理类!
所以也就引出下面的问题。

问题3:为什么我调用mapper对象方法就能发出sql操作数据库?

通过上面的讲解,我们知道了这个mapper对象其实是一个一个mapperProxy的代理类!所以呢这个mapperProxy必然实现了InvocationHandler接口。所以当我们调用我们的持久层接口的方法时必然就会调用到这个MapperProxy对象的invoke方法,所以接下来我们进入这个方法看看具体mybatis为我们做了什么。

  @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

从代码中我们可以看到前面做了一个判断,这个判断主要是防止我们调用像toString方法或者equals方法时也能正常调用。然后我们可以看到它调用cachedMapperMethod返回MapperMethod对象,接着就执行这个MapperMethod对象的execute方法。这个cachedMapperMethod方法主要是能缓存我们使用过的一些mapperMethod对象,方便下次使用。这个MapperMethod对象主要是获取方法对应的sql命令和执行相应SQL操作等的处理,具体细节同学们可以抽空研究。

继续跟踪MapperMethod.execute

  public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

我们可以清晰的看到这里针对数据库的增删改查做了对应的操作,这里我们可以看下查询操作。我们可以看到这里针对方法的不同返回值作了不同的处理,我们看下其中一种情况。

          Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);

这里我们可以看到它将方法参数类型转换成数据库层面上的参数类型,最后调用sqlSession对象的selectOne方法执行。所以我们看到最后还是回到sqlSession对象上来,也就是前面所说的sqlSession是mybatis提供的与数据库交互的唯一对象。

接下来我们看下这个selectOne方法做了什么事,这里我们看的是defaultSqlSession的selectOne方法。

  @Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

我们看到它调用selectList方法,通过去返回值的第一个值作为结果返回。那么我们来看下这个selectList方法。

  @Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

我们可以看到这里调用了executor的query方法,我们再进入到query里看看。这里我们看的是BaseExecutor的query方法

  @Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1.根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
// 2.为当前的查询创建一个缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
} @SuppressWarnings("unchecked")
@Override
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 {
// 3.缓存中没有值,直接从数据库中读取数据
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;
} private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//4. 执行查询,返回List 结果,然后 将查询的结果放入缓存之中
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

我们看到有个一个方法doQuery,进入方法看看做了什么。点进去后我们发现是抽象方法,我们选择simpleExecutor子类查看实现。

  @Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//5. 根据既有的参数,创建StatementHandler对象来执行查询操作
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//6. 创建java.Sql.Statement对象,传递给StatementHandler对象
stmt = prepareStatement(handler, ms.getStatementLog());
//7. 调用StatementHandler.query()方法,返回List结果集
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数
handler.parameterize(stmt);
return stmt;
}

我们可以看到通过configuration对象的newStatementHandler方法构建了一个StatementHandler,然后在调用prepareStatement方法中获取连接对象,通过StatementHandler得到Statement对象。另外我们注意到在获取了Statement对象后调用了parameterize方法。继续跟踪下去(自行跟踪哈)我们可以发现会调用到ParameterHandler对象的setParameters去处理我们的参数。所以这里的prepareStatement方法主要使用了StatementHandler和ParameterHandler对象帮助我们处理语句集和参数的处理。最后还调用了StatementHandler的query方法,我们继续跟踪下去。

这里我们进入到PreparedStatementHandler这个handler查看代码。这里我们进入到PreparedStatementHandler这个handler查看代码。

  @Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
//使用ResultHandler来处理ResultSet
return resultSetHandler.<E>handleResultSets(statement);
}

看到这里,我们终于找到了操作数据库的地方了,就是ps.execute()这句代码。底层我们可以发现就是我们平时写的JDBC!然后将这个执行后的PreparedStatement交给resultSetHandler处理结果集,最后返回我们需要的结果集。

mybatis解析和基本运行原理的更多相关文章

  1. 互联网轻量级框架SSM-查缺补漏第七天(MyBatis的解析和运行原理)

    第七章MyBatis的解析和运行原理 SqlSessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心借口SqlSession,所以要先创建SqlSess ...

  2. Mybatis的解析和运行原理

    Mybatis的解析和运行原理 Mybatis的运行过程大致分为两大步:第一步,读取配置文件缓存到Configuration对象,用以创建 SqlSessionFactory:第二步,SqlSessi ...

  3. mybatis运行原理

    mybatis运行原理 运行过程中涉及到的类或者接口 Resources(c) :用于加载mybatis核心配置文件 XMLConfigBuilder(c) :用于解析xml文件(核心配置文件) Co ...

  4. .NET/ASP.NET MVC Controller 控制器(深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

  5. 二、ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

  6. JavaScript运行原理解析

    原文:1.http://blog.csdn.net/liaodehong/article/details/50488098 2.Stack的三种含义 (阮一峰) 3. http://lib.csdn. ...

  7. 属性动画 ValueAnimator 运行原理全解析

    最近下班时间都用来健身还有看书了,博客被晾了一段时间了,原谅我~~~~ 提问环节 好,废话不多说,之前我们已经分析过 View 动画 Animation 运行原理解析,那么这次就来学习下属性动画的运行 ...

  8. (转)Apache和Nginx运行原理解析

    Apache和Nginx运行原理解析 原文:https://www.server110.com/nginx/201402/6543.html Web服务器 Web服务器也称为WWW(WORLD WID ...

  9. NET/ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

随机推荐

  1. 熟悉GitHub、VS工具的使用(《构建之法》第二次作业)

    GIT地址 https://github.com/slothph GIT用户名 slothph 学号后五位    62323 博客地址 https://www.cnblogs.com/slothph/ ...

  2. requests-html快速入门

    昨天写了requests库好!最近requests库的作者又发布了一个新库,叫做requests-html,看名字也能猜出来,这是一个解析HTML的库,而且用起来和requests一样爽,下面就来介绍 ...

  3. 使用history.js解决浏览器对history使用的兼容问题

    history.js即可以解决禁止浏览器回退的浏览器兼容问题,也能解决直接修改浏览器当前标签页url的浏览器兼容问题. 解决禁止浏览器回退: <script src="../../js ...

  4. 从入门到自闭之Python编码

    ascii码: 支持英文,数字,符号 1字节 不支持中文 gbk(国标) 支持英文,数字,符号 1字节 支持中文 2字节 unicode(万国码): 支持英文,数字,符号 4字节 支持欧洲 4字节 支 ...

  5. 从入门到自闭之Python整型,字符串以及for循环

    Day 01 整型: 对比: 在python 2 版本中有整型,长整型long 在python 3 版本中全部都是整型 用于计算和比较 整型和布尔值的转换 二进制转换成十进制: ​ print (in ...

  6. Linux-1.4文件操作命令(grep,cat,tail,head,less,find,chmod,tail,less)

    Linux基础命令(grep,cat,tail,head,less,find,chmod,tail,less) grep(常用) grep 指定“文件”搜索文件内容 grep hello 1.txt ...

  7. Max History CodeForces - 938E (组合计数)

    You are given an array a of length n. We define fa the following way: Initially fa = 0, M = 1; for e ...

  8. Css常用的技巧

    一.使用css缩写 使用缩写可以帮助减少你CSS文件的大小,更加容易阅读.  具体内容请浏览:CSS常用缩写语法 二.明确定义单位,除非值为0. 忘记定义尺寸的单位是CSS新手普遍的错误.在HTML中 ...

  9. [转载]C++STL—vector的插入与删除

    来源:https://blog.csdn.net/duan19920101/article/details/50717748 vector::erase():从指定容器删除指定位置的元素或某段范围内的 ...

  10. js的作用主要这么几个

    js的作用主要有这么几个表单验证:网页上,用户输入的信息需要进行验证,在客户端验证,可以减少对服务器端的压力.所以,你应该把握正则表达式方面的知识.网页特效:页面上很多特效是非常好的,能产生很好的用户 ...