MyBatis是如何让我们通过接口就能调用到SQL的

2021-02-13 21:55:06 780

大致可分为如下几个步骤

1. 动态注册bean

1.1 根据配置mapperScan, 扫描对应的包, 将对应的类解析成BeanDefinition
1.2 通过替换BeanDefinition中的BeanClass为MapperFactoryBean, (原来的BeanClass是Mapper接口) 实现了在spring生成对应的对象时, 返回的对象不是本身类型的对象,而是MapperFactoryBean重写FactoryBean接口的getObject()方法返回的代理对象。该方法getObject()已经对mapper接口进行了代理, 即后续进行自动注入时, 也是返回getObject()生成的代理对象

2. 生成对应的代理对象

2.1 在getObject()方法中, 会获取到接口的全限定名称, 然后进一步对代理方法进行封装, 调用链如下
   MapperFactoryBean: 
   	public T getObject() throws Exception {
   		return getSqlSession().getMapper(this.mapperInterface);
   	}
   
   DefaultSqlSession: 
       public <T> T getMapper(Class<T> type) {
       	//configuration是mybatis的重要配置类, 在初始化的时候, 就会将mapper接口添加到configuration中
       	return configuration.getMapper(type, this);
       }
   
   Configuration: 
   	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   		return mapperRegistry.getMapper(type, sqlSession);
   	}
   
   MapperRegistry: 
   	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);
   		}
   	}
   
   MapperProxyFactory: 
   	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);
   	}
2.2 在org.apache.ibatis.binding.MapperProxy#cachedInvoker中, new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())), 并且将代理方法调用器缓存起来. MapperMethod该对象即是最终调用方法的对象.

3. 执行对应的方法

3.1 方法调用, 代理里最常见的方法invoke
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    	//过滤掉object的方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
3.2. org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker重写的invoke方法中,判断该方法是否被调用过(是否存在于缓存), 若没有, 则创建一个PlainMethodInvoker方法调用器, 传入MapperMethod(MapperMethod是真正执行方法的对象), 并将新创建的PlainMethodInvoker存入缓存中(methodCache), 并调用该PlainMethodInvoker的invoke方法,
3.3. xml中的id和select等标签封装成了SqlCommand, 调用mapperMethod的execute, 执行对应的增删改查.

4. 结果集封装, 进行一些数据库数据对应java对象的转换

通过mybatis的封装和代理, 将mapper.xml转换成了接口的实例对象

如有谬误, 欢迎斧正

简化版如下: https://blog.csdn.net/sinat_25991865/article/details/89891581

public interface UserMapper {
	List<SysUser> selectAll();
}
public class MyMapperProxy<T> implements InvocationHandler {
	private Class<T> mapperInterface;
	private SqlSession sqlSession;
	
	public MyMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
			this.mapperInterface = mapperInterface;
			this.sqlSession = sqlSession;
	}

	@Override
	public Object invoke(Object proxy , Method method , Object[] args)
			throws Throwable {
		//针对不同的 sql 类型,需要调用sqlSession不同的方法
		//接口方法中的参数也有很多情况 ,这里只考虑没有有参数的情况
		List<T> list= sqlSession.selectList(
				mapperInterface.getCanonicalName() + ”.” + method.getName());
		//返回数据也有很多情况,这里不做处理直接返回
		return list;
	}
}

方法调用

//获取sqlSession
SqlSession sqlSession = getSqlSession();
//获取 UserMapper 接口
MyMapperProxy userMapperProxy = new MyMapperProxy(
		UserMapper.class , sqlSession) ;
UserMapper userMapper = (UserMapper) Proxy.newProxyinstance (
		Thread.currentThread().getContextClassLoader(),
		new Class[ ] {UserMapper.class},
		userMapperProxy) ;
//调 用 selectAll 方 法
List<SysUser> user= userMapper.selectAll();



MyBatis处理一对多关系时的性能考虑

