MyBatis源码解读

前言

之前我们通过图片讲解了Mybatis底层原理,今天我们就从源码入手去解读下Mybatis这个持久化框架是如何执行SQL的这个完整过程。

老样子,我们在看源码之前先写下demo,参考官方文档进行编写即可。
mysql5.7\mybatis1.3.9\springboot项目

mybatisdemo

User表的model类

package com.ckmike.mybatisdemo.model;

import java.util.Date;

/**
 * User 简要描述
 * <p> TODO:描述该类职责 </p>
 *
 * @author ckmike
 * @version 1.0
 * @date 18-12-16 下午3:14
 * @copyright ckmike
 **/
public class User {

    private int id;
    private String name;
    private int age;
    private String address;
    private Date ctime;

    public User() {  }

    public User(int id, String name, int age, String address, Date ctime) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
        this.ctime = ctime;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }

    public Date getCtime() {
        return ctime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ", address=‘" + address + ‘\‘‘ +
                ", ctime=" + ctime +
                ‘}‘;
    }
}

UserMapper接口

package com.ckmike.mybatisdemo.mapper;

import com.ckmike.mybatisdemo.model.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * @ClassName UserMapper
 * @Description TODO:描述该接口职责
 * @Author ckmike
 * @Date 18-12-16 下午3:18
 * @Version 1.0
 * @Copyright ckmike
 **/
@Mapper
public interface UserMapper {

    List<User> getUser();
}

UserMapper.xml接口映射SQL文件

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

<mapper namespace="com.ckmike.mybatisdemo.mapper.UserMapper">

    <select id="getUser" resultType="com.ckmike.mybatisdemo.model.User">
        SELECT * FROM user;
    </select>
</mapper>

mybatis-config.xml配置文件

<?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>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="false"/>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <typeAlias type="com.ckmike.mybatisdemo.model.User" alias="User"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" /> <!-- 声明使用那种事务管理机制 JDBC/MANAGED -->
            <!-- 配置数据库连接信息 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/ckmike?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;characterSetResults=utf8&amp;useSSL=false&amp;verifyServerCertificate=false&amp;autoReconnct=true&amp;autoReconnectForPools=true&amp;allowMultiQueries=true" />
                <property name="username" value="root" />
                <property name="password" value="******" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mybatis/UserMapper.xml"/>
    </mappers>

</configuration>

这里开启了mybatis日志执行输出。

接下来就是看测试代码:

package com.ckmike.mybatisdemo;

import com.ckmike.mybatisdemo.mapper.UserMapper;
import com.ckmike.mybatisdemo.model.User;
import com.ckmike.mybatisdemo.service.UserService;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisDemoApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    public void contextLoads() {
        // 1. 通过服务调用,获取users
        List<User> userList = this.userService.getUser();
        System.out.println(userList.get(0).toString());

        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            // 2.通过执行sql获取
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

            SqlSession session = sqlSessionFactory.openSession();

            String sql = "com.ckmike.mybatisdemo.mapper.UserMapper.getUser";
            List<User> list = session.selectList(sql);

            System.out.println(list.get(0).toString());

            // 3.通过获取Mapper
            UserMapper userMapper = session.getMapper(UserMapper.class);
            List<User> users = userMapper.getUser();
            System.out.println(users.get(0).toString());

            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

分析:
上面我们总共用来三种方式去调用mybatis执行sql语句,返回结果。
1.通过SpringBoot初始化项目,注入UserService调用UserMapper接口代理实例返回mybatis执行结果。
2.通过mybatis配置文件,构建一个SqlSessionFactory,开启一个SqlSession,通过执行UserMapper接口映射的方法返回结果。
3.通过SqlSession获取到UserMapper接口的代理实例,直接调用接口执行SQL返回结果。

其实上面1和3是同一种方式。只是前者通过application配置初始化了SqlSessionFactory,后续跟3是一致的。

那么我们就通过2和3两种方式开始解读这个mybatis源码,看看底层代码的设计实现是怎么样。

SqlSessionFactoryBuilder

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

package org.apache.ibatis.session;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

public class SqlSessionFactoryBuilder {
    public SqlSessionFactoryBuilder() {
    }

    public SqlSessionFactory build(Reader reader) {
        return this.build((Reader)reader, (String)null, (Properties)null);
    }

    public SqlSessionFactory build(Reader reader, String environment) {
        return this.build((Reader)reader, environment, (Properties)null);
    }

    public SqlSessionFactory build(Reader reader, Properties properties) {
        return this.build((Reader)reader, (String)null, properties);
    }

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                reader.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

    public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment) {
        return this.build((InputStream)inputStream, environment, (Properties)null);
    }

    public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return this.build((InputStream)inputStream, (String)null, properties);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

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

上面这个SqlSessionFactoryBuilder类非常的简单,就是构建SqlSessionFactory的工具类。可以通过Configuration类初始化一个SqlSessionFactory,也可以通过文件流的形式加载XML配置文件初始化SqlSessionFactory,不管Configuration也好、文件流也好,都是通过XML文件去构造Configuration或者文件流,核心就是Mybatis-config配置构造SqlSessionFactory.最终都是得到一个DefaultSqlSessionFactory(实现了SqlSessionFactory接口)。

DefaultSqlSessionFactory


package org.apache.ibatis.session.defaults;

import java.sql.Connection;
import java.sql.SQLException;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(boolean autoCommit) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }

    public SqlSession openSession(ExecutorType execType) {
        return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(TransactionIsolationLevel level) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), level, false);
    }

    public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
        return this.openSessionFromDataSource(execType, level, false);
    }

    public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
        return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, autoCommit);
    }

    public SqlSession openSession(Connection connection) {
        return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);
    }

    public SqlSession openSession(ExecutorType execType, Connection connection) {
        return this.openSessionFromConnection(execType, connection);
    }

    public Configuration getConfiguration() {
        return this.configuration;
    }

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        DefaultSqlSession var8;
        try {
            boolean autoCommit;
            try {
                autoCommit = connection.getAutoCommit();
            } catch (SQLException var13) {
                autoCommit = true;
            }

            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            Transaction tx = transactionFactory.newTransaction(connection);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var14, var14);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

    private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        return (TransactionFactory)(environment != null && environment.getTransactionFactory() != null ? environment.getTransactionFactory() : new ManagedTransactionFactory());
    }

    private void closeTransaction(Transaction tx) {
        if (tx != null) {
            try {
                tx.close();
            } catch (SQLException var3) {
                ;
            }
        }

    }
}

