MyBastis如何映射,MyBatis原理

Mybatis可以把Mapper.xml文件直接映射到对应的接口,调用接口方法会自动去Mapper.xml文件中找到对应的标签,这个功能就是利用java的动态代理在binding包中实现的。

动态代理:动态代理是Java语言中非常经典的一种设计模式,也是所有设计模式中最难理解的一种。将通过一个简单的例子模拟JDK动态代理实现,让你彻底明白动态代理设计模式的本质。

什么是代理?

从字面意思来看,代理比较好理解,无非就是代为处理的意思。举个例子,你在上大学的时候,总是喜欢逃课。因此,你拜托你的同学帮你答到,而自己却窝在宿舍玩游戏… 你的这个同学恰好就充当了代理的作用,代替你去上课。
是的,你没有看错,代理就是这么简单!
理解了代理的意思,你脑海中恐怕还有两个巨大的疑问:

  • 怎么实现代理模式
  • 代理模式有什么实际用途

要理解这两个问题,看一个简单的例子:

public interface Flyable { 
  void fly();
}
public class Bird implements Flyable { 
  
  @Override
  pubilc void fly(){ 
    System.out.println("Bird is flying...");
    try { 
      Thread.sleep(new Random().nextInt(1000));
    }catch(InterruptedException e){ 
      e.printStackTrace();
    }
  }
}

很简单的一个例子,用一个随机睡眠时间模拟小鸟在空中的飞行时间。接下来问题来了,如果我要知道小鸟在天空中飞行了多久,怎么办?

有人会说,很简单,在Bird->fly()方法的开头记录起始时间,在方法结束记录完成时间,两个时间相减就得到了飞行时间。

@Override
public void fly() { 
  long start = System.currentTimeMillis();
  System.out.println("Bird is flying...");
  try { 
    Thread.sleep(new Random().nextInt(1000));
  }catch (InterruptedException e){ 
    e.printStackTrace();
  }
  long end = System.currentTimeMillis();
  System.out.println("Fly time = " + (end - start));
}

的确,这个方法没有任何问题,接下来加大问题的难度。如果Bird这个类来自于某个SDK(或者说Jar包)提供,你无法改动源码,怎么办?

一定会有人说,我可以在调用的地方这样写:

public static void main(String[] args) { 
  Bird bird = new Bird();
  long start = System.currentTimeMillis();
  bird.fly();
  long end = System.currentTimeMillis();
  System.out.println("Fly time = " + (end - start));
}

这个方案看起来似乎没有问题,但其实你忽略了准备这些方法所需要的时间,执行一个方法,需要开辟栈内存、压栈、出栈等操作,这部分时间也是不可以忽略的。因此,这个解决方案不可行。那么,还有什么方法可以做到呢?

a)使用继承
继承是最直观的解决方案,相信你已经想到了,至少我最开始想到的解决方案就是继承。
为此,我们重新创建一个类Bird2,在Bird2中我们只做一件事情,就是调用父类的fly方法,在前后记录时间,并打印时间差:

public class Bird2 extends Bird { 
  @Override
  public void fly() { 
    long start = System.currentTimeMillis();
    super.fly();
    long end = System.currentTimeMillis();
    System.out.println("Fly time = " + (end - start));
  }
}

这是一种解决方案,还有一种解决方案叫做:聚合,其实也是比较容易想到的。
我们再次创建新类Bird3,在Bird3的构造方法中传入Bird实例。同时,让Bird3也实现Flyable接口,并在fly方法中调用传入的Bird实例的fly方法:

public class Bird3 implements Flyable { 
  private Bird bird;
  public Bird3(Bird bird) { 
    this.bird = bird;
  }
  @Override
  long start = System.currentTimeMillis();
  bird.fly();
  long end = System.currentTimeMillis();
  System.out.println("Fly time = " + (end - start));
}

为了记录Bird->fly()方法的执行时间,我们在前后添加了记录时间的代码。同样地,通过这种方法我们也可以获得小鸟的飞行时间。那么,这两种方法孰优孰劣呢?咋一看,不好评判!

继续深入思考,用问题推导来解答这个问题:
问题一:如果我还需要在fly方法前后打印日志,记录飞行开始和飞行结束,怎么办?

有人说,很简单!继承Bird2并在在前后添加打印语句即可。那么,问题来了,请看问题二。
问题二:如果我需要调换执行顺序,先打印日志,再获取飞行时间,怎么办?

