带你了解Mybatis中getMapper()的来龙去脉

getMapper用法在熟悉不过了,指明一个DAO接口的class,调用他的方法,即可从数据库中返回 ,是不是感觉很神奇?
其实都是通过JDK的动态代理来实现,getMapper返回的即一个代理对象,通常在写动态代理时,代理对象处理完成后还有调用被代理对象的对应方法,而像Mybatis这种面向接口的思想,没有被代理的对象,所以,Mybatis通过自己一系列操作后直接返回。

源码分析
一、加载MappedStatement

首先Mybatis要加载mapper下的所有insert、select、update、delete标签,并且封装成一个个MappedStatement对象,保存在Configuration下的Map<String, MappedStatement>中,具体实现在 XMLStatementBuilder#parseStatementNode()MapperBuilderAssistant#addMappedStatement() 的方法中(addMappedStatement参数不多,也就20个),其中 this.configuration.addMappedStatement()就是保存在Configuration配置类中
这里的Map键为namespace+标签的id,他在MapperBuilderAssistant#applyCurrentNamespace() 方法中被拼接,value即一个MappedStatement对象
Configuration的addMappedStatement方法拼接id还有之后在XMLMapperBuilder#bindMapperForNamespace() 保存mapper的namespace,存放在Configuration的mapperRegistry字段中的knownMappers下。knownMapper是一个map,key为namespace,value为MapperProxyFactory对象。

二、getMapper()

getMapper()从DefaultSqlSession下的getMapper()中开始,中间经过Configuration的getMapper(),最终调用到MapperRegistry下的getMapper();其中knownMappers中的值在上述addMapper()中被添加。

public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    public MapperRegistry(Configuration config) {
        this.config = config;
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    	//从已知的集合中返回mapper代理工厂,
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        //为空则抛出异常
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
            	//实例化代理对象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
 ........
}

动态代理生成工厂,通过newInstance实例化代理对象

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

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

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

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }
	
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
	    //JDK的动态代理实现,代理对象为MapperProxy。
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

所以在调用DAO中方法时,都会转入到MapperProxy的invoke方法下。
执行过程中判断了是不是java8中的default方法或者是不是Object下的方法。如果都不是,才去查询,

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
        //如果是Object中的方法,则直接执行代理类的对象的对应方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
		//如果是默认方法,也就是java8中的default方法
            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
		//从缓存中获取MapperMethod
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
    

这里先要从methodCache获取对应DAO方法的MapperMethod。

    private MapperMethod cachedMapperMethod(Method method) {
     MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
     if (mapperMethod == null) {
         mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
         this.methodCache.put(method, mapperMethod);
     }
     return mapperMethod;
 }

紧接着是生成MapperMethod.SqlCommand和MapperMethod.MethodSignature,两个自己的内部类,
在SqlCommand中,更具id从Configuration中获取MappedStatement,此时就是上述第一步中添加的MappedStatement,完成代码层和XML中的关系映射,并把MappedStatement的id和type提取出来,如果没有获取到,抛出异常,也就是为什么DAO中有select,xml配置中没有id为select抛出异常的原因。

public class MapperMethod {
   private final MapperMethod.SqlCommand command;
   private final MapperMethod.MethodSignature method;
   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
       this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
       this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
   }
//.........
 public static class SqlCommand {
       private final String name;
       private final SqlCommandType type;

       public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
           String methodName = method.getName();
           Class<?> declaringClass = method.getDeclaringClass();
           //从Configuration中获取MappedStatement
           MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
           if (ms == null) {
               if (method.getAnnotation(Flush.class) == null) {
                   throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
               }

               this.name = null;
               this.type = SqlCommandType.FLUSH;
           } else {
               this.name = ms.getId();
               this.type = ms.getSqlCommandType();
               if (this.type == SqlCommandType.UNKNOWN) {
                   throw new BindingException("Unknown execution method for: " + this.name);
               }
           }

       }

更具不同的标签类型,执行不同的方法

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 的 `SqlSession` 接口提供了许多关于数据库操作的方法,在其最为重要的就是 `getMapper(Class<T> type)` 方法。这个方法是用来获取一个 Mapper 接口的实例对象,从而可以调用其的方法来执行对应的 SQL 语句。 `getMapper()` 方法的原理是使用动态代理机制,根据传入的 `Class<T>` 类型动态生成一个代理类,并返回该代理对象。这个代理类会实现该接口的所有方法,当调用代理对象的方法时,实际上是调用了代理类的 `invoke()` 方法,该方法会根据方法名和参数类型等信息来生成对应的 SQL 语句,并通过 `SqlSession` 对象执行该 SQL 语句。 具体实现过程如下: 1. 当调用 `getMapper()` 方法时,会先判断是否已经存在该 Mapper 接口的代理对象,如果已经存在,则直接返回该代理对象;否则,进入下一步。 2. 根据传入的 `Class<T>` 类型使用 `Proxy.newProxyInstance()` 方法动态生成一个代理类,并返回一个代理对象。 3. 在代理类重写 `invoke()` 方法,当调用代理对象的方法时,会首先根据方法名和参数类型等信息生成对应的 `MappedStatement` 对象。 4. 调用 `SqlSession` 对象的 `selectList()`、`selectOne()`、`insert()`、`update()` 或 `delete()` 方法来执行 SQL 语句,其 `MappedStatement` 对象包含了 SQL 语句和参数等信息。 5. 最后返回执行结果。 总之,`getMapper()` 方法的核心就是使用动态代理机制来生成 Mapper 接口的代理对象,并将调用方法的信息转化为 SQL 语句执行。这样就可以在代码使用简单的方法调用来实现复杂的数据库操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值