zoukankan      html  css  js  c++  java
  • MyBatis-接口实现数据库操作的原理

    参考:

    https://blog.csdn.net/luoposhushengyizhuce/article/details/83902433

    https://www.cnblogs.com/williamjie/p/11188355.html

    https://www.cnblogs.com/williamjie/p/11188346.html

    mybatis如何通过只需要配置接口就能实现数据库的操作

    在用mybatis的时候,我们只需要写一个接口,然后服务层就能调用,在我们的认知中,是不能直接调接口的方法的,这个其中的原理是什么呢?由于自己比较好奇,就取翻了一下mybatis的源码,一下是做的一些记录。
    通过一个最简单的例子来揭开它的面目。

    @Test
    public void testDogSelect() throws IOException {
        String resource = "allconfig.xml";// ①
        InputStream inputStream = Resources.getResourceAsStream(resource);// ②
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// ③
        SqlSession session = sqlSessionFactory.openSession();// ④
        DogMapper dogMapper = session.getMapper(DogMapper.class);// ⑤
        List<Dog> dogs = dogMapper.selectDog();//⑥
        System.out.println(dogs.size());// ⑦
    }


    首先就是①②两行是没什么要解释的,就从第三行开始,我们追踪代码,进入SqlSessionFactoryBuilder的build(InputStream)的方法
    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }
    
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }


    重要代码就两行,其中XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);是构建解析器用的,主要看下一行build(parser.parse()),其中parser.parse()主要就是把我们的mybatis的配置文件进行解析,并把解析的大部分内容保存到Configuration中。然后看build的代码

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }


    返回了一个DefaultSqlSessionFactory
    继续看④这一句,我们知道sqlSessionFactory实际上是DefaultSqlSessionFactory的一个实例,进入DefaultSqlSessionFactory的openSession()方法

    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            final Executor executor = configuration.newExecutor(tx, execType);
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }


    其他我们都先不管,我们只要看到这个方法返回的是一个DefaultSqlSession的实例就好
    我们继续看⑤这一行,session是DefaultSqlSession的一个实例。我们进入DefaultSqlSession的getMapper(Class type)方法,

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


    其中configuration是Configuration的实例,在解析mybatis的配置文件的时候进行的初始化。继续追进去

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


    继续进到MapperRegistry的getMapper(Class type, SqlSession sqlSession)方法

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


    此方法根据传进来的type生成对应的代理,我们进入看看MapperProxyFactory

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }


    到这里已经看到已经完成代理的生成,MapperProxyFactory是个工厂。再继续看MapperProxy这个类
    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;
    
        public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
            this.sqlSession = sqlSession;
            this.mapperInterface = mapperInterface;
            this.methodCache = methodCache;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
            }
            final MapperMethod mapperMethod = cachedMapperMethod(method);
            return mapperMethod.execute(sqlSession, args);
        }
    
        private MapperMethod cachedMapperMethod(Method method) {
            MapperMethod mapperMethod = methodCache.get(method);
            if (mapperMethod == null) {
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
            }
            return mapperMethod;
        }
    
    }

    当我们通过生成的对象调用方法的时候,都会进入这个类的invoke方法方法,我看看到如果调用的是我们自定的方法,直接就是调用的mybatis的实现,通过接口找到配置信息,然后根据我们的配置去操作数据库。

    最后总结一下,mybatis之所以配置接口以后就能执行是因为在生产mapper的时候实质上是生成的一个代理,然后通过mapper调用接口方法的时候直接被MapperProxy的invoke截断了,直接去调用了mybatis为我们制定的实现,而没有去回调。

    MyBatis你只写了接口为啥就能执行SQL啊?

    一、静态代理

    又是一年秋招季,很多小伙伴开始去大城市打拼。来大城市第一件事就是租房,免不了和中介打交道,因为很多房东很忙,你根本找不到他。从这个场景中就可以抽象出来代理模式:

    • ISubject:被访问者资源的抽象

    • SubjectImpl:被访问者具体实现类(房东)

    • SubjectProxy:被访问者的代理实现类(中介)

    UML图如下:

    举个例子来理解一下这个设计模式:

    老板让记录一下用户服务的响应时间,用代理模式来实现这个功能。

    一切看起来都非常的美好,老板又发话了,把产品服务的响应时间也记录一下吧。又得写如下3个类:

    • IProductService

    • ProductServiceImpl

    • ProductServiceProxy

    UserServiceProxy和ProductServiceProxy这两个代理类的逻辑都差不多,却还得写2次。其实这个还好,如果老板说,把现有系统的几十个服务的响应时间都记录一下吧,你是不是要疯了?这得写多少代理类啊?

    二、动态代理

    黑暗总是暂时的,终究会迎来黎明,在JDK1.3之后引入了一种称之为动态代理(Dynamic Proxy)的机制。使用该机制,我们可以为指定的接口在系统运行期间动态地生成代理对象,从而帮助我们走出最初使用静态代理实现AOP的窘境

    动态代理的实现主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

    让我们用动态代理来改造一下上面记录系统响应时间的功能。虽然要为IUserService和IProductService两种服务提供代理对象,但因为代理对象中要添加的横切逻辑是一样的。所以我们只需要实现一个InvocationHandler就可以了。代码如下

    UML图如下。恭喜你,你现在已经理解了Spring AOP是怎么回事了,就是这么简单,今天先不展开谈Spring

    先简单谈谈动态代理在Mybatis中是如何被大佬玩的出神入化的

    三、Mybatis核心设计思路

    相信用过mybatis的小伙伴都能理解下面这段代码,通过roleMapper这个接口直接从数据库中拿到一个对象

    Role role = roleMapper.getRole(3L);

    直觉告诉我,一个接口是不能运行的啊,一定有接口的实现类,可是这个实现类我自己没写啊,难道mybatis帮我们生成了?你猜的没错,mybatis利用动态代理帮我们生成了接口的实现类,这个类就是:

    org.apache.ibatis.binding.MapperProxy,

    我先画一下UML图,MapperProxy就是下图中的SubjectProxy类

    和上面的UML类图对比一下,发现不就少了一个SubjectImpl类吗?那应该就是SubjectProxy类把SubjectImple类要做的事情做了呗,猜对了。SubjectProxy通过SubjectImple和SubjectImple.xml之间的映射关系知道自己应该执行什么SQL。所以mybatis最核心的思路就是这么个意思,细节之类的可以看源码,理清最主要的思路,看源码就能把握住重点。

    关于源码相关的内容,更进一步的解释动态代理在MyBatis中的使用,可以参考以前的一篇文章:《动态代理之投鞭断流!看一下MyBatis的底层实现原理!

    附录:

    https://mp.weixin.qq.com/s/ToMvAD0-QyUJgg_1eFvcaA

     
     
     
     
     
     
     
     
     

    动态代理之投鞭断流!看一下MyBatis的底层实现原理

    转:https://mp.weixin.qq.com/s?__biz=MzI1NDQ3MjQxNA==&mid=2247486856&idx=1&sn=d430be5d14d159fd36b733c83369d59a&chksm=e9c5f439deb27d2f60b69d7f09b240eb43a8b1de2d07f7511e1f1fecdf9d49df1cb7bc6e1ab5&scene=21#wechat_redirect

    一日小区漫步,我问朋友:Mybatis中声明一个interface接口,没有编写任何实现类,Mybatis就能返回接口实例,并调用接口方法返回数据库数据,你知道为什么不?朋友很是诧异:是啊,我也很纳闷,我们领导告诉我们按照这个模式编写就好了,我同事也感觉很奇怪,虽然我不知道具体是怎么实现的,但我觉得肯定是……(此处略去若干的漫天猜想),但是也不对啊,难道是……(再次略去若干似懂非懂)。

    这激发了我写本篇文章的冲动。


    动态代理的功能:通过拦截器方法回调,对目标target方法进行增强。

    言外之意就是为了增强目标target方法。上面这句话没错,但也不要认为它就是真理,殊不知,动态代理还有投鞭断流的霸权,连目标target都不要的科幻模式。

    注:本文默认认为,读者对动态代理的原理是理解的,如果不明白target的含义,难以看懂本篇文章,建议先理解动态代理。

    一、自定义JDK动态代理之投鞭断流实现自动映射器Mapper

    首先定义一个pojo

    再定义一个接口UserMapper.java

    接下来我们看看如何使用动态代理之投鞭断流,实现实例化接口并调用接口方法返回数据的。

    自定义一个InvocationHandler。

    上面代码中的target,在执行Object.java内的方法时,target被指向了this,target已经变成了傀儡、象征、占位符。在投鞭断流式的拦截时,已经没有了target。

    写一个测试代码:

    output:

    这便是Mybatis自动映射器Mapper的底层实现原理。

    可能有读者不禁要问:你怎么把代码写的像初学者写的一样?没有结构,且缺乏美感。

    必须声明,作为一名经验老道的高手,能把程序写的像初学者写的一样,那必定是高手中的高手。这样可以让初学者感觉到亲切,舒服,符合自己的Style,让他们或她们,感觉到大牛写的代码也不过如此,自己甚至写的比这些大牛写的还要好,从此自信满满,热情高涨,认为与大牛之间的差距,仅剩下三分钟。

    二、Mybatis自动映射器Mapper的源码分析

    首先编写一个测试类:

    Mapper长这个样子:

    org.apache.ibatis.binding.MapperProxy.java部分源码。

    org.apache.ibatis.binding.MapperProxyFactory.java部分源码。

    这便是Mybatis使用动态代理之投鞭断流。

    三、接口Mapper内的方法能重载(overLoad)吗?(重要)

    类似下面:

    Answer:不能。

    原因:在投鞭断流时,Mybatis使用package+Mapper+method全限名作为key,去xml内寻找唯一sql来执行的。类似:key=x.y.UserMapper.getUserById,那么,重载方法时将导致矛盾。对于Mapper接口,Mybatis禁止方法重载(overLoad)。

     
     
     
     
     
     
     
  • 相关阅读:
    100-days: twelve
    100-days: eleven
    100-days: ten
    [PKUWC 2018]随机算法
    [CTSC 2018]假面
    APIO 2018 游记
    CTSC 2018 游记
    [CQOI 2018]解锁屏幕
    [CQOI 2018]九连环
    [CQOI 2018]破解D-H协议
  • 原文地址:https://www.cnblogs.com/xuwc/p/13993164.html
Copyright © 2011-2022 走看看