浅谈mybatis接口式编程

最近在看mybatis框架,mybatis是什么呢??先把官网地址贴上。套用官方解释:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
mybatis框架中有一个最重要的类SqlSession,这个类可以有执行sql语句、提交或回滚事务和获取映射器实例的方法。
我们先搭建一个最简单的springboot+mybatis工程:
先看一下工程目录:

浅谈mybatis接口式编程_第1张图片
image.png

1.pom文件如下:



    4.0.0

    com.jshh
    mybatisdemo
    0.0.1-SNAPSHOT
    jar

    mybatisdemo
    Demo project for Spring Boot

    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.3.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.2
        

        
            mysql
            mysql-connector-java
            runtime
        
        
        
            com.mchange
            c3p0
            0.9.5.2
        

        
        
            org.projectlombok
            lombok
            1.16.16
            provided
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

2.两个配置类(其实也可以在xml文件中配置),一个是数据源的配置类,一个是mybatis的SqlSessesionFactory配置类

数据源的配置类

package com.jshh.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.beans.PropertyVetoException;

@Configuration
@MapperScan("com.jshh.dao")
public class DataSourceConfig {
    @Value("${jdbc.driver}")
    private String jdbcdriver;
    @Value("${jdbc.url}")
    private String jdbcurl;
    @Value("${jdbc.username}")
    private String jdbcusername;
    @Value("${jdbc.password}")
    private String jdbcpassword;

    @Bean(name = "comboPooledDataSource")
    public ComboPooledDataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(jdbcdriver);
        dataSource.setJdbcUrl(jdbcurl);
        dataSource.setUser(jdbcusername);
        dataSource.setPassword(jdbcpassword);
        dataSource.setAutoCommitOnClose(false);
        return dataSource;
    }

}

SqlSessesionFactory配置类

package com.jshh.config;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.List;

/**
 * @Auther: 王明
 * @Date: 2018/6/30 15:49
 * @Description:
 */
@Configuration
public class SqlSessesionFactoryConfig {

    @Value("${mybatis_config_file}")
    private String mybatisConfigFilePath;

    @Value("${mapper_path}")
    private String mapperPath;

    @Value("${entity_package}")
    private String entitypackage;

    @Autowired
    @Qualifier("comboPooledDataSource")
    private DataSource mDataSource;

    @Bean("sqlSessionFactoryBean")
    public SqlSessionFactoryBean getSqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        String packageSearchPath = PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + mapperPath;

        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(mybatisConfigFilePath));
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(packageSearchPath));
        sqlSessionFactoryBean.setDataSource(mDataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage(entitypackage);
 
        return sqlSessionFactoryBean;
    }
}

3.配置文件application.properties

server.port=9036

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatisdemo?useUnicode=true&characterEncoding=utf-8&useSSL=true
jdbc.username=root
jdbc.password=mysql
#mybatis
mybatis_config_file=mybatis-config.xml
mapper_path=/mapper/**.xml
entity_package=com.jshh.entity

4.mybatis核心配置文件与mapper映射的sqlxml文件

mybatis核心配置文件mybatis-config.xml,关于使用idea如何快速建立此配置文件可以参考这篇文章





    
    
    
    
        
        
        
        
        
        
    
    
    
    
    
    
    
    
    
    
    
    
    
        
        
            
            
            
            
                
                
                
                
            
        
    
    
    
    
    

mapper映射sqlxml 文件






    

    
        
        
        
        
        
    

    

    area_id ,area_name,priority,create_time,last_edit_time



5.最后交代一下本例中创建的实体类Area

package com.jshh.entity;

import lombok.Getter;
import lombok.Setter;

import java.util.Date;

/**
 * @Auther: 王明
 * @Description:
 */
@Getter
@Setter
public class Area {
    private Integer areaId;
    private String areaName;
    private String priority;
    private Date createTime;
    private Date lastEditTime;
}

创建工程到此告一段落,我们来使用mybatis中最重要的一个类SqlSession使一下:

...
     Area area = new Area();
        area.setAreaName("ss");
        area.setPriority("0");
        List objects1 = sqlSession.selectList("com.jshh.dao.AreaDao.queryAreaByBean", area);
...

调试结果当然是可以获取正常的返回值,现在我们来分析一下这句核心代码
List objects1 = sqlSession.selectList("com.jshh.dao.AreaDao.queryAreaByBean", area);

看这句代码得先关联着前面的mapperxml文件看:
浅谈mybatis接口式编程_第2张图片
image.png

