前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis源码学习(二)Mapper动态代理

Mybatis源码学习(二)Mapper动态代理

作者头像
虞大大
发布2020-09-01 11:17:20
3230
发布2020-09-01 11:17:20
举报

一、SqlSession

上一篇分析了SqlSessionFactoryBuilder是如何解析mapper的,并且mapper的核心在于会将所有的mapper接口注册到MapperRegistry中(key-mapper接口Class类,value-mapper的代理工厂)。

当SqlsessionFactoryBuilder的build方法执行完毕后,如下图Mybatis文档中的mapper.xml所示:mapper信息、select信息、resultType信息、sql信息、resultMap信息、paraterMap信息已经被全部解析出来。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select></mapper>

· SqlSession会话

BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101);

SqlSessionFactoryBuilder的build方法执行后,SqlSessionFactory就被创建了出来。如上图Mybatis文档的执行sql示例所示,我们需要使用SqlSessionFactory来打开sqlSession,通过sqlSession来获取mapper接口,执行数据库方法。

· openSession

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  @Override
  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource            (configuration.getDefaultExecutorType(), null, autoCommit);
  }  //...}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  Transaction tx = null;
  try {    //环境变量,存放着事物工厂、jdbc数据源信息    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //根据execType创建执行器,默认为Simple(SimpleExecutor)    final Executor executor = configuration.newExecutor(tx, execType);
    //最后创建sqlSession    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx);
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

通过SqlSessionFactory的openSession方法来创建SqlSession,在方法中,会对解析后的environment、事物管理器进行赋值,并且创建executor执行器。

· getMapper

SqlSession创建成功后,需要通过sqlSession.getMapper来获取mapper接口。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  //从map中取出mapper对应的代理工厂
final MapperProxyFactory<T> mapperProxyFactory =(MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
try {//通过jdk动态代理创建mapper代理类,并且指定了sqlSession。return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}protected T newInstance(MapperProxy<T> mapperProxy) {  //通过jdk动态代理生成mapper代理类  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

从上述getMapper的源码中可以看出,想要获取mapper接口类,实际上是调用了mapperRegistry.getMapper方法,从hashMap中取出mapper的代理工厂类,最后通过jdk动态代理来创建出mapper的代理类。

二、Mapper的动态代理类

· MapperProxy

我们先来看下生成的动态代理类中发生了什么:

public class MapperProxy<T> implements InvocationHandler, Serializable {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
       try {        //如果是类的基础方法,则invoke执行        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
}    //否则对代理方法进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);    //执行数据库操作    return mapperMethod.execute(sqlSession, args);
  }}
Blog blog = mapper.selectBlog(101);

当调用mapper.selectBlog(101)时,会先从mapper的注册中心中拿到对应的mapper动态代理类,在对非基础方法进行执行(即数据库操作方法)时,会先对代理方法进行缓存,然后再进行数据库操作。

(1)cachedMapperMethod

private MapperMethod cachedMapperMethod(Method method) {  //从缓存中判断是否存在  MapperMethod mapperMethod = methodCache.get(method);
  if (mapperMethod == null) {    //缓存不存在,则进行封装,然后放到缓存中。    mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    methodCache.put(method, mapperMethod);
  }
  return mapperMethod;
}
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  this.command = new SqlCommand(config, mapperInterface, method);
  this.method = new MethodSignature(config, mapperInterface, method);
}

· SqlCommand的封装

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  //name = 接口名+方法名  String statementName = mapperInterface.getName() + "." + method.getName();
  MappedStatement ms = null;
  //增删改成的方法,都会走到这步逻辑,  //在sqlSessionFactoryBuilder中都被加载到configuration中了  if (configuration.hasStatement(statementName)) {    //获取mappedStatement对象    ms = configuration.getMappedStatement(statementName);
  } else if (!mapperInterface.equals(method.getDeclaringClass())) {
    String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
    if (configuration.hasStatement(parentStatementName)) {
      ms = configuration.getMappedStatement(parentStatementName);
    }
  }  if (ms == null) {
    if(method.getAnnotation(Flush.class) != null){
      name = null;
      type = SqlCommandType.FLUSH;
    } else {
      throw new BindingException("Invalid bound statement (not found): " + statementName);
    }
  } else {    //name为接口名+方法名    name = ms.getId();
    //type为根据xml的sql信息解析后的select\insert\delete\update    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

在对SqlCommand进行封装时,会根据接口名+方法名来判断SqlSessionFactoryBuilder加载时是否存在对应的方法,如果存在设置name和type,name则为接口名+方法名,type则为根据xml的sql信息解析后的select\insert\delete\update标签。

· MethodSignature

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
//根据方法,接口解析returnType,里面主要包含了接口路径名、类加载器、返回类型等基础信息  Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  //设置returntype  if (resolvedReturnType instanceof Class<?>) {    this.returnType = (Class<?>) resolvedReturnType;
  } else if (resolvedReturnType instanceof ParameterizedType) {
    this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
  } else {
    this.returnType = method.getReturnType();
  }  //解析是否有返回参数  this.returnsVoid = void.class.equals(this.returnType);
  //返回信息是否为集合  this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
  this.returnsCursor = Cursor.class.equals(this.returnType);
  //是否存在mapKey注解  this.mapKey = getMapKey(method);
  this.returnsMap = (this.mapKey != null);    
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

在对MethodSignature进行封装时,主要是对方法的返回类型、返回参数等基础信息进行封装赋值。

最后全部封装完毕后,放入到methodCache缓存中。以便下一次相同接口路径名+方法名重复执行时,可以减少数据解析封装,提升性能。

三、总结

BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101);

以上述示例为例,在对mybatis进行加载时,会调用SqlSessionFatoryBuilder的build方法对mapper及其相关xml配置文件进行加载,mapper接口会被注册到mapperRegistry注册中心。需要使用mapper对数据库进行操作时,会通过sqlSession的getMapper方法,实际上mapper的底层也是调用了mapperRegistry.getMapper方法获取mapper接口,该注册中心的map的key为mapper的class对象,value为mapper接口的动态代理工厂。因此通过getMapper接口获取mapper时,也会对原来的mapper接口进行动态代理,生成一个mapper动态代理类进行返回。

当需要执行方法时,如selectBlog方法,会执行ProxyMapper中的增强方法,代理方法执行数据库操作前,会做一次缓存,缓存的数据结果的key为被执行的方法(即selectBolg方法对象),value为mapper解析后的基础信息,如:sql执行的标识id(即接口路径名+method)、增删改查的type、方法映射(返回集合、返回参数、返回类型)等基础信息,以便下次对同一个方法执行时,可以直接取缓存中的数据来提高性能。

最后通过executor开始执行数据库操作,下一篇分析。

问题一:接口Mapper内的方法能重载吗?

不能。在mapperProxy的动态代理方法中,会把sql执行的标识id等基础信息存放到缓存中来提高性能,而这里的标识id为接口路径名+执行方法,而这个标识id就是用作去xml中查找该执行的sql的唯一标识。如果出现了方法的重载,在使用标识id查询sql时就会出现多条需要执行的sql出现矛盾。

因此对于Mapper接口内的方法,是不允许重载的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档