mybatis-mapper 原理

403 阅读6分钟

概述

在日常开发中,我们经常会通过Mybatis的Mapper接口方式来实现操作数据库的功能,因为使用此方式最为方便快捷,我们只需要定义接口并编写对应的映射文件即可完成对数据库的操作。但此方式的底层实现原理是怎样的呢,本文将结合Mybatis源码来进行详细阐述

quick start- mapper 使用方式

  • 定义mapper

public interface UserMapper { 
void insert(User user); 
}
  • 编写对应的映射文件

<mapper namespace="spring.dao.mapper.UserMapper">
    <insert id="insert" parameterType="spring.domain.User">
        insert into t_user (user_id, user_name) values
        <![CDATA[
                (#{userId}, #{userName})
        ]]>
    </insert>
</mapper>
  • 在业务中使用

@Repository
public class UserDao {
	@Autowired
	private UserMapper userMapper;

	public void create(User user) {
		userMapper.insert(user);
	}
}

Mapper原理详细解读

image.png

  • MapperRegistry & MapperProxyFactory

MapperRegistry是Mapper接口及其对应的代理对象工厂的注册中心。Configuration是MyBatis全局性的配置对象,在MyBatis初始化过程中,所有配置信息都会被解析成相应的对象并记录到Configuration对象中。MapperRegistry中字段的含义和功能如下:

public class MapperRegistry {

  //Configuration对象, MyBatis全局唯一的配置对象, 其中包含了所有配置信息
  private final Configuration config;
  
  //记录了Mapper接口与对应MapperProxyFactory之间的关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  
  ...
  }

在MyBatis初始化过程中会读取配置文件以及Mapper接口中的注解信息,并调用MapperRegistry.addMapper()  方法填充MapperRegistry.knownMappers集合,代码如下:

public <T> void addMapper(Class<T> type) {
///通常来说Mapper是一个interface
  if (type.isInterface()) {
  //已经注册直接抛异常(不能重复注册) Start
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    //是否注册成功
    boolean loadCompleted = false;
    try {
      knownMappers.put(type, new MapperProxyFactory<T>(type));
      
      //这里是XML解析和注解的处理 Start
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

在knownMappers集合中,key是Mapper接口对应的Class对象,value是MapperProxyFactory工厂对象,可以为Mapper接口创建代理对象

在需要执行某SQL语句时,会先调用MapperRegistry.getMapper()方法获取实现了Mapper接口的代理对象,getMapper()方法如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //获取Mapper代理对象工厂 Start
  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

MapperProxyFactory主要负责创建代理对象,其核心字段含义如下:

public class MapperProxyFactory<T> {

  //Mapper对应的Class
  private final Class<T> mapperInterface;
  
  //key是mapper interface接口中某个方法的Method对象, value是对应的MapperMethodInvoker对象
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

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

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

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

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

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

}
  • MapperProxy

MapperProxy实现了InvocationHandler接口,这里就不做过多介绍了。该接口的实现是代理对象的核心逻辑。MapperProxy中核心字段的含义和功能如下:

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

  @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);
    }
    
    
    
    // 利用接口、方法等信息,构造mapperMethod并进行缓存 
    // MapperMethod代理Mapper中的方法(下面有对MapperMethod的详细介绍)
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    
    // 最终通过MapperMethod代理执行具体的数据库操作
    return mapperMethod.execute(sqlSession, args);
  }

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

  @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return ((method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
        && method.getDeclaringClass().isInterface();
  }
}

此处简单来讲,MapperProxy就对应是一个Mapper接口的代理,一个MapperMethod就是Mapper定义的一个方法的封装类。 结合开篇所述的Mapper接口的日常使用例子,UserMapper的代理类就是一个MapperProxy实例,mapper的insert方法的封装类就是一个mapperMethod实例。

注:插入一个文章内容之外的知识点,大家知道,代理中的invoke方法的第一个参数有什么意义吗? 回答:invoke第二个参数是method,是通过Proxy动态实例 (即第一个参数proxy)生成后(class文件),通过反向编译得到的,也就是说只有proxy实例在InvocationHandler实现类里加载才能产生第二个参数method,所以Proxy 实例要把自己传给InvocationHandler的invoke 方法。真正在invoke方法执行过程中,一般用不到proxy参数

  • MapperMethod详解

MapperMethod是对Mapper接口方法的实际映射(封装类),每个Mapper接口方法在调用的时候,都会先生成对应的MapperMethod,来最终实例化执行数据库操作。

SqlCommand:内部类,封装了SQL标签的类型:insert update delete select
MethodSignature:内部类,封装了方法的参数信息、返回类型信息等

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
  • MapperMethod的execute方法

execute方法是对SqlSession的包装,对应insert、delete、update、select四种操作。

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
  // INSERT操作
    case INSERT: {
    // 处理参数
       Object param = method.convertArgsToSqlCommandParam(args);
   // 调用sqlSession的insert方法
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
    // UPDATE操作 同上
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
    // DELETE操作 同上
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
    // SELECT
      if (method.returnsVoid() && method.hasResultHandler()) {
      // 如果返回void 并且参数有resultHandler 
      // 则调用void select(String statement, Object parameter, ResultHandler handler)方法
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
      // 如果返回多行结果
      // executeForMany这个方法调用 <E> List<E> selectList(String statement, Object parameter); 
        result = executeForMany(sqlSession, args);
        
      } else if (method.returnsMap()) {
      // 如果返回类型是MAP 则调用executeForMap方法
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
         //返回游标,则调用executeForCursor
        result = executeForCursor(sqlSession, args);
      } else {
      //否则就是查询单个对象
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName() 
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

总结

本文结合Mybatis源码,详细的阐述了业务编码中通过Mapper接口方式来操作数据库的底层实现原理。 实现关键在于每次通过调用接口方法操作数据库的时候,Mybatis都会利用MapperProxyFactory创建当前Mapper接口对应的MapperProxy代理实现类,在此代理类定义的增强中,会利用sqlSession、接口、方法等信息构造MapperMethod。MapperMethod是Mybatis对Mapper的接口方法生成的对应封装类,此封装类定义了真正的操作数据库的代码实现,最终对数据库的操作就是依赖他实现的。 综上所述,开发人员之所以可以非常便捷的使用Mapper接口方式来实现操作数据库,是因为Mybatis利用了代理机制,封装了中间的代码实现,极大的降低了开发复杂度。