Mybatis技术原理理——总体流程理解

前言:2018年,是最杂乱的一年!因此你看个人博客,是否是很空!html

 

网上有不少关于Mybatis原理介绍的博文,这里介绍两篇我我的很推荐的博文  Mybatis3.4.x技术内幕 MyBaits源码分析!让我找到了学习的入口,固然另外你必需要看的官方文档 MyBatis学习。那么有了这些知识,就让咱们愉快的吃鸡之路吧!mysql

一:你首先得知道的知识点。

1.1 JDBCsql

  在我的看来, Mybatis的核心就是对SQL语句的管理!那么在JAVA环境下,对SQL的管理或者其余任何的实现,确定是离不开JAVA的数据库操做接口,包括Connrction接口、Statement接口、PreparadStatement接口以及ResultSet接口等等的属性,你能够先经过JDBC来操做一次或者更屡次的数据库。这个就很少作赘述了!数据库

1.2 动态代理apache

  不知道你最初有没有和我同样,有这样的疑问。Mybatis的mapper层明明就是一个接口,都没有实现类!即便是在TMapper.xml中作了映射,也没有看到任何关于接口的实现类,那他是怎么被实例化的呢,又是怎么实现方法的功能的呢?动态代理会告诉你答案!缓存

先来看看jdk动态代理的一个小例子:
创建一个实体session

public class TestDemo { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

     创建一个mappermybatis

public interface TestDemoMapper { TestDemo getById(Integer id); }

  创建一个Mapper的代理类,须要继承一个 InvocationHandler 便可app

public class DemoMapperProxy<T> implements InvocationHandler { @SuppressWarnings("unchecked") public T newInstance(Class<T> clz) { return (T)Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { TestDemo testDemo = new TestDemo(); testDemo.setId(1); testDemo.setName("proxyName"); return testDemo; } }

测试类ide

public static void main(String[] args) {
        DemoMapperProxy<TestDemoMapper> demoMapperProxy = new DemoMapperProxy<TestDemoMapper>();
        TestDemoMapper testDemoMapper = demoMapperProxy.newInstance(TestDemoMapper.class);
        TestDemo byId = testDemoMapper.getById(1);
        System.out.println(byId);
    }

  结果:TestDemo{id=1, name='proxyName'},

  因此这就应该大体可以说明Mybatis中的mapper是怎么工做的了吧!

二:关于mapper的总体流程理解

2.1 先来看看官方网站的一个例子吧

在官方文档中的 Mybatis 3 的入门介绍中,介绍了怎么配置一个mybatis的测试类

mybatis-config.xml 中的一个简单配置:

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://172.20.139.237:3306/rubber-fruit?useUnicode=true&amp;characterEncoding=utf-8&amp;autoReconnect=true" />
                <property name="username" value="user123" />
                <property name="password" value="u123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml" />
    </mappers>
</configuration>

  而后在写一个测试类:

public static void main(String[] args) throws Exception { String resouse = "mybatis-config.xml"; InputStream stream = Resources.getResourceAsStream(resouse); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User byId = mapper.getById(1); sqlSession.close(); System.out.println(byId); }

  其实这个逻辑很简单,首先是拿到mabatis-config.xml 中的配置对象,生成SqlSessionFactory,而后经过sqlSession拿到mapper,经过动态代理完成方法的执行,并返回结果!那么咱们就能够简单的拆分红两个部分,第一:配置文件的初始化,第二:sql的执行,后面的文章也会更具这个大模块来更具细化的讲解

  其实上面的第四行代码 能够更换成config对象的,可能看的更清楚一些:

     String resouse = "mybatis-config.xml"; InputStream stream = Resources.getResourceAsStream(resouse); XMLConfigBuilder configBuilder = new XMLConfigBuilder(stream,null,null); Configuration parse = configBuilder.parse(); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(parse);

  其实Mybatis的前期初始化的一个重要的对象就是 Configuration对象,后面会慢慢揭开它的面纱!

2.2 总体流程图

经过上面的这个测试方法,咱们经过源码流程,大体能够拆分出这样的一个图:

 其实从这个图中咱们已经能够看出Mybatis划分先两个部分:

 1:初始化过程

  首先是经过XMLConfigBuilder解析 mybatis-config.xml 对象来进行初始化,拿到Configuration对象,而后SqlSessionFactoryBuilder经过初始化的Configuration对象,生成SqlSession,SqlSession中调用getMapper对象方法,其实也就是Configuration中的getMapper方法 经过MapperProxy代理对象生成一个mapper。到这里位置就是一个简单的初初始化思路了!固然Mybatis的初始化远远不止这些。

2:mybatia中执行一个sql的完整流程

  从图中也是可以很较清晰的看出这个执行流程的。由于Mapper是一个接口,因此不能直接实例化的!那么MapperProxy的做用,就是经过JDK的动态代理,来间接的对mapper进行实例化,不了解的能够看上面的1.2中的例子。那么咱们能够看看org.apache.ibatis.binding.MapperProxy源码中的对象方法:

@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); } 

经过代理的对象方法方法,拿到了一个MapperMethod对象,并对mapperMethod对象进行来缓存。为啥要缓存呢?这个时候能够看看MapperMethod对象是什么?org.apache.ibatis.binding.MapperMethod中对两个私有方法

private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

     MapperMethod中的SqlCommand对象中的两个私有方法,    

public static class SqlCommand { private final String name; private final SqlCommandType type; }
public enum SqlCommandType {
  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}

    而MapperMethod中的MethodSignature对象呢?

   private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver;

   因此这些很清晰了,MapperMethod对象是把mapper中的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; }

  经过执行的sql类型 选取不通的sqlSession进行执行。其实能够看出,转了一圈,最后的最后仍是落在了SqlSession当中。而SqlSession又把这个重要的操做交个了执行器Executor。最后又到了StatementHandler来负责执行最后的sql,ResultSetHandler放回执行的结果。

  2.3 记一个知识点

  看到这里,忽然是想问一下一个问题的,Mapper的方法是否支持重载呢?

  答案是不能的!mybatis是使用package+Mapper+method 全名称做为Key值 去xml中寻找一个惟一sql来执行的那么,重载方法时将致使矛盾。对于Mapper接口,Mybatis禁止方法重载。经过代码断点能够看到。

  org.apache.ibatis.session.Configuration对象中有一个方法,addMappedStatement()

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

 /**中间部分省略掉**

 public void addMappedStatement(MappedStatement ms) {

 mappedStatements.put(ms.getId(), ms); }

    

  而这个地方put的ms.getId 就是经过mapper的配置文件组合成的一个惟一的key值。那咱们在看看StrictMap 中的put方法源码,那么就一目了然了

@SuppressWarnings("unchecked") public V put(String key, V value) { if (containsKey(key)) { throw new IllegalArgumentException(name + " already contains value for " + key); } if (key.contains(".")) { final String shortKey = getShortName(key); if (super.get(shortKey) == null) { super.put(shortKey, value); } else { super.put(shortKey, (V) new Ambiguity(shortKey)); } } return super.put(key, value); }

  若是有相同的key的,那么会抛出异常,这也就是为啥mapper不能重载的缘由!

相关文章
相关标签/搜索