有人说,再新建一个类Bird4继承Bird,打印日志。再新建一个类Bird5继承Bird4,获取方法执行时间。
问题显而易见:使用继承将导致类无限制扩展,同时灵活性也无法获得保障。那么,使用 聚合 是否可以避免这个问题呢?

答案是:可以!但我们的类需要稍微改造一下。修改Bird3类,将聚合对象Bird类型修改为Flyable

public class Bird3 implements Flyable { 
  private Flyable flyable;
  public Bird3(Flyable flyable) { 
     this.flyable = flyable;
  }
  @Override
  public void fly() { 
    long start = System.currentTimeMillis();
    flyable.fly();
    long end = System.currentTimeMillis();
    System.out.println("Fly time = " + (end - start));
  }
}

为了让你看的更清楚,我将Bird3更名为BirdTimeProxy,即用于获取方法执行时间的代理的意思。同时我们新建BirdLogProxy代理类用于打印日志:

public class BirdLogProxy implements Flyable { 
  private Flyable flyable;
  public BirdLogProxy(Flyable flyable) { 
    this.flyable = flyable;
  }
  @Override
  public void fly(){ 
    System.out.println("Bird fly start...");
    flyable.fly();
    System.out.println("Bird fly end...");
  }
}

接下来神奇的事情发生了,如果我们需要先记录日志,再获取飞行时间,可以在调用的地方这么做:

public static void main(String[] args) { 
  Bird bird = new Bird();
  BirdLogProxy p1 = new BirdLogProxy(bird);
  BirdTimeProxy p2 = new BirdTimeProxy(p1);
  p2.fly();
}

反过来,可以这么做:

 public static void main(String[] args) { 
   Bird bird = new Bird();
   BirdTimeProxy p2 = new BirdTimeProxy(bird);
   BirdLogProxy p1 = new BirdLogProxy(p2);
   p1.fly();
 }

看到这里,有同学可能会有疑问了。虽然现象看起来,聚合可以灵活调换执行顺序。可是,为什么 聚合 可以做到,而继承不行呢。我们用一张图来解释一下:
《MyBastis如何映射,MyBatis原理》

静态代理

接下来,观察上面的类BirdTimeProxy,在它的fly方法中我们直接调用了flyable->fly()方法。换而言之,BirdTimeProxy其实代理了传入的Flyable对象,这就是典型的静态代理实现。

从表面上看,静态代理已经完美解决了我们的问题。可是,试想一下,如果我们需要计算SDK中100个方法的运行时间,同样的代码至少需要重复100次,并且创建至少100个代理类。往小了说,如果Bird类有多个方法,我们需要知道其他方法的运行时间,同样的代码也至少需要重复多次。因此,静态代理至少有以下两个局限性问题:

  • 如果同时代理多个类,依然会导致类无限制扩展
  • 如果类中有多个方法,同样的逻辑需要反复实现

那么,我们是否可以使用同一个代理类来代理任意对象呢?我们以获取方法运行时间为例,是否可以使用同一个类(例如:TimeProxy)来计算任意对象的任一方法的执行时间呢?甚至再大胆一点,代理的逻辑也可以自己指定。比如,获取方法的执行时间,打印日志,这类逻辑都可以自己指定。这就是最难理解的部分:动态代理

动态代理

继续回到上面这个问题:是否可以使用同一个类(例如:TimeProxy)来计算任意对象的任一方法的执行时间呢。

这个部分需要一定的抽象思维,我想,你脑海中的第一个解决方案应该是使用反射。反射是用于获取已创建实例的方法或者属性,并对其进行调用或者赋值。很明显,在这里,反射解决不了问题。但是,再大胆一点,如果我们可以动态生成TimeProxy这个类,并且动态编译。然后,再通过反射创建对象并加载到内存中,不就实现了对任意对象进行代理了吗?那么用一张图来描述接下来要做什么:
《MyBastis如何映射,MyBatis原理》
动态生成Java源文件并且排版是一个非常繁琐的工作,为了简化操作,我们使用 JavaPoet 这个第三方库帮我们生成TimeProxy的源码。不理解 JavaPoet 没有关系,你只要把它当成一个Java源码生成工具使用即可。

第一步:生成TimeProxy源码

