侧边栏壁纸
  • 累计撰写 101 篇文章
  • 累计创建 89 个标签
  • 累计收到 9 条评论

Mybatis源码简单分析二

bearjun
2021-08-18 / 0 评论 / 1 点赞 / 1,242 阅读 / 10,359 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2021-08-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前面说了Mybatis的核心对象,那Mybatis的核心对象如何与SqlSession建立联系呢?

Mybatis的核心对象与SqlSession之间的联系

首先我们知道,用Mybatis操作数据库获取结果有两只方式

// 指定配置文件
String resource = "org/mybatis/example/mybatis-config.xml";
// 记在配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

//方式一
try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

//方式二
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

由上面的代码我们都知道,第二种方式更加优雅和美观,原因是可以更好更清楚的知道我们要做什么操作,干什么。而不是像方法一样写一个字符串(该字符串是dao的全限定类名 + "." + xml中id)。
方法二是Mybatis现在的版本我们最常用的方式。单其实,方法二的底层就是用的第一种进行操作的。
接下来我们一步步追一下方式一源码:
79516-1v4tx7qzbos.png
在DefaultSqlSession中,我们看到了executor.query()方法,再往下:
24163-kh7zdi37phj.png
45887-yui31c2hc9.png
79047-4ml87o65804.png
在SimpleExecutor中,先获取StatementHandler,然后在执行update操作:
53396-63a4km7b7cp.png
终于看到熟悉的代码了,statement.execute()不就是JDBC中的操作吗?
现在终于把之前的都串起来,首先SqlSession内部封装了Executor,而Executor咋操作增删改查的时候,调用了StatementHandler,StatementHandler内部封装了Statement,直接调用操作,完成数据操作。

那么问题来呢?
我们一般常用的是方法二,那方法二又是怎么封装转换的呢?

我们观察方法二发现,session.getMapper(BlogMapper.class)返回了一个BlogMapper,我们都知道,BlogMapper是一个借口,是没有办法进行实例化的。那么只能说明一个,BlogMapper是BlogMapper接口实现类的对象。

那么问题又来了?
BlogMapper的接口实现类在哪呢?按照我们前面的7步开发步骤,没有BlogMapper实现类这一步啊?
其实啊,MyBatis在这里用了动态字节码技术,即类在JVM运行时创建,当JVM运行结束后,就消失不见了。

好了,我们知道是用什么技术创建的。那么问题又又又来了?

  1. 如何创建BlogMapper接口的实现类呢?
  2. BlogMapper接口的实现类如何实现的呢?

我们先来看问题二,实现接口还不简单,就是创建一个类,来实现BlogMapper,那实现类的具体功能怎么写呢?根据前面的内容我们知道,Mybatis就是通过SqlSession来进行各种select、update、delete操作的,那实现类的内容就是SqlSession.select()。

好了,问题二我们分析好了,那问题一呢?我们的项目并没有创建实现类,但是Mybatis怎么就已经做出了操作呢?
要看这个问题,我们要了解一下动态代理的几种应用场景:

  • 为原始目标增强,就是spring中常用的功能增强
  • 远程代理,一些rpc中网络通信或者数据传输
  • 实现接口类,虽然看不见,但运行的时候可以体现出来

看到这里,我们已经知道用什么了吧。对,就是动态代理,通过动态代理来实现接口的实现类,从而实现对应的功能。
那下面,我们也来写一个来模拟一下吧:

String resource = "org/mybatis/example/mybatis-config.xml";
// 记在配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

try (SqlSession session = sqlSessionFactory.openSession()) {
  // BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 通过动态代理模拟上面的一句代码,进而获取到BlogMapper
  Class[] interfalces = new Class []{BlogDAO. class};
   BlogDAO mapper = (BlogDAO) Proxy.newProxyInstance(this.class.getClassLoader() , interfaces
, new MyMapperProxy(sqLSession, BlogDAO.class));

  Blog blog = mapper.selectBlog(101);
}

定义一个proxy来实现InvocationHandler

public class MyMapperProxy impLements InvocationHandler {

	private SqLSession sqlSession;
	private Class daoClass;

	public MyMapperProxy (SqLSession sqlSession, Class daoClass) {
		this.sqLSession = sqLSession;
		this.daoCLass = daoCLass;
	}

	@Override
	public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
		return sqLSession.select/update/delete(daoClass.getName() + "." + method.getName() ) ;
	}
}

看到这里,我想大家对Mybatis代理的核心思想都已经比较清楚了。

Mybatis完成代理创建的核心类型

