准备系列-Mybatis(十八) TKmybatis 源码底层工作原理

144 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 18 天,点击查看活动详情

前面我们讲了Mybatis如何用及Mybatis的缓存等等,其实都是方便我们在项目中使用的,但是我们仅仅停留在使用阶段是不可以的,我们要懂得原理,这样才不会一被问到源码你就答不上来,下面我们就来 了解一下Mybatis的工作原理

1.Mybatis工作原理

  1. mybatis应用程序读取mybatis-config.xml配置文件
  2. 通过SqlSessionFactoryBuilder从初始化SqlSessionFactory
  3. SqlSessionFactory的实例直接开启SqlSession
  4. SqlSession实例通过getMapper()方法 动态代理获取Mapper对象
  5. mapper对象运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交
  6. 之后关闭SqlSession,完成SQL命令的完整执行

image.png

2.Mybatis的核心对象

2.1 JDBC链接Mysql查询

关于MyBatis的工作原理,要结合JDBC来理解MyBatis的工作原理往往才能更透彻。我们最初没有mybatis的时候,也没有用hibernate框架的时候, 我们采用原生的JDBC链接,JDBC有四个核心对象:
(1)DriverManager,用于注册数据库连接 ,获取获取驱动
(2)Connection,与数据库连接对象 ,获取数据库链接
(3)Statement/PrepareStatement,操作数据库SQL语句的对象,拼接SQL语句
(4)ResultSet,结果集或一张虚拟表,获取SQL的结果集

public static void testJdbc() {
   try {
       //定义一个MYSQL链接对象,链接本地MYSQL 
       Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
       //链接本地MYSQL
       Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驱动
       //创建Statement声明
       Statement stmt = con.createStatement();

       //插入一条数据
       stmt.executeUpdate("INSERT INTO user (username, password) VALUES ('init', '123456')");

       //执行查询,收集查询结果
       ResultSet res = stmt.executeQuery("select LAST_INSERT_ID()");
       
       int ret_id;
       if (res.next()) {
           ret_id = res.getInt(1);
           System.out.print(ret_id);
       }
   } catch (Exception e) {
       System.out.print("MYSQL ERROR:" + e.getMessage());
   }
}
2.2 Mybatis的四大核心对象

现在有了Mybatis,MyBatis有四大核心对象,类似于JDBC链接的几个步骤

  1. SqlSession对象,该对象中包含了驱动器,连接器Connection信息,及要执行SQL语句的方法
  2. Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement
  3. MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id-方法名、入参参数信息
  4. ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。类似于 jdbc的 ResultSet返回值
//从SqlSessionFactory 中获取 sqlSession
SqlSession sqlSession = sessionFactory.openSession();

//sqlSession获取Mapper 代理对象
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);

//mapper方法执行 mapper方法
UserInfoPO userInfoPO = mapper.selectUser("1");

我们看一下 SqlSession.getMapper方法

UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);

getMapper走的是 DefaultSqlSession 逻辑实现

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

Configuration 底层是 mapperRegistry的getMapper逻辑

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

MapperRegistry通过代理类MapperProxyFactory 来创建实例 mapperProxyFactory.newInstance(sqlSession);

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

我们进入 MapperProxyFactory的 newInstance方法,是不是很熟悉

Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

是的就是动态代理Proxy.newProxyInstance

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

protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

下面我们看下 MapperProxy结构 把SqlSession当作参数传进去,构建一个MapperProxy对象 的invoke方法 返回的是 mapperMethod.execute(sqlSession, args);

@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.execute执行方法, 判断SQL类型 具体执行不同的操作, 最终的最终具体的类型 insert,update, delete, select的executeForMany,executeForMap,executeForCursor 底层也是靠SqlSession在操作的SqlSession的操作

image.png

image.png

MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。

  • SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。
  • MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用

最终还是都是靠SqlSession来执行操作


至此我们 通过源码分析了mybatis的执行逻辑及底层实现原理,解了mybatis的底层操作是以动态代理的方式的