别小看这句代码,此处代码至少有四处值得我们分析,com.jshh.dao.AreaDao这是我们的namespace,queryAreaByBean这是关联sql的id,area是我们传入的查询参数, List objects1这个是我们返回的Area实体的集合。这种写法当然是正确的,但是有没有更优的做法呢?既然这么说了那肯定是有的对吧。我们常常从一些书里面或者一些老鸟口中听到“java设计第一原则:面向接口编程,对修改关闭对扩展开放”。是不是有种不明觉厉的感觉?别慌,我们先弄清什么是面向接口编程?

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

是不是还挺迷糊的?确实,编程思想这个东西得需要从大量的经验中慢慢感悟,颇有点“道”的味道。没关系我们继续看上面那句代码,我们有没有发现这种写法有什么弊端?
首先是关于传入的"坐标"(即定位到mapper文件中的namespace和id)"com.jshh.dao.AreaDao.queryAreaByBean",这样手写必然是不安全的,容易疏忽出错。
第二个问题是传入的参数area,因为sqlSession.selectList方法中参数是object,所有我们传入什么参数都可以编译通过(编译通过不一定会执行通过,比如mapper文件中需要的是Area2,你传入Area这样的话就就会执行出错),这样不利于代码的健壮性。
还有一个问题是返回值的问题。同样的道理也是不利于代码健壮。
我们应该如何改造呢???
既然今天谈的是mybatis接口式编程,那肯定是要建立一个接口嘛。

AreaDao接口

package com.jshh.dao;

import com.jshh.entity.Area;
import java.util.List;

public interface AreaDao {

    List queryArea();

    List queryAreaByBean(Area area);

    Area queryAreaById();
}

写个单元测试:

package com.jshh;

import com.jshh.dao.AreaDao;
import com.jshh.entity.Area;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisdemoApplicationTests {
    @Autowired
    AreaDao areaDao;
    @Test
    public void contextLoads() {
    }
    @Test
    public void queryArea() {
        Area area = new Area();
        area.setAreaName("ss");
        area.setPriority("0");
        List areas1 = areaDao.queryAreaByBean(area);
    }
}

我们发现改造之后的代码更加清爽。它的执行效果和sqlSession.selectList("com.jshh.dao.AreaDao.queryAreaByBean", area);是一样的。我们不禁要问,凭什么这样我们就能直接调用到mapper中的sql语句呢?对于这两句代码执行效果一样,我们可以通过阅读mybatis源码来找到答案。在阅读源码之前,请先确保对java动态代理相关的知识有所了解,我写过一篇关于java动态代理的文章,记录了我对java动态代理的理解,感兴趣的朋友可以去瞅瞅,文章地址,此处不做赘述。我们先理出一个思路,然后跟着思路去阅读源码。
首先要证明areaDao.queryAreaByBean(area)==sqlSession.selectList("com.jshh.dao.AreaDao.queryAreaByBean", area);
这两句代码等效。
我们有想过areaDao这个实例是怎么来的吗?你可能回答是由spring管理的的。是的没错,但是如果我们不使用spring呢?这个实例怎么来?就是我们之前一直强调的mybatis中最重要的一个类SqlSession类,sqlSession.getMapper(AreaDao.class);可以获取到接口的实例。因此我们可以得到

第一个结论

sqlSession.getMapper(AreaDao.class)=Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
我们往下走之前,我们得先弄清楚为什么areaDao这个没有实现类的接口为什么能执行queryAreaByBean方法,这个里面涉及到动态代理的知识。由动态代理的知识我们知道代理类MapperProxy实现InvocationHandler接口,里面有个invoke方法,当我们执行这句areaDao.queryAreaByBean(area)代码的时候,就会触发代理类中的invoke方法。因此我们可以得到

第二个结论

areaDao==Proxy.newProxyInstance()
areaDao.queryAreaByBean(area)==MapperProxy.invoke

还有一个问题,invoke方法中是如何知晓该执行哪条sql语句呢,我们可以猜想必定是配置信息被加载之后存在某个方法中,当invoke方法执行的时候一旦匹配上就可以知晓执行哪条sql语句了。那是怎么匹配上的呢。我们仔细看一下Areamapper文件和接口的关系
浅谈mybatis接口式编程_第3张图片
image.png
发现了吗namespace是接口的全路径,id是接口中的方法名,因此当执行invoke方法的时候匹配就顺利成章了。因此我们得到

第三个结论(最终结论)

areaDao.queryAreaByBean(area)==sqlSession.selectList("com.jshh.dao.AreaDao.queryAreaByBean", area);
思路理清之后,我们瞅瞅源码,看看我们的猜想是否正确:
sqlSession.getMapper(AreaDao.class)进入
类DefaultSqlSession:

.....
 public  T getMapper(Class type) {
        return this.configuration.getMapper(type, this);
    }
