IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> MyBatis中Mapper的动态代理模式(深入源码+言简意赅) -> 正文阅读

[Java知识库]MyBatis中Mapper的动态代理模式(深入源码+言简意赅)

MyBatis中Mapper的动态代理模式

0 疑问

在 Mybatis 中,我们只需要定义 ?Mapper接口??该Mapper接口所对应的XML配置文件?,就可以完成对数据库的 “增删查改” 操作,获取结果集。但是,从始至终我们都没有定义 Mapper 接口的实现类,且 Java 中也不允许使用接口来创建实例对象,那么当我们来调用接口中的方法时,到底是由谁来执行这个方法呢?

  • 答案就是 Mybatis 借助设计模式中的 ?动态代理模式? 帮助我们在底层创建了 Mapper 接口的实现类,从而调用该实现类的方法完成了我们所需要的操作;
  • 对设计模式中的 ?代理模式? 不熟悉的小伙伴可以看我的另一篇 CSDN 博客:设计模式之代理模式
  • 在此过程中,主要涉及三个至关重要的类:MapperRegistryMapperProxyMapperMethod

1 示例

我们以下面的代码为例,深入源码且言简意赅地说明 Mybatis 中的 Mapper 动态代理的实现过程,其中重点是第 5、6 点。

  @Test
  public void test() throws IOException {
      //1.读取MyBatis的核心配置文件
      InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

      //2.创建SqlSessionFactoryBuilder对象
      SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

      //3.通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
      SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

      //4.创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
      SqlSession sqlSession = sqlSessionFactory.openSession(true);

      //5.通过动态代理模式创建UserMapper接口的代理实现类对象
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

      //6.调用UserMapper接口中的方法
      User user = userMapper.getUserById();
      System.out.println(user.toString());
  }

2 实现过程

2.1 代理对象的生成

  1. 当执行到示例代码的第 5 点时,Mybatis 在底层借助 MapperRegistry 类生成 UserMapper 接口的具体实现类,即 userMapper 对象的代理对象。下面是 MapperRegistry 类的源码,其中最重要的是 getMapper() 方法,下面对其进行详细解释;
public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //生成代理对象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
        return this.knownMappers.containsKey(type);
    }

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(this.knownMappers.keySet());
    }

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        Iterator var5 = mapperSet.iterator();

        while(var5.hasNext()) {
            Class<?> mapperClass = (Class)var5.next();
            this.addMapper(mapperClass);
        }

    }

    public void addMappers(String packageName) {
        this.addMappers(packageName, Object.class);
    }
}
  1. 从源码中我们可以看到,MapperRegistry 中使用一个 Map 容器 knownMappers 存储已经注册过的 Mapper 接口,该容器的 key 值为 Mapper 接口,value 值为可以生成该 Mapper 接口具体实现类(代理对象)的 MapperProxyFactory 工厂,如下图所示;
    在这里插入图片描述
  2. 所以,代理对象的生成过程可以描述为:我们首先调用 SqlSession 对象的 getMapper() 方法(传入 Mapper 接口作为参数)。然后,Mybatis 底层实际去执行 MapperRegistry 类的 getMapper() 方法,在该类的 knownMappers 容器中以我们传入的 Mapper 接口为 key 值找到可以生成该 Mapper 接口所对应的代理对象的 MapperProxyFactory 工厂。最后,MapperProxyFactory 工厂通过 newInstance() 方法生成 Mapper 接口的代理对象 MapperProxy 类实例,至此进入下一阶段。

2.2 Mapper接口方法的执行

  1. 在第 5 点中的步骤执行完毕之后,我们成功获得了 UserMapper 接口的代理对象,下面解释在第 6 点的步骤中如何执行 Mapper 接口的 getUserById() 方法。首先是由 UserMapper 接口的代理对象 MapperProxy 类实例在其内部通过反射机制调用 invoke() 方法来生成 getUserById() 方法所对应的 MapperMethod 类实例对象,由 MapperMethod 类实例作为执行者完成我们的数据库操作;
/*
 * 代码有删减,只截取核心代码
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {
    //生成getUserById()方法对应的MapperMethod类实例对象
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? 
            method.invoke(this, args) : 
            this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

    private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
        private final MapperMethod mapperMethod;

        public PlainMethodInvoker(MapperMethod mapperMethod) {
            this.mapperMethod = mapperMethod;
        }

        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            //调用MapperMethod的execute()方法完成实际的数据库操作
            return this.mapperMethod.execute(sqlSession, args);
        }
    }
}
  1. 然后,getUserById() 方法所对应的 MapperMethod 类实例对象执行自己的 execute() 方法,解析 UserMapper 接口的 getUserById() 方法,并找到执行对应的 SQL 语句的方法(在 switch…case…中查找)。最终,getUserById() 方法会对应到 “SELECT” 中的 executeForMany() 方法,该方法与数据库进行交互,查询到我们所需要的数据,并返回结果,结束整个过程。
/*
 * 代码有删减,只截取核心代码
 */
public class MapperMethod {
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-07 11:02:36  更:2022-05-07 11:04:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:11:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码