Mybatis完成代理创建的核心类型其实就是MapperProxy和MapperProxyFactory。
MapperProxy就相当于上面的MyMapperProxy,主要就是实现了InvocationHandler接口,并重写invoke的方法,在里面用Sqlsession进行了insert|update|delete|select等操作。
MapperProxyFactory在底层就是做了刚才增强的操作:Proxy.newProxyInstance()。

接下来,我们来追一下源码来看看:

public class MapperProxyFactory<T> {
  
  // 需要代理的接口 
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  // 传入了mapperProxy,即实现了InvocationHandler的类
  public T newInstance(SqlSession sqlSession) {
    // 创建mapperProxy 参数:sqlSession,需要代理的接口,..
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

我们发现,MapperProxyFactory确实需要一个mapperInterface的参数,而且这个参数就是需要代理的接口,即各种xxxMapper。而且构造方法中Proxy.newProxyInstance()不就和我们分析的一致吗?
然后在看参数:mapperProxy,这个就是实现了InvocationHandler的那个类。

然后再来看mapperProxy,这个肯定比我们写的那个要难得多:

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  static {
    Method privateLookupIn;
    try {
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
      // JDK 1.8
      try {
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Exception e) {
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  @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 {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

  private MethodHandle getMethodHandleJava9(Method method)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
        declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
        declaringClass);
  }

  private MethodHandle getMethodHandleJava8(Method method)
      throws IllegalAccessException, InstantiationException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  }

  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

  private static class DefaultMethodInvoker implements MapperMethodInvoker {
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
      super();
      this.methodHandle = methodHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
  }
}

首先,我们可以很清楚的看到两次参数:sqlSession,mapperInterface,其次该方法实现了InvocationHandler。这个和我们分析的是一致的。然后我们来读一下代码:

  • 静态代码代块,给一些属性变量进行赋值操作
  • 然后invoke操作,首先判断是否是Object自带的一些方法,如果不是,则执行invoke
  • 先往methodCache中存入值,存的过程中,判断当前方法是否是公共非抽象实例方法
  • 然后我们会调用PlainMethodInvoker的构造方法,MapperMethod里面有两个特别的属性,我们需要注意下:MapperMethod:1、获取namespace + id 2、判断是什么操作, insert|update|delete|select等操作 MethodSignature:封装了返回值类型,分页,参数等
  • PlainMethodInvoker的invoke方法,执行sql
  • 判断是否查询进行分别操作

好了,到这里,前面的内容基本都讲清楚。

配置文件读取和工厂的创建

接下来我们就来看看前半部分:

String resource = "org/mybatis/example/mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession()

那这一部分说了些什么呢?

  • 读取配置文件,之后通过解析config.xml成Configuration对象和解析xxMapper.xml成MappedStatement

47120-2i308wxwxut.png
24266-spzn4yhpw3k.png
通过xPath解析成对应的对象。

  • 通过解析的Configuration对象创建DefaultSqlSessionFactory
  • 通过DefaultSqlSessionFactory#openSessionFromDataSource()创建SqlSession对象

69635-kwzjwf4xvxa.png

至此,Mybatis的前半部分基本清晰明了。

总结

最后我们来总结一下整个Mybatis的主要流程:

1、读取配置文件
2、解析config.xml --> Configuration(通过xPathParser解析成XNode实现的)
   bulid() -> parseConfiguration(parser.evalNode("/configuration"))
   parseConfiguration -> mapperElement(root.evalNode("mappers"))
                           |- XMLMapperBuilder mapperParser = new XMLMapperBuilder();
                              |- mapperParser.parse()
                                 |-configurationElement(parser.evalNode("/mapper"))
                                     |- statementParser.parseStatementNode()
                                        |- MappedStatement statement = statementBuilder.build()
                                           |-configuration.addMappedStatement(statement)
3、通过DefaultSqlSessionFactory#openSessionFromDataSource()创建SqlSession对象
   final Environment environment = configuration.getEnvironment();
   final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
   tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
   final Executor executor = configuration.newExecutor(tx, execType);
   return new DefaultSqlSession(configuration, executor, autoCommit);
4、slSession.getMapper()通过动态代理实现xxDAO接口的实现
   MapperProxyFactory通过jdk创建代理
      |- InvocationHandler
         |- SqlCommoand
            |- type:操作类型inset|update|datele|select
            |- name:namespace.id
         |- MethodSignature
            |- 方法的参数、返回值、分页等信息
            |- 通过类型不同执行sqlSession.update|delete|select
               |-Executor(SimpleExecutor ReuserExecutor BatchExecutor)
                  |-StatementHandler
                     |- ParameterHadler ResultSetHandler
                         TypeHandler
5、获取结果集

小弟也是第一次分析,如有纰漏,欢迎各位大佬指出。

1

评论区