public class Proxy{ 
  public static Object newProxyInstance() throws IOException { 
   TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TimeProxy").addSuperinterface(Flyable.class);
   FieldSpec fieldSpec = FieldSpec.builder(Flyable.class, "flyable", Modifier.PRIVATE).build();
   typeSpecBuilder.addField(fieldSpec);
   MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addParameter(Flyable.class, "flyable").addStatement("this.flyable = flyable").build();
   typeSpecBuilder.addMethod(constructorMethodSpec);
   Method[] methods = Flyable.class.getDeclaredMethods();
   for (Method method : methods) { 
     MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName()).addModifiers(Modifier.PUBLIC).addAnnotation(Override.class).returns(method.getReturnType()).addStatement("long start = $T.currentTimeMillis()", System.class).addCode("\n").addStatement("this.flyable." + method.getName() + "()").addCode("\n").addStatement("long end = $T.currentTimeMillis()", System.class).addStatement("$T.out.println(\"Fly Time =\" + (end - start))", System.class).build();
     typeSpecBuilder.addMethod(methodSpec);
   }
   JavaFile javaFile = JavaFile.builder("com.Slgod.proxy", typeSpecBuilder.build()).build();
   // 为了看的更清楚,我将源码文件生成到桌面
   javaFile.writeTo(new File("/Users/Slgod/Desktop/"));
   return null;
  }
}

在main方法中调用Proxy.newProxyInstance(),你将看到桌面已经生成了TimeProxy.java文件,生成的内容如下:

package com.Slgod.proxy;

import java.lang.Override;
import java.lang.System;

class TimeProxy implements Flyable { 
  private Flyable flyable;
  public TimeProxy(Flyable flyable) { 
    this.flyable = flyable;
  }
  @Override
  public void fly() { 
    long start = System.currentTimeMillis();
    this.flyable.fly();
    long end = System.currentTimeMillis();
    System.out.println("Fly Time =" + (end - start));
  }
}

第二步:编译TimeProxy源码
编译TimeProxy源码我们直接使用JDK提供的编译工具即可,为了使你看起来更清晰,我使用一个新的辅助类来完成编译操作:

public class JavaCompiler { 
  public static void compile(File javaFile) throws IOException { 
    javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
    Iterable iterable = fileManager.getJavaFileObjects(javaFile);
    javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
    task.call();
    fileManager.close();
  }
}

在Proxy->newProxyInstance()方法中调用该方法,编译顺利完成:

// 为了看的更清楚,我将源码文件生成到桌面
String sourcePath = "/Users/Slgod/Desktop/";
javaFile.writeTo(new File(sourcePath));

// 编译
JavaCompiler.compile(new File(sourcePath + "/com/youngfeng/proxy/TimeProxy.java"));

《MyBastis如何映射,MyBatis原理》
第三步:加载到内存中并创建对象

URL[] urls = new URL[] { new URL("file:/" + sourcePath)};
URLClassLoader classLoader = new URLClassLoader(urls);
Class clazz = classLoader.loadClass("com.Slgod.proxy.TimeProxy");
Constructor constructor = clazz.getConstructor(Flyable.class);
Flyable flyable = (Flyable) constructor.newInstance(new Bird());
flyable.fly();

通过以上三个步骤,我们至少解决了下面两个问题:

  • 不再需要手动创建TimeProxy
  • 可以代理任意实现了Flyable接口的类对象,并获取接口方法的执行时间

可是,说好的任意对象呢…

第四步:增加InvocationHandler接口
查看Proxy->newProxyInstance()的源码,代理类继承的接口我们是写死的,为了增加灵活性,我们将接口类型作为参数传入:
《MyBastis如何映射,MyBatis原理》
接口的灵活性问题解决了,TimeProxy的局限性依然存在,它只能用于获取方法的执行时间,而如果要在方法执行前后打印日志则需要重新创建一个代理类,显然这是不妥的!

为了增加控制的灵活性,我们考虑针将代理的处理逻辑也抽离出来(这里的处理就是打印方法的执行时间)。新增InvocationHandler接口,用于处理自定义逻辑:

public interface InvocationHandler { 
    void invoke(Object proxy, Method method, Object[] args);
}

想象一下,如果客户程序员需要对代理类进行自定义的处理,只要实现该接口,并在invoke方法中进行相应的处理即可。这里我们在接口中设置了三个参数(其实也是为了和JDK源码保持一致):

  • proxy => 这个参数指定动态生成的代理类,这里是TimeProxy
  • method => 这个参数表示传入接口中的所有Method对象
  • args => 这个参数对应当前method方法中的参数

引入了InvocationHandler接口之后,我们的调用顺序应该变成了这样:

MyInvocationHandler handler = new MyInvocationHandler();
Flyable proxy = Proxy.newProxyInstance(Flyable.class, handler);
proxy.fly();
方法执行流:proxy.fly() => handler.invoke()

为此,需要在Proxy.newProxyInstance()方法中做如下改动:

  • 在newProxyInstance方法中传入InvocationHandler
  • 在生成的代理类中增加成员变量handler
  • 在生成的代理类方法中,调用invoke方法
  public static Object newProxyInstance(Class inf, InvocationHandler handler) throws Exception { 
        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TimeProxy")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(inf);

        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();
        typeSpecBuilder.addField(fieldSpec);

        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(InvocationHandler.class, "handler")
                .addStatement("this.handler = handler")
                .build();

        typeSpecBuilder.addMethod(constructorMethodSpec);

        Method[] methods = inf.getDeclaredMethods();
        for (Method method : methods) { 
            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(method.getReturnType())
                    .addCode("try {\n")
                    .addStatement("\t$T method = " + inf.getName() + ".class.getMethod(\"" + method.getName() + "\")", Method.class)
                    // 为了简单起见,这里参数直接写死为空
                    .addStatement("\tthis.handler.invoke(this, method, null)")
                    .addCode("} catch(Exception e) {\n")
                    .addCode("\te.printStackTrace();\n")
                    .addCode("}\n")
                    .build();
            typeSpecBuilder.addMethod(methodSpec);
        }

        JavaFile javaFile = JavaFile.builder("com.Slgod.proxy", typeSpecBuilder.build()).build();
        // 为了看的更清楚,我将源码文件生成到桌面
        String sourcePath = "/Users/Slgod/Desktop/";
        javaFile.writeTo(new File(sourcePath));

        // 编译
        JavaCompiler.compile(new File(sourcePath + "/com/Slgod/proxy/TimeProxy.java"));

        // 使用反射load到内存
        URL[] urls = new URL[] { new URL("file:" + sourcePath)};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class clazz = classLoader.loadClass("com.Slgod.proxy.TimeProxy");
        Constructor constructor = clazz.getConstructor(InvocationHandler.class);
        Object obj = constructor.newInstance(handler);

        return obj;
 }

直接调用该方法,查看最后生成的源码。在main方法中测试newProxyInstance查看生成的TimeProxy源码:

测试代码

Proxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird()));

生成的TimeProxy.java源码

package com.Slgod.proxy;

import java.lang.Override;
import java.lang.reflect.Method;

public class TimeProxy implements Flyable { 
  private InvocationHandler handler;

  public TimeProxy(InvocationHandler handler) { 
    this.handler = handler;
  }

  @Override
  public void fly() { 
    try { 
        Method method = com.Slgod.proxy.Flyable.class.getMethod("fly");
        this.handler.invoke(this, method, null);
    } catch(Exception e) { 
        e.printStackTrace();
    }
  }
}

MyInvocationHandler.java

public class MyInvocationHandler implements InvocationHandler { 
    private Bird bird;

    public MyInvocationHandler(Bird bird) { 
        this.bird = bird;
    }

    @Override
    public void invoke(Object proxy, Method method, Object[] args) { 
        long start = System.currentTimeMillis();

        try { 
            method.invoke(bird, new Object[] { });
        } catch (IllegalAccessException e) { 
            e.printStackTrace();
        } catch (InvocationTargetException e) { 
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }
}

至此,整个方法栈的调用栈变成了这样:
《MyBastis如何映射,MyBatis原理》
在静态代理部分,我们在代理类中传入了被代理对象。可是,使用newProxyInstance生成动态代理对象的时候,我们居然不再需要传入被代理对象了。我们传入了的实际对象是InvocationHandler实现类的实例,这看起来有点像生成了InvocationHandler的代理对象,在动态生成的代理类的任意方法中都会间接调用InvocationHandler->invoke(proxy, method, args)方法。

其实的确是这样。TimeProxy真正代理的对象就是InvocationHandler,不过这里设计的巧妙之处在于,InvocationHandler是一个接口,真正的实现由用户指定。另外,在每一个方法执行的时候,invoke方法都会被调用 ,这个时候如果你需要对某个方法进行自定义逻辑处理,可以根据method的特征信息进行判断分别处理。

再回到Mybatis,继续研究Mybatis的配置和接口映射原理

一、注册Mapper

在初始化时会把获取到的Mapper接口注册到MapperRegistry,注册的时候创建一个Mapper代理工厂,这个工厂通过JDK的代理创建一个执行对象,创建代理需要的InvocationHandler为MapperProxy

//接口注册
public class MapperRegistry { 
  public <T> void addMapper(Class<T> type) { 
    //如果是接口
    if (type.isInterface()) { 
      if (hasMapper(type)){ 
        throw new BindingException("Type " + type + "is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try { 
        //放到map中, value为创建代理的工厂
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It’s important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the mapper parser. If the type is already known, it won’t try.
        //这里是解析Mapper接口里面的注解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      }finally{ 
        if (!loadCompleted) { 
           knownMappers.remove(type);
        }
      }
    }
  }
}

二、获取接口对象

从knownMappers中根据接口类型取出对应的代理创建工厂,用该工厂创建代理。

public class MapperRegistry { 
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 
    //取出MapperProxyFactory
    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);
    }
  }
}

