美文网首页
浅谈mybatis接口式编程

浅谈mybatis接口式编程

作者: 谜00016 | 来源:发表于2018-07-01 20:25 被阅读35次

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

image.png

1.pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jshh</groupId>
    <artifactId>mybatisdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>mybatisdemo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

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如何快速建立此配置文件可以参考这篇文章

<?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>
    <!--属性-->
    <!--<properties></properties>-->
    <!--设置-->
    <settings>
        <!--允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。-->
        <setting name="useGeneratedKeys" value="true"/>
        <!--使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果-->
        <setting name="useColumnLabel" value="true"/>
        <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--类型命名-->
    <!--<typeAliases></typeAliases>-->
    <!--类型处理器-->
    <!--<typeHandlers></typeHandlers>-->
    <!--对象工厂-->
    <!--<objectFactory type=""/>-->
    <!--插件-->
    <!--<plugins>-->
    <!--<plugin interceptor=""></plugin>-->
    <!--</plugins>-->
    <!--配置环境-->
    <!--<environments default="default">-->
        <!--&lt;!&ndash;环境变量&ndash;&gt;-->
        <!--<environment id="default">-->
            <!--&lt;!&ndash;事务管理器&ndash;&gt;-->
            <!--<transactionManager type="jdbc"/>-->
            <!--&lt;!&ndash;数据源&ndash;&gt;-->
            <!--<dataSource type="pooled">-->
                <!--<property name="driver" value="${jdbc.driver}"/>-->
                <!--<property name="url" value="${jdbc.url}"/>-->
                <!--<property name="username" value="${jdbc.username}"/>-->
                <!--<property name="password" value="${jdbc.password}"/>-->
            <!--</dataSource>-->
        <!--</environment>-->
    <!--</environments>-->
    <!--数据库厂商标识-->
    <!--<databaseIdProvider type=""/>-->
    <!--映射器-->
    <!--<mappers></mappers>-->
</configuration>

mapper映射sqlxml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!--

       Copyright 2009-2016 the original author or authors.

       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.

-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.jshh.dao.AreaDao">
    <select id="queryArea" resultType="com.jshh.entity.Area">
        SELECT * FROM tb_area
    </select>

    <resultMap type="com.jshh.entity.Area" id="AreaResult">
        <id column="area_id" property="areaId" jdbcType="INTEGER"/>
        <result column="area_name" property="areaName" jdbcType="VARCHAR"/>
        <result column="priority" property="priority" jdbcType="VARCHAR"/>
        <result column="create_time" property="createTime" jdbcType="DATE"/>
        <result column="last_edit_time" property="lastEditTime" jdbcType="DATE"/>
    </resultMap>

    <select id="queryAreaByBean" parameterType="com.jshh.entity.Area" resultMap="AreaResult">
        select
        <include refid="columns"/>
        from tb_area
        <where>
            <if test="areaName != null and !&quot;&quot;.equals(areaName.trim())">
                and area_name=#{areaName}
            </if>
            <if test="priority != null and !&quot;&quot;.equals(priority.trim())">
                and priority like '%' #{priority} '%'
            </if>
        </where>
    </select>

    <sql id="columns">area_id ,area_name,priority,create_time,last_edit_time</sql>

</mapper>

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<Area> objects1 = sqlSession.selectList("com.jshh.dao.AreaDao.queryAreaByBean", area);
...

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

看这句代码得先关联着前面的mapperxml文件看: image.png
别小看这句代码,此处代码至少有四处值得我们分析,com.jshh.dao.AreaDao这是我们的namespace,queryAreaByBean这是关联sql的id,area是我们传入的查询参数, List<Area> 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<Area> queryArea();

    List<Area> 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<Area> 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文件和接口的关系 image.png 发现了吗namespace是接口的全路径,id是接口中的方法名,因此当执行invoke方法的时候匹配就顺利成章了。因此我们得到

第三个结论(最终结论)

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

.....
 public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }
....
进入类Configuration:配置文件加载
public <T> T getMapper(Class<T> 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<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

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

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

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

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set<Class<? extends Class<?>>> 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<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

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

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

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

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

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> 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<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 {
        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<Lookup> 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 <E> 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接口式编程

      本文链接:https://www.haomeiwen.com/subject/bbkxuftx.html