....
进入类Configuration:配置文件加载
public  T getMapper(Class type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
进入类MapperRegistry
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.binding;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.io.ResolverUtil.IsA;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;

public class MapperRegistry {
    private final Configuration config;
    private final Map, MapperProxyFactory> knownMappers = new HashMap();

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

    public  T getMapper(Class type, SqlSession sqlSession) {
        MapperProxyFactory 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  boolean hasMapper(Class type) {
        return this.knownMappers.containsKey(type);
    }

    public  void addMapper(Class 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> getMappers() {
        return Collections.unmodifiableCollection(this.knownMappers.keySet());
    }

    public void addMappers(String packageName, Class superType) {
        ResolverUtil> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set>> 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);
    }
}

MapperRegistry类方法getMapper说明:这个方法是通过MapperProxyFactory代理工厂获取代理类实例。其中代理工厂是从knownMappers中获取,我们看下knownMappers这个map是在哪进行put的。很容易找到在本类addMapper方法中,这个方法是在加载配置文件时被执行。

进入类MapperProxyFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.binding;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.session.SqlSession;

public class MapperProxyFactory {
    private final Class mapperInterface;
    private final Map methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class getMapperInterface() {
        return this.mapperInterface;
    }

    public Map getMethodCache() {
        return this.methodCache;
    }

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

    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

MapperProxyFactory类方法newInstance说明:通过Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);这个方法来获取到代理类的实例,第一个参数是类加载器,第二个参数是代理类要实现的接口数组,第三个参数是代理实例的处理程序(这个参数不是很理解,猜想类似装饰模式吧)

进入类MapperProxy(重点类)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

public class MapperProxy implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class mapperInterface;
    private final Map methodCache;

    public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if(Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if(this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
        if(mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            this.methodCache.put(method, mapperMethod);
        }

        return mapperMethod;
    }

    @UsesJava7
    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        Constructor constructor = Lookup.class.getDeclaredConstructor(new Class[]{Class.class, Integer.TYPE});
        if(!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }

        Class declaringClass = method.getDeclaringClass();
        return ((Lookup)constructor.newInstance(new Object[]{declaringClass, Integer.valueOf(15)})).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
    }
}

MapperProxy类说明;当执行areaDao.queryAreaByBean(area)时其实就执行了invoke这个方法.最终执行了MapperMethod .execute,我们看下cachedMapperMethod这个方法,注意 mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());这方法中的参数,我们只需要知道从这个对象中,我们可以获取到namespace.id的值,我们进入到MapperMethod类

类MapperMethod(重点)

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Flush;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ParamNameResolver;
import org.apache.ibatis.reflection.TypeParameterResolver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MapperMethod(Class mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch(null.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
        case 1:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case 2:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case 3:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case 4:
            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);
            }
            break;
        case 5:
            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;
        }
    }

    private Object rowCountResult(int rowCount) {
        Object result;
        if(this.method.returnsVoid()) {
            result = null;
        } else if(!Integer.class.equals(this.method.getReturnType()) && !Integer.TYPE.equals(this.method.getReturnType())) {
            if(!Long.class.equals(this.method.getReturnType()) && !Long.TYPE.equals(this.method.getReturnType())) {
                if(!Boolean.class.equals(this.method.getReturnType()) && !Boolean.TYPE.equals(this.method.getReturnType())) {
                    throw new BindingException("Mapper method '" + this.command.getName() + "' has an unsupported return type: " + this.method.getReturnType());
                }

                result = Boolean.valueOf(rowCount > 0);
            } else {
                result = Long.valueOf((long)rowCount);
            }
        } else {
            result = Integer.valueOf(rowCount);
        }

        return result;
    }

    private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
        MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(this.command.getName());
        if(!StatementType.CALLABLE.equals(ms.getStatementType()) && Void.TYPE.equals(((ResultMap)ms.getResultMaps().get(0)).getType())) {
            throw new BindingException("method " + this.command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation, or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
        } else {
            Object param = this.method.convertArgsToSqlCommandParam(args);
            if(this.method.hasRowBounds()) {
                RowBounds rowBounds = this.method.extractRowBounds(args);
                sqlSession.select(this.command.getName(), param, rowBounds, this.method.extractResultHandler(args));
            } else {
                sqlSession.select(this.command.getName(), param, this.method.extractResultHandler(args));
            }

        }
    }

    private  Object executeForMany(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        if(this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(this.command.getName(), param);
        }

        return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;

    }
...
}

类MapperMethod 说明:在这类中execute方法是最终执行类,顺着往下看executeForMany方法中,我们看到了一行熟悉的代码: result = sqlSession.selectList(this.command.getName(), param);终于在最后看到了曙光!!!
至此我们队mybatis的接口式编程有了一个大概的了解,这其中还有很多细节值得深挖,再次感叹学海无涯啊!

你可能感兴趣的:(浅谈mybatis接口式编程)