zoukankan      html  css  js  c++  java
  • 通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis核心原理

    本文将通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis原理

    1.平常我们是如何使用Mapper的

    先写一个简单的UserMapper,它包含一个全表查询的方法,代码如下

    public interface UserMapper {
    
        @Select("select * from user")
        public List<User> queryAll();
    }
    

    然后大家思考一个问题,我们平时是怎么使用这个UserMapper的?

    很多时候我们会把Mybatis和Spring整合起来一起使用,于是会有类似下面的代码:

    @Service
    public class UserServiceImpl {
    
        @Autowired
        private UserMapper userMapper;
    
        public List<User> queryAll(){
            return this.userMapper.queryAll();
        }
    }
    

    看到这段熟得不能再熟的代码不知道大家会不会有一丝疑惑:UserMapper明明是一个接口,为什么可以直接调用他的queryAll方法呢?

    这个问题其实也不难解,我们不能直接调用一个接口的方法,这背后肯定是有一个对象的,至于这个对象是怎么来的,这里直接告诉大家是通过动态代理生成的。只要弄懂了这个动态代理对象是怎么生成的,整个Mybatis框架原理就就说清楚了。在模拟之前我们先验证一下是不是使用JDK的动态代理。

    2.验证Mybatis是通过动态代理生成Mapper代理对象

    由于上面一段代码整合了spring,spring又为我们封装了许多细节,我们重新看一段代码,看看没有spring的情况下我们怎么获得一个UserMapper

    public static void main(String[] args){
        
        SqlSessionFactory sqlSessionFactory = ....  //这里省略,官网给了很多配置SqlSessionFactory的方法(不一定是这么获得)
    
        SqlSession session = sqlSessionFactory.openSession();
    
        UserMapper userMapper = session.getMapper(UserMapper.class);
    }
    

    为了验证获得UserMapper采用的是动态代理,我们可以在IDE中对着session.getMapper(UserMapper.class)一路按着Ctrl点进去,我们会发现最终调用的代码是这样的:

      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    

    最后果然调用的是JDK的动态代理

    3.动手模拟一次Mybatis的动态代理

    既然知道了原理,下面我们就动手验证吧!

    为了简单明了,我们不写SqlSessionFactory类了,直接自定义一个MySession类,在里面给出模拟的getMapper方法:

    public class MySession {
        public static Object getMapper(Class clazz){
            //调用newProxyInstance需要传入class数组
            Class[] clazzs = new Class[]{clazz};
            //把动态代理过程中生成的代理类保留下来,有助于新手理解动态代理(此行可省略)
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            //通过动态代理生成Mapper
            Object object = Proxy.newProxyInstance(MySession.class.getClassLoader(), clazzs, new MyInvocationHandler());
            return object;
        }
    }
    

    如果不熟悉JDK的动态代理也没关系,下面我会逐步分析。

    大家可以看到调用动态代理生成动态代理对象需要三个参数:

    1. 类加载器
    2. class数组
    3. InvocationHandler实例

    为什么需要类加载器?

    动态代理生成类和其调用类必须通过同一个类加载器加载,否则它们之间无法相互调用

    为什么有个class数组?

    JDK的动态代理是基于接口的,class数组中存放的是动态代理类需要实现的接口。比如本文中的例子生成的动态代理类需要实现UserMapper接口,所以你得把接口告诉它。

    为什么会有InvocationHandler实例?

    动态代理会在原有方法上实现增强,而增强的逻辑就写在InvocationHandler类的invoke方法上,所以要有这么个实例。

    你想一想,当初的这段代码

    public interface UserMapper {
    
        @Select("select * from user")
        public List<User> queryAll();
    }
    

    ,我们想实现一个怎样的功能?

    无非就是给它一条sql语句,希望它能去数据库中执行这条sql语句并返回结果。这个过程可以拆分成两个部分:

    1. 得到sql语句
    2. 通过JDBC操作数据库,并执行sql返回结果

    这里省略JDBC的过程,给出一个简单的invoke方法示例(Mybatis为我们封装了一切JDBC的处理细节):

    public class MyInvocationHandler implements InvocationHandler {
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //解析得到sql
            Select annotation = method.getAnnotation(Select.class);
            String sql = annotation.value()[0];
    
            //执行sql(模拟JDBC)
            System.out.println(sql + " executing...");
            return null;
        }
    }
    

    最终我们执行UserMapper的queryAll()方法时,就会出现如下结果:

    public class Main {
        public static void main(String[] args) {
            UserMapper userMapper = (UserMapper) MySession.getMapper(UserMapper.class);
            userMapper.query();
        }
    }
    
    //打印:
    //select * from user executing...
    

    总结

    最后我们总结一下Mybatis框架的核心原理:

    1. 用户只需要创建Mapper接口,并使用Mapper接口即可。
    2. Mybatis会对Mapper接口产生动态代理对象,这个动态代理对象实现了Mapper接口,拥有Mapper中定义的所有方法,并对这些方法进行了增强。增强的逻辑是获得sql语句和执行sql语句。

    通过个核心原理我们也就知道了Mybatis为我们做了什么:

    1. 让方法和sql语句对应起来,操作数据库就如同调用方法一般简单
    2. 屏蔽掉JDBC的细节
  • 相关阅读:
    SpringCloud教程第10篇:高可用的服务注册中心(F版本)
    SpringCloud教程第9篇:Sleuth(F版本)
    requests.session保持会话
    Jmeter Constant Throughput Timer 使用
    Jmeter提取响应数据的结果保存到本地的一个文件
    练习2
    练习1
    一道简单的练习题
    Maven下org.junit.Test无法使用
    [转]解决pycharm无法导入本地包的问题(Unresolved reference 'tutorial')
  • 原文地址:https://www.cnblogs.com/tanshaoshenghao/p/12202986.html
Copyright © 2011-2022 走看看