//创建代理的工厂
public class MapperProxyFactory<T> { 
  //需要创建代理的接口
  private final Class<T> mapperInterface;
  //执行方法的缓存,不需要每次都创建MapperMethod
  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  public MapperProxyFactory(Class<T> mapperInterface) { 
    this.mapperInterface = mapperInterface;
  }
  public Class<T> getMapperInterface() { 
    return mapperInterface;
  }
  public Map<Method, MapperMethod> getMethodCache() { 
    return methodCache;
  }
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) { 
    //创建代理, InvocationHanderl是MapperProxy
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {  mapperInterface },mapperProxy);
  }
        
     //传人sqlSession创建代理1 
     //@param sqlSession1 
     //@return
    
  public T newInstance(SqlSession sqlSession) { 
    //把代理执行需要用到的对象传入
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

三、调用接口方法

调用代理方法会进入到MapperProxy的public Object invoke(Object proxy, Method method, Object[] args)方法

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;
  }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
    //如果方法是Object里面的则直接调用方法
    if (Object.class.equals(method.getDeclaringClass())){ 
      try { 
        return method.invoke(this, args);
      }catch (Throwable t) { 
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //获取执行方法的封装对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //里面就是找到对应的sql 执行sql语句
    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;
  }
}

最终执行sql会进入到MapperMethod中execute方法:

//具体的根据接口找到配置文件标签的类
public class MapperMethod { 
  private final SqlCommand command;
  private final MethodSignature method;
  
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { 
    //SqlCommand封装该接口方法需要执行sql的相关属性,如:id(name), 类型
    this.command = new SqlCommand(config, mapperInterface, method);
    //执行方法特性进行封装,用于构造sql参数,判断执行sql逻辑走哪条分支
    this.method = new MethodSignature(config, method);
  }
   public Object execute(SqlSession sqlSession, Object[] args) { 
     Object result;
      //先找到对应的执行sql类型, sqlSession会调用不同方法
     if (SqlCommandType.INSERT == command.getType()) { 
       Object param = method.convertArgsToSqlCommandParam(args);
       result = rowCountResult(sqlSession.insert(command.getName(), param));
     } else if (SqlCommandType.UPDATE == command.getType()) { 
       Object param = method.convertArgsToSqlCommandParam(args);
       result = rowCountResult(sqlSession.update(command.getName(), param));
     } else if (SqlCommandType.DELETE == command.getType()) { 
       Object param = method.convertArgsToSqlCommandParam(args);
       result = rowCountResult(sqlSession.delete(command.getName(), param));
     } else if (SqlCommandType.SELECT == command.getType()) { 
       //如果是查询, 需要对返回做判断处理
       //根据方法的特性判断进入哪个执行分支
       if (method.returnsVoid() && method.hasResultHandler()) { 
         executeWithResultHandler(sqlSession, args);
         result = null;
       } else if (method.returnsMany()) { 
          result = executeForMany(sqlSession, args);
       } else if (method.returnsMap()) { 
          result = executeForMap(sqlSession, args);
       } else { 
          //只查一条数据
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
       }
     } else { 
       throw new BindingException("Unknown execution method for: " + command.getName());
     }
     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid() { 
        throw new BindingException("Mapper method '" + command.getName()+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
     }
     return result;
   }
}

上面就是根据接口、方法、配置参数找到对应的执行sql,并构造参数,解析执行结果,具体sql执行在sqlSession流程里面,后面再看。

    原文作者:Slgod
    原文地址: https://blog.csdn.net/weixin_43143087/article/details/107098620
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