`

MyBatis的Mapper是什么`垃圾`

阅读更多

说明

MyBatis版本:3.5.1

 

前言

通过上面的叙述我们已经知道我们与mybatis交互主要是通过配置文件或者配置对象,但是我们最终的目的是要操作数据库的,所以mybatis为我们提供了sqlSession这个对象来进行所有的操作,也就是说我们真正通过mybatis操作数据库只要对接sqlSession这个对象就可以了。在实际中,我们并不会直接使用sqlSession进行直接操作,而是使用了Mapper。

问题1:Mapper对象怎么来的?

       我们可以使用SqlSession的getMapper进行获取:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

说明:

通过调用DefaultSqlSession的getMapper方法并且传入一个类型对象获取,底层呢调用的是配置对象configuration的getMapper方法,configuration对象是我们在加载DefaultSqlSessionFactory时传入的。

       然后我们再来看下这个配置对象的getMapper,传入的是类型对象(也就是我们自定义的Mapper.java对应的接口)和自身,也就是DefaultSqlSession。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

 

我们看到这个configuration的getMapper方法里调用的是mapperRegistry的getMapper方法,参数依然是类型对象和sqlSession。这里呢,我们要先来看下这个MapperRegistry即所谓Mapper注册器是什么。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    ……
}

 

从这里我们可以知道其实啊这个MapperRegistry就是保持了一个Configuration对象和一个HashMap,而这个HashMap的key是类型对象,value呢是MapperProxyFactory。MapperRegistry对象是怎么来的,是在初始化Configuration对象时初始化了这个MapperRegistry对象的,代码大家可以去看。接下来我们继续看下这个MapperRegistry的getMapper方法:

 

 
@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中获取key为类型的MapperProxyFactory对象。然后调用MapperProxyFactory对象的newInstance方法返回指定类型的Mapper,newInstance方法传入sqlSession对象。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 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<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

       这里可以看到MapperpProxyFactory直接new了一个MapperProxy,然后调用了重载方法newInstance。这里最终是Proxy.newProxyInstance动态代理了我们的mapper对象。

       分析了这么多可以得出:通过sqlSesssion.getMapper(clazz)得到的Mapper对象是一个mapperProxy的代理类。

 

 

问题2:为什么调用mapper对象中的方法就能发出sql操作数据库?

       通过上面我们知道了Mapper是一个个MapperProxy的代理类,对于JDK的动态代理,MapperProxy接口就要实现InvocationHandler接口:

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

 

       当我们调用Mapper中接口定义的方法的时候,就会调用InvocationHandler的invoke方法:

@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);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  } 

 

       这里要关注的重点是MapperMethod:cachedMapperMethod返回MapperMethod对象,接着就执行这个MapperMethod对象的execute方法:

 

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

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }
    //…
}

       可以看到execute对数据库进行了CRUD的操作。我看下SELECT的情况:

单条查询的时候是使用了sqlSession.selectOne,多条查询是使用了sqlSession.selectList:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
 

 

总结      

到这里在结合上一篇文章我们就应该很好理解Mapper了:

通过SqlSession.getMapper会返回一个mapper的代理类MapperProxy,当调用mapper中的方法的时候,实际上是调用了代理类MapperProxy的invoke方法, 在invoke方法中会使用sqlSession进行数据库的操作,当然sqlSession并不直接操作数据库,而是由Executor和数据库打交道。

 

       通过本篇文章,你应该能理解为什么Mapper是接口,我们没有具体的实现也能执行的原因了吧?

 

我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。

 

à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

Spring Cloud视频:http://t.cn/A6ZagxSR

SpringBoot Shiro视频:http://t.cn/A6Zag7IV

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringData和JPA视频:http://t.cn/A6Zad1OH

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

Sharding-JDBC分库分表实战http://t.cn/A6ZarrqS

分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr

分享到:
评论

相关推荐

    基于Springboot+Mybatis+Layui的垃圾管理系统

    其中可能包含了Springboot应用的主类、Mybatis的Mapper接口和XML配置文件、业务逻辑服务类、以及使用Layui构建的前端界面HTML、CSS、JavaScript文件等。通过这些代码,系统实现了后台的增删改查操作,与前端界面的...

    基于mybatis的租房系统

    每个接口方法都对应一个XML中的SQL映射,通过MyBatis的自动代理机制,可以直接调用Mapper接口的方法执行SQL。 5. **MapperRegistry**:负责管理和注册Mapper,使得系统能够找到对应的Mapper XML文件,并将其与...

    基于Spring Boot+Layui+mybatis垃圾分类管理系统源码.zip

    5. `Mapper`接口和对应的XML文件:MyBatis的SQL映射。 **项目实现** 1. **用户模块**:用户注册、登录,可能有权限管理。 2. **垃圾分类模块**:定义不同类型的垃圾,如可回收物、有害垃圾等,以及对应的分类规则。...

    maven、spring、spring mvc、mybatis 整合实现ssm通用增删改查基础开发框架

    5. **配置MyBatis**:编写MyBatis的配置文件,`mybatis-config.xml`,设置数据库连接信息,引入Mapper XML文件的位置。 6. **编写实体类**:根据数据库表结构创建对应的Java实体类。 7. **编写Mapper接口**:定义...

    struts2,hibernate,mybatis验证码,分页

    1. **MyBatis配置**:在Mapper接口和XML文件中,定义分页查询的SQL语句,使用`LIMIT`和`OFFSET`(MySQL)或`ROWNUM`(Oracle)等关键字实现。 2. **传递参数**:Action接收来自前端的分页参数,如当前页数和每页记录...

    Javamybatis面试题.docx

    MyBatis 是一款著名的 Java 持久层框架,它专注于 SQL 查询的处理,使得开发者能够更加关注 SQL 语句的编写,同时提供了...同时,熟悉 Java 垃圾回收机制也是面试中的重要部分,因为这关系到程序的内存管理和性能优化。

    mybatis_jdk_proxy.zip

    总的来说,"mybatis_jdk_proxy.zip"可能包含的是关于如何在MyBatis中利用JDK Proxy实现Mapper接口动态代理的教程或示例代码。通过学习这部分内容,你可以掌握如何在MyBatis中优雅地处理数据库操作,并理解Java内存...

    Java和mybatis书籍.zip

    - **MyBatis入门**:安装配置、创建映射文件、编写Mapper接口、使用SqlSession等。 - **MyBatis高级**:深入理解动态SQL、缓存机制、MyBatis插件、ResultMap的使用等。 - **实战项目**:通过实际项目锻炼,将理论...

    Spring+SpringMVC+MyBatis

    开发者可以查看`web.xml`来了解SpringMVC的配置,` applicationContext.xml `来理解Spring的bean配置,以及MyBatis的配置文件`mybatis-config.xml`和映射文件`Mapper.xml`。此外,可能还有Java源码,包括SpringMVC的...

    CSDN最全面JavaEE面试题(Spring,SpringMVC,Hibernate,Mybatis)

    - 内存管理:了解堆栈内存分配,垃圾回收机制和JVM内存模型。 - 集合框架:ArrayList、LinkedList、HashMap、HashSet等数据结构的特性和使用场景。 - 多线程:线程的创建方式,同步机制(synchronized、Lock),...

    面向对象,JQuery,javascript,java基础,mybatis的接口开发的思维导图

    总的来说,这个思维导图涵盖了从基础的编程概念(面向对象、Java基础)到实际开发中常用的工具(JavaScript、jQuery)以及数据库操作框架(MyBatis的Mapper接口开发)。对于初学者或者希望巩固基础知识的开发者来说...

    基于SSM的垃圾分类回收平台.zip

    为了高效管理垃圾处理流程,一个基于SSM(Spring、SpringMVC、MyBatis)架构的垃圾分类回收平台应运而生。本篇文章将深入探讨该平台的核心技术和实现原理。 首先,我们来了解一下SSM框架。SSM是Java开发Web应用的...

    Java相关知识总结,包括Java基础、MySQL、Springboot、MyBatis、Redis、RabbitMQ等等

    MyBatis允许开发者直接编写SQL,提供动态SQL支持,并通过Mapper接口与Java对象映射。熟悉XML配置和注解方式,以及事务管理,可以帮助你有效地使用MyBatis进行数据操作。 五、Redis Redis是一个高性能的键值存储...

    1000道互联网java工程师面试题

    3. MyBatis框架的缺点:(1)学习成本高、需要掌握SQL语句和Mapper文件配置、需要在Mapper文件中编写SQL语句等。 4. MyBatis框架适用场合:(1)大型项目中需要复杂的数据库交互、需要高效的数据库访问、需要灵活的...

    JSP基于SSM垃圾分类查询管理系统设计毕业源码案例设计.zip

    3. **Dao**:数据访问层,通过MyBatis的Mapper接口与数据库进行通信。 4. **Controller**:控制器层,处理HTTP请求,调用Service并转发结果到视图层(JSP页面)。 5. **View**:视图层,JSP页面展示数据,如查询结果...

    【ssm管理系统】基于SSM的垃圾分类回收平台.zip

    在本项目中,MyBatis可能被用来操作数据库,例如存储和检索垃圾分类的相关信息,如垃圾类型、回收时间、重量等。开发者可以通过编写Mapper接口和XML配置文件,定义SQL查询和更新操作。 关于SQL,这是用于管理关系...

    ssm智能垃圾分类系统9377a.zip

    通过Mapper接口,开发者可以便捷地执行SQL查询和更新操作。MyBatis与Spring结合使用时,可以实现自动事务管理。 4. **数据库设计**:智能垃圾分类系统可能需要存储垃圾种类、分类规则、用户信息、分类历史等数据。...

    基于Java SSM MySQL实现垃圾分类知识在线考试系统【优质毕业设计、课程设计项目】.zip

    MyBatis通过Mapper接口和XML配置文件,将SQL与Java对象进行绑定,实现了数据的增删查改操作。 在数据库设计方面,本项目可能包含关于垃圾分类的知识库表,如垃圾类型、分类规则、题目库等。MySQL作为关系型数据库,...

    Java的MyBatis框架中XML映射缓存的使用教程

    例如,可以设置`eviction`属性来改变回收策略,如`FIFO`(先进先出)、`SOFT`(基于垃圾回收器状态和软引用)、`WEAK`(基于垃圾收集器状态和弱引用),默认是`LRU`。`flushInterval`属性可以设置刷新间隔,如`60000...

    基于SSM的垃圾分类回收平台(含数据库文件).zip

    - **MyBatis**:与数据库交互,通过Mapper接口动态生成SQL,保持代码简洁。 - **数据库设计**:可能包含用户表、垃圾类型表、回收站表、预约表等,涉及关系模型设计和优化。 - **前端技术**:可能使用HTML5、CSS3...

Global site tag (gtag.js) - Google Analytics