首页 > 其他分享 >mybatis mapper 底层原理

mybatis mapper 底层原理

时间:2023-07-26 17:55:27浏览次数:39  
标签:mapper apache sqlSession mybatis org configuration type method 底层

使用 SqlSession 的接口查询比较麻烦,MappedStatement 的 id 也是字符串容易出错,也不符合面向接口的编程方式。所以 mybatis 也支持使用 mapper 接口的方法来简化操作

初始化

前面分析初始化过程的时候有说到 MappedStatement 的维护,这一步是在解析映射文件的时候完成的,mapper 的注册也是在这一步,看下关键源码:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 就会走这里了
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else { 
                
                ...
                
            }
        }
    }
}

@MapperScan 注解会扫描所有的 mapper 接口文件,是根据配置的包名来扫描的,关键方法就是 addMappers(),看下这个方法:

// org.apache.ibatis.session.Configuration#addMapper
public void addMappers(String packageName, Class<?> superType) {
    // 维护 configuration.mapperRegistry
	mapperRegistry.addMappers(packageName, superType);
}

// org.apache.ibatis.binding.MapperRegistry#addMappers
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        // 调用了 addMapper 方法
    	addMapper(mapperClass);
    }
}

// org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
    	if (hasMapper(type)) {
    		throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    	}
    	boolean loadCompleted = false;
    	try {
            // 维护 configuration.mapperRegistry.knownMappers 属性(创建代理对象的包装工厂)
            // SqlSession.getMapper(type) 获取一个包装工厂发,再根据包装工厂生成代理对象
    		knownMappers.put(type, new MapperProxyFactory<>(type));
    		MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // 主要是这里
    		parser.parse();
    		loadCompleted = true;
    	} finally {
    		if (!loadCompleted) {
      			knownMappers.remove(type);
    		}
    	}
    }
}

parser.parse() 做了什么

// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse  
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 这个方法会读取映射文件,维护 configuration.mappedStatements,原理和前面分析的一样
    	loadXmlResource();
    	configuration.addLoadedResource(resource);
    	assistant.setCurrentNamespace(type.getName());
    	parseCache();
    	parseCacheRef();
        // 遍历 mapper 接口中的方法,type 就是接口的 class
    	for (Method method : type.getMethods()) {
    		if (!canHaveStatement(method)) {
    			continue;
    		}
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() 
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                parseStatement(method);
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
    	}
    }
    parsePendingMethods();
}

至此 configuration.mappedStatements、configuration.mapperRegistry 都维护好了

工作流程

先看 SqlSession.getMapper(xxxMapper.class) 底层

// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
	return configuration.<T>getMapper(type, this);
}

// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	return mapperRegistry.getMapper(type, sqlSession);
}

// org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取 mapper 接口的代理工厂
    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);
    }
}

怎么生成代理对象?

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
    // 拿到工厂对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 调用下面的方法生成代理对象
    return newInstance(mapperProxy);
}

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

对象生成好了,就是调用方法了,其实调用的是代理对象的方法,显而易见会通过反射执行 invoke 方法

// org.apache.ibatis.binding.MapperProxy#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 先判断执行的方法是不是 Object 类的方法,比如tostring,hashcode 等方法,如果是,直接执行
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            // 判断执行的方法是否为默认方法(jdk8的新东西),如果是则直接执行反射执行
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
	// 利用接口、方法等信息,构造 mapperMethod 并进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 最终通过 MapperMethod 代理执行具体的数据库操作
    return mapperMethod.execute(sqlSession, args);
}

再看下 execute 方法源码:

// org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;	// 返回结果
    if (SqlCommandType.INSERT == command.getType()) { // INSERT 操作
      // 处理参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 调用 sqlSession 的 insert 方法 
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) { // UPDATE 操作 
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) { // DELETE 操作
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) { // SELECT 操作
      // 如果返回 void 并且参数有 resultHandler,调用 sqlSession.select() 
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        // 如果返回不是 void,是多个结果,就调用 sqlSession.selectList()  
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        // 如果返回类型是 MAP 则调用 executeForMap 方法 
        result = executeForMap(sqlSession, args);
      } else {
        // 否则就是查询单个对象
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
        // 接口方法没有和sql命令绑定
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // 如果返回值为空 并且方法返回值类型是基础类型 并且不是 VOID 则抛出异常  
    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;
}