这个SqlSessionFactory接口的实现就是获得一个指定数据源的SqlSession.

SqlSession

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

package org.apache.ibatis.session;

import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;

public interface SqlSession extends Closeable {
    <T> T selectOne(String var1);

    <T> T selectOne(String var1, Object var2);

    <E> List<E> selectList(String var1);

    <E> List<E> selectList(String var1, Object var2);

    <E> List<E> selectList(String var1, Object var2, RowBounds var3);

    <K, V> Map<K, V> selectMap(String var1, String var2);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);

    <T> Cursor<T> selectCursor(String var1);

    <T> Cursor<T> selectCursor(String var1, Object var2);

    <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);

    void select(String var1, Object var2, ResultHandler var3);

    void select(String var1, ResultHandler var2);

    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);

    int insert(String var1);

    int insert(String var1, Object var2);

    int update(String var1);

    int update(String var1, Object var2);

    int delete(String var1);

    int delete(String var1, Object var2);

    void commit();

    void commit(boolean var1);

    void rollback();

    void rollback(boolean var1);

    List<BatchResult> flushStatements();

    void close();

    void clearCache();

    Configuration getConfiguration();

    <T> T getMapper(Class<T> var1);

    Connection getConnection();
}

这个接口定义了所有的CRUD操作,以及回滚,事务提交、getMapper等。实现这个SqlSession接口的类有DefaultSqlSession类、SqlSessionTemplate类,可以看下他们的具体实现。

session.selectList(sql)

String sql = "com.ckmike.mybatisdemo.mapper.UserMapper.getUser";
session.selectList(sql);
最终执行是如下源代码:是根据UserMapper接口映射绑定到UserMapper.xml文件的SQL,执行处理后返回结果。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

getMapper

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

MapperRegistry

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

所有Mapper接口都要在这里注册等级,MapperProxyFactory代理工厂会代理实例化一个Mapper实例返回。

MapperProxyFactory

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

