MyBatis接口绑定原理

获取代理对象

public class App1 {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        InputStream is = App1.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 获取 SqlSession, 代表和数据库的一次会话, 用完需要关闭
        // SqlSession 和 Connection, 都是非线程安全的, 每次使用都应该去获取新的对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取实现接口的代理对象
        // UserMapper 并没有实现类, 但是mybatis会为这个接口生成一个代理对象(将接口和xml绑定)
        // 这里返回的是一个 MapperProxy 代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        sqlSession.close();
    }
}

从上面可以看出,通过SqlSession来获取代理类,SqlSession对象表示MyBaits框架与数据库建立的会话,我们可以通过SqlSession实例完成对数据库的增删改查操作。SqlSession是一个接口

// SqlSession.java
public interface SqlSession extends Closeable {
    <T> T selectOne(String statement);
    <T> T selectOne(String statement, Object parameter);
    <E> List<E> selectList(String statement);
    <E> List<E> selectList(String statement, Object parameter);
    <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
    <K, V> Map<K, V> selectMap(String statement, String mapKey);
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
    <T> Cursor<T> selectCursor(String statement);
    <T> Cursor<T> selectCursor(String statement, Object parameter);
    <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
    void select(String statement, Object parameter, ResultHandler handler);
    void select(String statement, ResultHandler handler);
    void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
    int insert(String statement);
    int insert(String statement, Object parameter);
    int update(String statement);
    int update(String statement, Object parameter);
    int delete(String statement);
    int delete(String statement, Object parameter);
    void commit();
    void commit(boolean force);
    void rollback();
    void rollback(boolean force);
    List<BatchResult> flushStatements();
    @Override
    void close();
    void clearCache();
    Configuration getConfiguration();
    // 根据接口类型获取接口对应的代理
    <T> T getMapper(Class<T> type);
    Connection getConnection();
}

SqlSession的默认实现为DefaultSqlSession

// DefaultSqlSession.java
public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
    private boolean dirty;
    private List<Cursor<?>> cursorList;
}

@Override
public <T> T getMapper(Class<T> type) {
    // MyBatis初始化时已经把Mapper接口对应的代理信息保存到了 Configuration 中
    // 这里直接从 Configuration 中获取代理类
    // 这里返回的是一个 MapperProxy
    // 找到Configuration类的getMapper方法,该方法比较简单
    // 主要发生如下几个步骤
    // Map<Class<?>, MapperProxyFactory<?>> knownMappers
    // 1. 从 Configuration 获取 Mapper 接口对应的 MapperProxyFactory
    // 2. MapperProxyFactory 为 Mapper 接口生成 MapperProxy 代理对象并返回
    return configuration.<T>getMapper(type, this);
}

MapperProxy

MyBatis中通过MapperProxy类实现动态代理。下面是MapperProxy类的关键代码:

// MapperProxy.java
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    // Mapper 接口
    private final Class<T> mapperInterface;
    // Mapper 接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系
    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;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是Object中定义的方法,直接执行。如toString(),hashCode()等
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 将 Method 转换成 MapperMethod 并保存到 methodCache 中
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 这里面将会执行 Mapper接口对应的 SQL
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
            // 通过 Configuration 获取对应的 MappedStatement
            // 通过 MappedStatement 获取 MapperMethod 需要的各种信息
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
}

MapperProxy使用的是JDK内置的动态代理,实现了InvocationHandler接口,invoke()方法中为通用的拦截逻辑。当我们调用自己Mapper接口中的方法时,其实就是在调用MapperProxy的invoke()方法。

由上面的分析可以知道,通过MapperMethod来执行代理方法。

MapperMethod

public class MapperMethod {
    // 里面有两个属性
    // name: 要执行的方法名,如com.example.demo.UserMapper.getUserByUserName
    // type: SQL标签的类型 insert update delete select
    private final SqlCommand command;
    // 封装了该方法的参数信息、返回类型信息等 
    private final MethodSignature method;

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

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // 根据insert、update、delete、select调用不同的方法
        switch (command.getType()) {
            case INSERT: {
                /**
                 * args是用户 Mapper 所传递的方法参数列表 
                 * 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
                 * 如果包含多个参数或包含 @Param 注解修饰的参数,则返回包含名字和对应值的Map对象
                 */
                Object param = method.convertArgsToSqlCommandParam(args);

                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName() 
                                       + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }
}

从上面可以看出,数据的增删改查都是通过SqlSession来实现的。

MethodSignature

MethodSignatureMapperMethod的内部类。MethodSignatureMapperMethod类提供了三个作用。

  1. 获取待执行方法中的参数和@Param注解标注的参数名
  2. 获取标注有@MapKey的值
  3. 获取SELECT操作时必要的标志位。
public static class MethodSignature {
    // 是否多值查询
    private final boolean returnsMany;
    // 是否map查询
    private final boolean returnsMap;
    // 是否void查询
    private final boolean returnsVoid;
    // 是否游标查询
    private final boolean returnsCursor;
    // 返回类型
    private final Class<?> returnType;
    // 获取mapKey的值
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    // 参数解析器
    private final ParamNameResolver paramNameResolver;

    /**
     * args是用户 Mapper 所传递的方法参数列表 
     * 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
     * 如果包含多个参数或包含命名参数,则返回包含名字和对应值的Map对象
     */
    public Object convertArgsToSqlCommandParam(Object[] args) {
        return paramNameResolver.getNamedParams(args);
    }
}

ParamNameResolver

public class ParamNameResolver {

    private static final String GENERIC_NAME_PREFIX = "param";

    // 保存方法参数列表的参数名
    // 例如方法:getUser(String userName, Integer age)
    // names.get(0) = arg0,names.get(1) = arg1

    // 例如方法:getUser(@Param(value = "userName") String userName, Integer age)
    // names.get(0) = 'userName',names.get(1) = arg1

    // key存储参数所在位置的下标
    // value为参数名,如果存在@Param,则使用@Param注解里面的值,否则 value = 'arg' + 参数下标索引值
    private final SortedMap<Integer, String> names;

    // 方法参数列表是否存在 @Param 注解的参数
    private boolean hasParamAnnotation;

    /**
     * args是用户 Mapper 所传递的方法参数列表 
     * 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
     * 如果包含多个参数或包含 @Param 注解修饰的参数,则返回包含名字和对应值的Map对象
     */
    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        if (args == null || paramCount == 0) {
            // 如果方法参数为空,则直接放回 null 
            return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
            // 如果方法参数没有使用 @Param 注解修饰的参数,并且只有一个参数
            // 这里直接返回参数值,相当于是返回args[0]
            return args[names.firstKey()];
        } else {
            // 创建一个Map对象
            // key 为参数名,value为参数值
            final Map<String, Object> param = new ParamMap<Object>();
            int i = 0;
            for (Map.Entry<Integer, String> entry : names.entrySet()) {

                param.put(entry.getValue(), args[entry.getKey()]);

                // add generic param names (param1, param2, ...)
                final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
                // ensure not to overwrite parameter named with @Param
                if (!names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!