Mybatis框架对于处理一对多的情况有两种方法查询的时候JOIN子表, 然后交给MyBatis拼装数据查询的时候不JOIN子表, 主表查询完成后发起select再查询关联表数据, 还可以配置fetchType=lazy进行懒加载这两种方法各有问题:第一种方案有两个缺陷: 1) 做分页查询的时候不准
2022-02-11

模拟Mybatis"无实现类"调用接口

Mybatis让我们通过接口(interface)就能调用到对应sql, 看起来好像没有实现类, 貌似不符合"常识"通过这个简易demo, 带你大致了解mybatis背后所做的事先建立maven工程 <parent> <groupId>org.springframework.b
2021-02-21

MyBatis是如何让我们通过接口就能调用到SQL的

大致可分为如下几个步骤1. 动态注册bean1.1 根据配置mapperScan, 扫描对应的包, 将对应的类解析成BeanDefinition1.2 通过替换BeanDefinition中的BeanClass为MapperFactoryBean, (原来的BeanClass是Mapper接口) 实
2021-02-13

MyBatis拦截器实现SQL打印

mybatis有自带的sql打印, 但只会出现在抛异常的时候, 或者配置日志输出, 但是输出的日志较为冗长像这样### Error querying database. Cause: java.lang.ArithmeticException: / by zero ### The error ma
2021-02-08
MyBatis拦截器执行顺序

MyBatis拦截器执行顺序

最近项目用上了mybatis, 但是想像hibernate那样能打印sql, 于是写了个基于mybatis拦截器的sql打印, 参考这个https://blog.22xcode.com/post/78然后, 碰到了问题, 拦截器会重复输出一句sqlmybatis sql: SELECT id, na
2021-02-08

freemarker 时间显示不正常 设置时区

项目在本地开发的时候显示正常,部署上服务器就一直差8个小时,最后发现freemarker官方文档有这样的说明time_zone:时区的名称来显示并格式化时间。 默认情况下,使用JVM的时区。 也可以是 Java 时区 API 接受的值,或者 "JVM default" (从 FreeMarker 2
2020-03-28
IDEA 2019.1 xml 不高亮

IDEA 2019.1 xml 不高亮

前几天更新了idea后,发现xml里的代码都没有了高亮,变得跟记事本一个德性了打开setting ,搜索 File Types,找到xml项, 查看下方的匹配格式,果然没有xml,(idea真是厉害)点击右方的+,输入*.xml,点击ok,解决问题
2020-03-28

npm install 淘宝镜像

npm install --registry=https://registry.npm.taobao.org
2020-03-28
Java中方法的参数传递机制

Java中方法的参数传递机制

来看一段代码 public class Man { private String name; private Integer age; public String getName() { return name; } publi
2020-03-28
基于自定义注解手写权限控制

基于自定义注解手写权限控制

方法一: AOP 方法二: 拦截器项目结构项目依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-w
2020-03-28

Docker 部署 详细全过程 附代码

Docker 部署本站 全过程环境:CentOS7.61. 安装Docker其他版本CentOS可以参考这个https://help.aliyun.com/document_detail/187598.html查看本机内核版本,内核版本需高于 3.10uname -r 确保 yum 包最新yum u
2020-03-28

SpringBoot 启动普通java工程

引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.9</version> </dependency>
2020-03-28

Vue.js DOM操作

<template> <input type="button" @click="reply($event)" value="回复"> </template> export default { methods: { replyFun(e) {
2020-03-29
CentOS7编译调试OpenJDK12

CentOS7编译调试OpenJDK12

1. 下载源码https://hg.openjdk.java.net/jdk/jdk12点击左侧的browse,再点击zip,就可以下载zip格式的源码压缩包。unzip xxx.zip 解压文件2. 安装jdkyum install java-11-openjdk-devel -y3. 运行con
2020-04-23
编写自己的Spring Boot Starter

编写自己的Spring Boot Starter

1.新建一个maven项目命名规则统一是xxx-spring-boot-starter完整pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"
2020-06-29