MapperProxy

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(Class.class, Integer.TYPE);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }

        Class<?> declaringClass = method.getDeclaringClass();
        return ((Lookup)constructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

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

看到这里大家应该知道Mybatis是通过对Mapper接口做JDK的动态代理,然后通过代理MapperProxyFactory去实例化Mapper,每MapperProxyFactory都需要在MapperRegistry进行注册。这是不是就是我们非常熟悉的工厂方法模式。

说道这里我们只是清楚了我们调用Mapper接口时,是如何获取到代理后的Mapper实例,我们接下来继续往下看,我们在调用Mapper接口中绑定的SQL时,参数是如何解析的,结果集又是如何映射为我们的model集合的。

MapperMethod

下面是MapperProxy中invoke代码:

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

MapperMethod源码

package org.apache.ibatis.binding;

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(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            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 FLUSH:
            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 = rowCount > 0;
            } else {
                result = (long)rowCount;
            }
        } else {
            result = 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);
        }

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

    private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        Cursor result;
        if (this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectCursor(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectCursor(this.command.getName(), param);
        }

        return result;
    }

    private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
        Object collection = config.getObjectFactory().create(this.method.getReturnType());
        MetaObject metaObject = config.newMetaObject(collection);
        metaObject.addAll(list);
        return collection;
    }

    private <E> Object convertToArray(List<E> list) {
        Class<?> arrayComponentType = this.method.getReturnType().getComponentType();
        Object array = Array.newInstance(arrayComponentType, list.size());
        if (!arrayComponentType.isPrimitive()) {
            return list.toArray((Object[])((Object[])array));
        } else {
            for(int i = 0; i < list.size(); ++i) {
                Array.set(array, i, list.get(i));
            }

            return array;
        }
    }

    private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        Map result;
        if (this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectMap(this.command.getName(), param, this.method.getMapKey(), rowBounds);
        } else {
            result = sqlSession.selectMap(this.command.getName(), param, this.method.getMapKey());
        }

        return result;
    }

    public static class MethodSignature {
        private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;
        private final boolean returnsCursor;
        private final Class<?> returnType;
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final ParamNameResolver paramNameResolver;

        public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
            if (resolvedReturnType instanceof Class) {
                this.returnType = (Class)resolvedReturnType;
            } else if (resolvedReturnType instanceof ParameterizedType) {
                this.returnType = (Class)((ParameterizedType)resolvedReturnType).getRawType();
            } else {
                this.returnType = method.getReturnType();
            }

            this.returnsVoid = Void.TYPE.equals(this.returnType);
            this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
            this.returnsCursor = Cursor.class.equals(this.returnType);
            this.mapKey = this.getMapKey(method);
            this.returnsMap = this.mapKey != null;
            this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
            this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
            this.paramNameResolver = new ParamNameResolver(configuration, method);
        }

        public Object convertArgsToSqlCommandParam(Object[] args) {
            return this.paramNameResolver.getNamedParams(args);
        }

        public boolean hasRowBounds() {
            return this.rowBoundsIndex != null;
        }

        public RowBounds extractRowBounds(Object[] args) {
            return this.hasRowBounds() ? (RowBounds)args[this.rowBoundsIndex] : null;
        }

        public boolean hasResultHandler() {
            return this.resultHandlerIndex != null;
        }

        public ResultHandler extractResultHandler(Object[] args) {
            return this.hasResultHandler() ? (ResultHandler)args[this.resultHandlerIndex] : null;
        }

        public String getMapKey() {
            return this.mapKey;
        }

        public Class<?> getReturnType() {
            return this.returnType;
        }

        public boolean returnsMany() {
            return this.returnsMany;
        }

        public boolean returnsMap() {
            return this.returnsMap;
        }

        public boolean returnsVoid() {
            return this.returnsVoid;
        }

        public boolean returnsCursor() {
            return this.returnsCursor;
        }

        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
            Integer index = null;
            Class<?>[] argTypes = method.getParameterTypes();

            for(int i = 0; i < argTypes.length; ++i) {
                if (paramType.isAssignableFrom(argTypes[i])) {
                    if (index != null) {
                        throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
                    }

                    index = i;
                }
            }

            return index;
        }

        private String getMapKey(Method method) {
            String mapKey = null;
            if (Map.class.isAssignableFrom(method.getReturnType())) {
                MapKey mapKeyAnnotation = (MapKey)method.getAnnotation(MapKey.class);
                if (mapKeyAnnotation != null) {
                    mapKey = mapKeyAnnotation.value();
                }
            }

            return mapKey;
        }
    }

    public static class SqlCommand {
        private final String name;
        private final SqlCommandType type;

        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String methodName = method.getName();
            Class<?> declaringClass = method.getDeclaringClass();
            MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
            if (ms == null) {
                if (method.getAnnotation(Flush.class) == null) {
                    throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
                }

                this.name = null;
                this.type = SqlCommandType.FLUSH;
            } else {
                this.name = ms.getId();
                this.type = ms.getSqlCommandType();
                if (this.type == SqlCommandType.UNKNOWN) {
                    throw new BindingException("Unknown execution method for: " + this.name);
                }
            }

        }

        public String getName() {
            return this.name;
        }

        public SqlCommandType getType() {
            return this.type;
        }

        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
            String statementId = mapperInterface.getName() + "." + methodName;
            if (configuration.hasStatement(statementId)) {
                return configuration.getMappedStatement(statementId);
            } else if (mapperInterface.equals(declaringClass)) {
                return null;
            } else {
                Class[] var6 = mapperInterface.getInterfaces();
                int var7 = var6.length;

                for(int var8 = 0; var8 < var7; ++var8) {
                    Class<?> superInterface = var6[var8];
                    if (declaringClass.isAssignableFrom(superInterface)) {
                        MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
                        if (ms != null) {
                            return ms;
                        }
                    }
                }

                return null;
            }
        }
    }

    public static class ParamMap<V> extends HashMap<String, V> {
        private static final long serialVersionUID = -2212268410512043556L;

        public ParamMap() {
        }

        public V get(Object key) {
            if (!super.containsKey(key)) {
                throw new BindingException("Parameter ‘" + key + "‘ not found. Available parameters are " + this.keySet());
            } else {
                return super.get(key);
            }
        }
    }
}

