美文网首页
Mybatis-接口请求原理

Mybatis-接口请求原理

作者: 麦大大吃不胖 | 来源:发表于2021-01-22 14:54 被阅读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里面
    • cachedMapperMethod,缓存method对应的信息,包含configuration,inf
    • MapperMethod执行sql并返回结果, MapperMethod包含sql、sql需要的参数、方法签名
    1. springboot已经帮我们做了读取xml解析为配置等等.我们直接@Autowired时,放入的是代理对象,调用方法时执行2

    相关文章

      网友评论

          本文标题:Mybatis-接口请求原理

          本文链接:https://www.haomeiwen.com/subject/fplszktx.html