MyBatis源码解析之基础模块—binding

MyBatis源码解析之基础模块—bindingbinding未诞生之暗黑时代在介绍MyBatis的binding之前,咱们先一段代码:UserInfoDAOpackage com.todobugs.study.dao;import com.todobugs.study.domain.UserInfo;import com.todobugs.study.query.UserInfoQuery;public interface UserInfoDAO { Long insert(U.
摘要由CSDN通过智能技术生成

在这里插入图片描述

MyBatis源码解析之基础模块—binding

binding未诞生之暗黑时代

在介绍MyBatis的binding之前,咱们先一段代码:

UserInfoDAO

package com.todobugs.study.dao;

import com.todobugs.study.domain.UserInfo;
import com.todobugs.study.query.UserInfoQuery;

public interface UserInfoDAO {
   
    Long insert(UserInfo userInfo);
    /**
     * 根据用户名查找
     * @param userName
     * @return
     */
    UserInfo selectUserInfoByName(String userName);
}

UserInfoDaoImpl

package com.todobugs.study.dao.impl;

import com.todobugs.study.dao.BaseDAO;
import com.todobugs.study.dao.UserInfoDAO;
import com.todobugs.study.domain.UserInfo;
import org.springframework.stereotype.Repository;

@Repository("userInfoDAO")
public class UserInfoDAOImpl extends BaseDAO implements UserInfoDAO {
   
  
    private static final String SQLMAP_SPACE = "USER_INFO.";

    public Long insert(UserInfo userInfo) {
   
        return (Long)getSqlMapClientTemplate().insert(SQLMAP_SPACE + "insert", userInfo);
    }

	@Override
	public UserInfo selectUserInfoByName(String userName) {
   
		return (UserInfo) this.getSqlMapClientTemplate().queryForObject(SQLMAP_SPACE+"getByName", userName);
	}
}

上述两份源码就是使用ibatis开发的dao,从中可以看出dao实现类其实没有什么业务逻辑处理,就是为了绑定namespace 及sql节点。

在ibatis时代,开发者在编写dao(即现在的mapper)时必须要实现该dao接口,其根本目的只是指定对应的namespace及操作节点。虽然实现内容很简单,这给开发者带来不必要且繁琐的编码,且在编译时并不能发现开发者是否存在异常,只有在运行时才能发现。

为解决这种操作方式的弊端,在mybatis版本中提供了binding模块。从而能够在编译期就能够发现问题。同时通过采用jdk动态代理模式,开发者只需要要编写对应的接口即可完成持久层的开发工作。即降低工作量,有大大降低出错概率。

接下来,我们将通过源码详细介绍binding的执行逻辑。

架构设计

binding模块所在包路径为org.apache.ibatis.binding,类关系比较简单,总共就五个类:

  • MapperRegistry:Mapper注册类
  • MapperProxyFactory:Mapper代理工厂类
  • MapperProxy:Mapper代理类
  • MapperMethod:Mapper执行方法
  • BindingException:绑定异常类()

其类之间的架构设计关系为:

在这里插入图片描述

接下来各类中主要方法依次介绍。

源码解读

MapperRegistry

老规矩,先上源码:

package org.apache.ibatis.binding;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;


public class MapperRegistry {
   
  /** 全局配置类 */
  private final Configuration config;
  /** 已添加的mapper代理类工厂 */
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  /** 构造函数 */
  public MapperRegistry(Configuration config) {
   
    this.config = config;
  }

  /** 根据包名添加mapper */
  public void addMappers(String packageName) {
   
    //默认superType为Object.class,这样该包下的所有接口均会被添加到knownMappers中
    addMappers(packageName, Object.class);
  }

  /** 根据指定包名及父类类型添加mapper */
  public void addMappers(String packageName, Class<?> superType) {
   
    /** 通过resolverUtil类判断查询packageName包下所有匹配superType的类型,并添加到一个set类型集合中 */
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    /** 循环遍历该集合,并将该mapperClass添加到knownMappers中 */
    for (Class<?> mapperClass : mapperSet) {
   
      addMapper(mapperClass);
    }
  }

  /**
   * 判断是否为接口,是的话才会生成代理对象
   * 后续会在运行时该代理对象会被拦截器进行拦截处理
   */
  public <T> void addMapper(Class<T> type) {
   
    /** 1、判断传入的type是否是一个接口
     *  2、判断knownMappers是否已经存在,若存在则抛出已存在异常。
     *  3、设置是否加载完成标识,final会根据是否加载完成来区别是否删除该type的设置
     *  4、将该接口put到knownMappers中
     *  5、调用MapperAnnotationBuilder构造方法,并进行解析。(具体处理逻辑会在builder模块中展开)
     */
    if (type.isInterface()) {
   
      if (hasMapper(type)) {
   
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
   
        knownMappers.put(type, new MapperProxyFactory<>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
   
        if (!loadCompleted) {
   
          knownMappers.remove(type);
        }
      }
    }
  }

  /** 获取mapper代理对象 */
  @SuppressWarnings("unchecked")
  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);
    }
  }

  /**判断knownMappers中是否存在该类型的mapper代理工厂*/
  public <T> boolean hasMapper(Class<T> type) {
   
    return knownMappers.containsKey(type);
  }

  /** 主要用于测试,无需关注 */
  public Collection<Class<?>> getMappers() {
   
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }
}

在Configuration实例化时,会通过如下方式进行实例化。

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

从源码中可以看出,MapperRegister类只有两个属性,七个方法(包含一个构造方法)

  • config:该属性里面包含各种mybatis的配置信息,此处不再赘述。
  • knownMappers:该属性存放mapper接口并提供其代理对象(稍后介绍MapperProxyFactory)。
  • MapperRegistry:该构造方法注入config配置信息。
  • addMappers(String packageName):根据包名添加该包下的所有mapper接口类,调用下述重载方法
  • addMappers(String packageName, Class<?> superType):重载方法,根据包名及父类类型添加该包下的所有mapper接口类
  • getMapper:根据mapper类型及sqlsession获取对应mapper接口的代理对象。
  • hasMapper:根据mapper类型判断knownMappers中是否已存在,主要用于addMapper时校验。
  • addMapper:根据mapper类型将其添加到knownMappers中,该方法默认被addMappers(String packageName, Class<?> superType)循环调用,开发者亦可手动调用。
MapperProxyFactory
package org.apache.ibatis.binding;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.binding.MapperProxy.MapperMethodInvoker;
import org.apache.ibatis.session.SqlSession;

public class MapperProxyFactory<T> {
   
  /** mapper接口 */
  private final Class<T> mapperInterface;
  /** method缓存 */
  private final Map<Method, MapperMethodInvoker> methodCache = 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值