可以看到最终还是根据 SqlSession 来操作的,感觉是饶了一下,SqlSession 可以完成的工作,多饶了一圈 mapper,最终还是使用 SqlSession,SqlSession 的工作原理前面分析过,这里就不分析了

总结

  1. 根据配置扫描 mapper 所在的包,根据 configuration.addMappers() 方法来维护 configuration.mappedStatements、configuration.mapperRegistry 属性
    1. mappedStatements 就是 MappedStatement 对象集合
    2. mapperRegistry 属性里面维护了一个 knownMappers 属性,knownMappers 是个 map,key 是 接口,value 是创建接口代理对象的工厂对象
  2. sqlSession.getMapper() 就是获取 configuration.mapperRegistry.knownMappers 的值,然后创建一个代理对象
  3. mapper.method() 就是执行代理对象的方法,底层还是调用了 sqlSession 的方法
  4. sqlSession 又到 configuration.mappedStatements 找到具体的 MappedStatement
  5. 然后又是 Executor 根据 MappedStatement 查询数据(缓存、处理参数、创建连接、映射结果)

标签:mapper,apache,sqlSession,mybatis,org,configuration,type,method,底层
From: https://www.cnblogs.com/hangychn/p/17583194.html

相关文章

  • @mapper(componentModel = “spring”)
    在接口上使用该注解,可以自动生成该接口的实现类.实现DTO-DO各种模型之间的字段映射(不仅仅限制于DTO-DO)https://blog.csdn.net/qq_36937844/article/details/126848404......
  • Mybatis数据库模型-代码生成器
    pom文件添加<dependencies><!--SpringBoot整合MyBatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3&l......
  • mybatisplus入门
    1. MybatisPlus的介绍MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。愿景我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。2.......
  • mybatis批量保存
    <insertid="saveBatchForList"parameterType="java.util.List">insertintoT_PRICE_PPI_BASEWEIGHT(BASEYEAR,ORGCODE,ORGNAME,BASECODE,BASENAME,WEIGHT,DATA_TYPE,STARTTIME,CREATOR,CREATETIME,AREACODE,AREANAME,GROUP_......
  • 转:求求你们了,MyBatis 批量插入别再乱用 foreach 了,5000 条数据花了 14 分钟。。
    转:求求你们了,MyBatis批量插入别再乱用foreach了,5000条数据花了14分钟。。MyBatis批量插入的五种方式,哪种最强??? Mysql调整Mybatis批量插入数量限制Mysql对语句的长度有限制,默认是4M。Mybatis对动态语句没有数量上的限制。   ......
  • docker底层实现
    目录1、基本架构2、名字空间2.1pid名字空间2.2net名字空间2.3ipc名字空间2.4mnt名字空间2.5uts名字空间2.6user名字空间3、控制组4、联合文件系统5、容器格式6、Docker网络实现6.1基本原理6.2创建网络参数6.3网络配置细节(1)基本架构(2)名字空间•pid名字空间......
  • 注解最后一篇利用注解整合mybatis
    步骤:1.在pom中添加依赖,下方spring-jdbc中的版本号需要和最开始添加依赖的版本一致2.在config中创建配置类,在配置类里面利用@bean创建方法返回数据库信息,同时扫描包,将包放入IOC容器中 总结:整合Batista就做两件事:1.创建会话工厂  2.生成目标包(dao层/mapper层)下所有有接口的......
  • MyBatis-Plus数据权限控制
    平时开发中遇到根据当前用户的角色,只能查看数据权限范围的数据需求。列表实现方案有两种,一是在开发初期就做好判断赛选,但如果这个需求是中途加的,或不希望每个接口都加一遍,就可以方案二加拦截器的方式。在mybatis执行sql前修改语句,限定where范围。当然拦截器生效后是全局性的,如何......
  • mybaties --- insert的底层封装代码
    //提交,当前的对象到数据库//.save()方法是IService接口提供的,而EmployeeService接口继承了IService接口employeeService.save(employee);/*defaultbooleansave(Tentity){returnSqlHelper.retBool(this.getBaseMapper().insert(entity));}这段代码是一个通用的保存方法......
  • MyBatis-Plus这样实现动态SQL
    拦截器介绍拦截器是一种基于AOP(面向切面编程)的技术,它可以在目标对象的方法执行前后插入自定义的逻辑。MyBatis定义了四种类型的拦截器,分别是:Executor:拦截执行器的方法,例如update、query、commit、rollback等。可以用来实现缓存、事务、分页等功能。ParameterHandler:拦截参......