基本概念

MyBatis 使用 Mapper Proxy 动态代理实现对数据库操作的封装,用户只需定义一个接口(Mapper 接口),在接口方法上通过注解或 XML 文件指定 SQL 语句的执行逻辑,而不需要手动实现接口的方法。

  • Mapper 接口:定义了数据库操作方法的接口,例如 insertUser, getUserById 等。
  • 动态代理:在运行时生成接口实现类的实例,将方法调用映射为对应的 SQL 操作。

MyBatis 的 Mapper Proxy 是一个实现了数据访问层接口的动态代理机制,用于在运行时将接口方法映射到特定的 SQL 语句执行逻辑。

主要组件

  • SqlSession:MyBatis 的核心接口,用于执行 SQL 语句和管理事务。Mapper Proxy 通过 SqlSession 来执行数据库操作。
  • Mapper Proxy:MyBatis 提供的动态代理实现类,用于代理 Mapper 接口。
  • Mapper Proxy Factory:MapperProxyFactory 是用于创建 MapperProxy 对象的工厂类。每个 Mapper 接口对应一个 MapperProxyFactory 实例。

优势与局限性

优势

  • 解耦:开发者只需定义接口和 SQL,无需关注具体实现。
  • 简洁性:代码量减少,尤其适合简单的 CRUD 操作。
  • 动态性:基于动态代理,无需生成实际的实现类。

局限性

  • 性能开销:动态代理在运行时生成对象,性能略低于手写实现类。
  • 可读性:当 Mapper 接口较多或方法复杂时,调试和维护可能变得困难。
  • 复杂逻辑限制:复杂查询和多表关联操作需要依赖 XML 或其他方式实现。

额外扩展

  • 使用 XML 配置或注解配置定义 SQL。
  • 可以通过插件(Interceptors)扩展 MapperProxy 的行为,例如记录 SQL 执行时间、日志输出等。

运行机制

获取 Mapper 的实例

当通过 SqlSession.getMapper(Class type) 获取一个 Mapper 接口的实例时,MyBatis 会执行以下步骤:

  1. 代理工厂创建代理对象:MyBatis 使用 MapperProxyFactory 来生成一个 MapperProxy 的动态代理对象。
  2. MapperProxyFactory 内部调用 JDK 的动态代理工具生成代理类。
  3. 生成的代理类实现了用户定义的 Mapper 接口。
  4. 返回代理对象:该代理对象在表面上是用户定义的接口的实现类,但其方法调用会被拦截并处理。

方法调用拦截

当调用 Mapper 接口中的方法时,实际上会触发 MapperProxy 的 invoke 方法:

  1. 判断方法所属:MapperProxy 会首先检查方法是否是 Object 类的方法(例如 toString、hashCode 等),这些方法会直接调用 Object 的实现。
  2. 获取 SQL 映射:如果方法是 Mapper 接口的方法,MapperProxy 会通过 cachedMapperMethod 方法查找或创建 MapperMethod。MapperMethod 是一个内部工具类,表示 Mapper 接口方法和 SQL 语句的映射关系。
  3. 执行 SQL 操作:通过 MapperMethod.execute() 方法调用对应的 SQL。

SQL 执行

MapperMethod 执行时会调用 SqlSession 提供的数据库操作方法,例如:

  • selectOne:执行单条记录查询
  • selectList:执行多条记录查询
  • insert:执行插入操作
  • update:执行更新操作
  • delete:执行删除操作

Mapper 代理

示例代码:

public class WzkicuPage01 {
    public static void main(String[] args) throws Exception {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        PageHelper.startPage(2, 1);
        List<WzkUser> dataList = userMapper.selectList();
        for (WzkUser wzk : dataList) {
            System.out.println(wzk);
        }
        sqlSession.close();
    }
}

MyBatis 初始化时对接口的处理,MapperRegistry 是 Configuration 中的一个属性,它内部维护一个 HashMap 用于存放 mapper 接口的工厂类,每个接口对应一个工厂类。

<mappers>
    <mapper resource="mapper.xml"/>
    <mapper resource="OrderMapper.xml"/>
    <mapper resource="UserMapper.xml"/>
    <mapper resource="RoleMapper.xml"/>
    <mapper resource="UserCacheMapper.xml"/>
</mappers>

当解析 mappers 标签的时候,会将对应的配置文件中的增删改查封装成 MappedStatement 对象,存入到 MappedStatements 中。

getMapper 源码解析

// DefaultSqlSession 中的 getMapper
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

// Configuration 中的 getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

// MapperRegistry 中的 getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从 MapperRegistry 中的 HashMap 中获取 MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 通过动态代理工厂生成实例
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

// MapperProxyFactory 类中的 newInstance 方法
public T newInstance(SqlSession sqlSession) {
    // 创建 JDK 动态代理的 Handler 类
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 调用重载方法
    return newInstance(mapperProxy);
}

// MapperProxy 类,实现了 InvocationHandler 接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
    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;
    }
    // 省略其他实现部分
}

invoke

在动态代理之后,执行时在 MapperProxy 中的 invoke 方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 1. 如果是 Object 类中定义的方法,直接调用
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }

        // 2. 如果是默认方法,调用默认方法
        else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        // 3. 捕获并解包异常
        throw ExceptionUtil.unwrapThrowable(t);
    }

    // 4. 获取 MapperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);

    // 5. 执行 MapperMethod 对应的方法
    return mapperMethod.execute(sqlSession, args);
}

execute 方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;

    // Determine the type of operation based on the command's type
    switch (command.getType()) {
        case INSERT:
            result = executeInsert(sqlSession, args);
            break;
        case UPDATE:
            result = executeUpdate(sqlSession, args);
            break;
        case DELETE:
            result = executeDelete(sqlSession, args);
            break;
        case SELECT:
            result = executeSelect(sqlSession, args);
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }

    // Check for null result and primitive return type, throw exception if necessary
    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;
}

private Object executeInsert(SqlSession sqlSession, Object[] args) {
    Object param = method.convertArgsToSqlCommandParam(args);
    int rowCount = sqlSession.insert(command.getName(), param);
    return rowCountResult(rowCount);
}

private Object executeUpdate(SqlSession sqlSession, Object[] args) {
    Object param = method.convertArgsToSqlCommandParam(args);
    int rowCount = sqlSession.update(command.getName(), param);
    return rowCountResult(rowCount);
}

private Object executeDelete(SqlSession sqlSession, Object[] args) {
    Object param = method.convertArgsToSqlCommandParam(args);
    int rowCount = sqlSession.delete(command.getName(), param);
    return rowCountResult(rowCount);
}

private Object executeSelect(SqlSession sqlSession, Object[] args) {
    if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        return null;
    } else if (method.returnsMany()) {
        return executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
        return executeForMap(sqlSession, args);
    } else if (method.returnsCursor()) {
        return executeForCursor(sqlSession, args);
    } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        Object result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
            return Optional.ofNullable(result);
        }
        return result;
    }
}