Mybatis-接口请求原理

2021-01-22  本文已影响0人  麦大大吃不胖

by shihang.mai

1. mybatis的简单实用

我们使用mybatis的时候,首先定义接口,可以选择在接口中直接用注解的方式写上sql

@Mapper
public interface StudentMapper {

    @Select("select id, name,age from student")
    List<Student> queryStudents();
}

或者用xml方式写sql

<mapper namespace="com.shihangmai.test.mapper.StudentMapper">

    <resultMap id="studentMap" type="com.shihangmai.test.Student">
        <result column="id" property="id" />
        <result column="name" property="name" />
        <result column="age" property="age" />
    </resultMap>

    <select id="queryStudents" resultMap="studentMap" >
         select id, name,age from student
    </select>

</mapper>

使用的时候,直接用接口.方法就可以了


//使用接口
public class StudentImpl {

    @Autowired
    StudentMapper studentMapper;

    public void test(){
      studentMapper.queryStudents()
    }

}

那当然配置mybatis肯定需要咯,这里用springboot,配置文件中配置sql xml路径

mybatis:
   configuration:
      cache-enabled: false
      map-underscore-to-camel-case: true
      use-generated-keys: true
      default-executor-type: reuse
      default-statement-timeout: 60
   mapper-locations: classpath:sqlmapper/*Mapper.xml

启动类上写上扫面接口的路径

@MapperScan("com.shihangmai.test.mapper")
@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        ApplicationContext a =SpringApplication.run(CacheApplication.class, args);
    }

}

2. 原理分析

那为什么只用接口就可以执行sql返回数据呢?
我们传统用mybatis,都先通过

  1. Resources.getResourceAsStream(sql xml文件)获取SqlSessionFactory
  2. 通过SqlSessionFactory.openSession()获取SqlSession
  3. 再通过SqlSession.getMapper(类.class)获取接口对象,再点方法就可以用了;

所以关键点在于SqlSession.getMapper是如何获取到接口对象的,接下来分析源码,sqlSession使用的是默认实现类DefaultSqlSession

  1. DefaultSqlSession. getMapper(Class<T> type)调用Configuration.getMapper(Class<T> type, SqlSession sqlSession)
//DefaultSqlSession类
@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
//Configuration类
protected MapperRegistry mapperRegistry = new MapperRegistry(this);

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  1. 接着调用MapperRegistry.getMapper(Class<T> type, SqlSession sqlSession),其中MapperRegistry中包含Configuratio。这里会通过Class获取MapperProxyFactory.获取MapperProxyFactory后调用其newInstance(SqlSession sqlSession)
//MapperRegistry类
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

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);
    }
  }
  1. MapperProxyFactory. newInstance(SqlSession sqlSession)中构建MapperProxy,再调用一个重载方法newInstance(MapperProxy<T> mapperProxy)
//MapperProxyFactory
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;

//单例
  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;
  }

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

}

至此看出返回了一个接口的代理对象,并且InvocationHandler是mapperProxy

  1. 我们来看看MapperProxy的invoke,这个方法会在代理对象调用方法时调起,先放到从cachedMapperMethod中获取,没有就缓存,并调起MapperMethod.execute方法
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 {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    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;
  }

}

3. 总结

流程图
  1. getMapper方法实质上是获取一个JDK动态代理对象,这个代理类会继承MapperProxy类,实现被代理的接口,如StudentMapper,并且里面持有一个MapperProxy类型的触发管理类
  2. 拿到代理类后,调用方法时,调起MapperProxy的invoke,invoke里面
  1. springboot已经帮我们做了读取xml解析为配置等等.我们直接@Autowired时,放入的是代理对象,调用方法时执行2
上一篇 下一篇

猜你喜欢

热点阅读