看上面的代码我们对于理解SQL参数如何解析,结果集如何被转化为我们的model集合就有很深入的理解了,同时这里面会做一次基于Method缓存,其实从我的demo里就可以知道,获取User只执行了一次,第二次直接从缓存中获取结果返回。

说道这里我们第一遍mybatis的解读就到这里,有兴趣的同学可以按照demo进行断点测试,追踪整个过程。谢谢!打完收工。如果发现有错误,请留意告诉我,我会第一时间修改,以防止误导读者。

原文地址:http://blog.51cto.com/4837471/2331138

时间: 2024-10-14 11:13:54

MyBatis源码解读的相关文章

MyBatis源码解读(3)——MapperMethod

在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在初学MyBatis的时候几乎每个人都会发出一个疑问,为什么明明是XXXDao接口,我没有用任何代码实现这个接口,但却能直接使用这个接口的方法.现在清楚了,动态代理.我们来写一个demo小程序来看看. 首先是一个Test.java的接口,只有一个say方法. 1 package day_16_prox

MyBatis源码解读(1)——SqlSessionFactory

在前面对MyBatis稍微有点了解过后,现在来对MyBatis的源码试着解读一下,并不是解析,暂时定为解读.所有对MyBatis解读均是基于MyBatis-3.4.1,官网中文文档:http://www.mybatis.org/mybatis-3/zh/getting-started.html,MyBatis-3.4.1.jar. 本应在开始读MyBatis源码时首先应该了解下MyBatis的SqlSession的四大对象:Executor.StatemenHandler.ParameterHa

【转】Mybatis源码解读-设计模式总结

原文:http://www.crazyant.net/2022.html?jqbmtw=b90da1&gsjulo=kpzaa1 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少遇到了以下的设计模式的使用: Builder模式,例如SqlSessionFactoryBuilder.XMLConfigBuilder.XMLMapperBuilder

Mybatis源码解读-9种设计模式总结

虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式.Mybatis至少遇到了以下的设计模式的使用: Builder模式,例如SqlSessionFactoryBuilder.XMLConfigBuilder.XMLMapperBuilder.XMLStatementBuilder.CacheBuilder:工厂模式,例如SqlSessionFactory.ObjectF

MyBatis源码解读(4)——SqlSession(上)

在上一篇博客中提到MyBatis是如何实现代理类MapperProxy,并抛出了一个问题--是怎么执行一个具体的sql语句的,在文末中提到了MapperMethod的execute采用命令模式来判断是何种sql语句,并将具体语句的执行交由SqlSession处理.所以此篇博客正是要讲到SqlSession. 在SqlSession接口中包含了所有可能执行的sql语句在这里不一一列举,请参考org.apache.ibatis.session.SqlSession源码.DefaultSqlSessi

mybatis源码解读(三)——数据源的配置

在mybatis-configuration.xml 文件中,我们进行了如下的配置: <!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境常用: 一.development:开发模式 二.work:工作模式 --> <environments default="development"> <!--id属性必须和上面的default一样 --> <environment id="deve

MyBatis源码解读(2)——MapperProxy

SqlSession可以说是整个MyBatis的重中之重,在SqlSession中涉及到前一篇四大对象:Executor.StatementHandler.ParameterHandler.ResultHandler,所以在此先只对SqlSession有一个大概的了解. 在代码中我们可以看到当我们构造出一个SqlSession实例过后,可以通过SqlSession构造出Mappper映射器.UserMapper是一个接口,那么我们可以肯定的是,它一定是用了Java的动态代理生成了一个代理类. S

源码解读Mybatis List列表In查询实现的注意事项

转自:http://www.blogjava.net/xmatthew/archive/2011/08/31/355879.html 源码解读Mybatis List列表In查询实现的注意事项 在SQL开发过程中,动态构建In集合条件查询是比较常见的用法,在Mybatis中提供了foreach功能,该功能比较强大,它允许你指定一个集合,声明集合项和索引变量,它们可以用在元素体内.它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符.这个元素是很智能的,它不会偶然地附加多余的分隔符.下面是一个演

深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)[转]

上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)>我们通过对mybatis源码的简单分析,可看出,在mybatis配置文件中,在configuration根节点下面,可配置properties.typeAliases.plugins.objectFactory.objectWrapperFactory.settings.environments.databaseIdProvider.typeHandlers.mappers这些节点.那么本次,就会先介绍prope