Mybatis中的xxxMapper接口在没有实现类的情况下是如何完成sql查询的?通过代码来模拟一下

85 阅读3分钟

前言

公司使用的是一个自研的orm框架,这也导致我工作三四年了对目前市场上的主流orm框架认识浅显。虽然orm框架的底层原理都是基于jdbc来封装实现的,但是封装的思路不同,使用的方便程度和运行效率也有天壤之别。mybais是目前市场上使用比较多的一款orm框架,平时在使用的时候都是在XXXMapper.xml中定义好sql语句,然后编写XXXMapper.java接口,然后就可以使用了。但是仔细思考发现,我们仅仅是定义了接口,并没有定义实现类,程序是如何实现方法的调用并执行sql语句的呢?

mybatis如何找到Mapper.xml文件

mybatis最初设计时,并没有考虑到要和spring去集成,所以原生的提供了基于配置文件的使用方式。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
  <settings>
    <!-- 打印sql日志 -->
    <setting name="logImpl" value="STDOUT_LOGGING" />
  </settings>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"></transactionManager>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="poolMaximumActiveConnections" value="10"/>
        <property name="poolMaximumIdleConnections" value="5"/>
        <property name="poolTimeToWait" value="20000"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/cz/entity/TestCaseMapper.xml"></mapper>
    <mapper resource="com/cz/entity/AutoCollectMethodParamsMapper.xml"></mapper>
  </mappers>
</configuration>

在使用的时候程序中指定配置文件的路径,如下:

public class Test1 {

  public static void main(String[] args) throws IOException {

    // 1、获取配置文件
    InputStream inputStream = Resources.getResourceAsStream("com/cz/resource/mybatis-config.xml");
    // 2、构建SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 3、开启会话
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 4、操作Mapper接口
    //List<String> list1 = sqlSession.getMapper(AutoCollectMethodParamsMapper.class).selectByRequestHashCode("655839017");
    // 5、流式查询 一、直接使用sqlSession.selectCursor("selectAll");指定方法名,如果多个mapper中都有selectAll这个方法,会报错
/*    Cursor<AutoCollectMethodParams> selectByRequestHashCode = sqlSession.selectCursor("selectAll");
    Iterator<AutoCollectMethodParams> iterator = selectByRequestHashCode.iterator();
    while (iterator.hasNext()) {
      System.out.println(iterator.next().getRequestHash());
    }*/
    // 6、流式查询 二、
    TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
    Cursor<TestCase> autoCollectMethodParamsWithBLOBs = mapper.selectAll();
    Iterator<TestCase> iterator1 = autoCollectMethodParamsWithBLOBs.iterator();
    while (iterator1.hasNext()) {
      System.out.println(iterator1.next().getCaseName());
    }

  }
}

程序在执行new SqlSessionFactoryBuilder().build(inputStream);时会解析mybatis-config.xml中的mapper并将mapper添加到HashMap保存。 解析mybatis-config.xml image.png 解析mapper.xml image.png

image.png

解析出mapper中的方法名,以及sql等

image.png

解析完成之后,此时xml中的各种数据已经被保存到了内存中,接下来开始使用。 sqlSession.getMapper(AutoCollectMethodParamsMapper.class).selectByRequestHashCode("655839017"); 这里getMapper其实获取到的就是上面addMapper进去的代理对象。那么代理对象中的方法是什么时候设置进去的?

下面模拟mybatis写一个例子:

package com.cz.proxy;

/**
 * @program: Reids
 * @description: 测试Mybatis
 * @author: Cheng Zhi
 * @create: 2023-05-07 19:58
 **/
public interface Test1Mapper {
    public String select();
    public void insert();
}

模拟mybatis代理类

package com.cz.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @program: Reids
 * @description: 动态代理类
 * @author: Cheng Zhi
 * @create: 2023-05-07 19:59
 **/
public class TestMapperProxy<T> implements InvocationHandler {

    Map map;
    TestMapperProxy(Map map) {
        this.map = map;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print(method.getName());
        System.out.print(":");
        // 这里模拟处理sql
        System.out.println(map.get("sql"));
        return null;
    }
}
package com.cz.proxy;



import java.lang.reflect.Proxy;

/**
 * @program: Reids
 * @description:
 * @author: Cheng Zhi
 * @create: 2023-05-07 20:32
 **/
public class TestMapperProxyFactory<T> {

    Class mapperInterface;

    TestMapperProxyFactory(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    protected T newInstance(TestMapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
}
package com.cz.proxy;

import java.util.Map;

/**
 * @program: Reids
 * @description:
 * @author: Cheng Zhi
 * @create: 2023-05-07 20:23
 **/
public class TestSqlSession {

    Map map;

    TestSqlSession(Map map) {
        this.map = map;
    }

    public <T> T getMapper(Class<T> type) {

        return new TestMapperProxyFactory<T>(type).newInstance(new TestMapperProxy<T>(map));
    }
}
package com.cz.proxy;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: Reids
 * @description:
 * @author: Cheng Zhi
 * @create: 2023-05-07 20:22
 **/
public class TestSqlSessionFactory {

    Map map;

    TestSqlSessionFactory(Map map) {
        this.map = map;
    }

    public TestSqlSession openSqlSession() {

        return new TestSqlSession(map);
    }
}
package com.cz.proxy;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: Reids
 * @description: 模拟mybatis的SqlSessionFactory
 * @author: Cheng Zhi
 * @create: 2023-05-07 20:17
 **/
public class TestSqlSessionFactoryBuilder {


    public TestSqlSessionFactory builder() {
        String id = "select";
        String sql = "select * from ca_bill";
        Map map = new HashMap();
        // 这里理论上应该从文件中获取,这里只是模拟,写死
        map.put("id", id);
        map.put("sql", sql);
        return new TestSqlSessionFactory(map);
    }
}

测试类:

package com.cz.proxy;

/**
 * @program: Reids
 * @description:
 * @author: Cheng Zhi
 * @create: 2023-05-07 20:39
 **/
public class Main {

    public static void main(String[] args) {
        TestSqlSessionFactory builder = new TestSqlSessionFactoryBuilder().builder();
        TestSqlSession testSqlSession = builder.openSqlSession();
        Test1Mapper mapper = testSqlSession.getMapper(Test1Mapper.class);
        mapper.select();
    }
}

运行结果:

image.png

从上面的例子可以看出来,其实并没有类去真实的实现TestMapper接口中的方法,只是在代理类中,根据mapper.xml中的内容做为了方法的内容来处理。综上就是mybatis如何通过代理实现sql语句的执行。