`
wen866595
  • 浏览: 268881 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

MyBatis Mapper 代理实现数据库调用原理

阅读更多

1. Mapper 代理层执行

 

Mapper 代理上执行方法调用时,调用被委派给 MapperProxy 来处理。

 

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
        // 接口里声明的方法,转换为 MapperMethod 来调用
        final MapperMethod mapperMethod = cachedMapperMethod(method);

        // 与 Spring 集成时此处的 sqlSession 仍然 SqlSessionTemplate
        return mapperMethod.execute(sqlSession, args);
    }

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

 

MapperMethod 根据 mapperInterface.getName() + "." + method.getName() 从 Configuration 对象里找到对应的 MappedStatement,从而得到要执行的 SQL 操作类型(insert/delete/update/select/flush),然后调用传入的 sqlSession 实例上的相应的方法。

 

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
        // 把参数转换为 SqlSession 能处理的格式
        Object param = method.convertArgsToSqlCommandParam(args);

        // 在 sqlSession 上执行并处理结果
        result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
    ...省略

 

如果上述方法传入的是 SqlSessionTemplate,那么这些方法调用会被 SqlSessionInterceptor 拦截,加入与 Spring 事务管理机制协作的逻辑,具体可以看这篇文章MyBatis 事务管理,这里不再展开,最终会调用到 DefaultSqlSession 实例上的方法。

 

2. 会话层的执行过程

 

SqlSession 里声明的所有方法的第一个参数如果是 String statement,则都是 mapperInterface.getName() + "." + method.getName(),表示要调用的 SQL 语句的标识符。通过它从 configuration 找到 MappedStatement

 

会话层最主要的逻辑是进行参数的包装,获取对应的 MappedStatement,然后调用持有的 Executor 的方法去执行。

 

 

 

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    private Executor executor;

    private boolean autoCommit;
    private boolean dirty;
    private List<Cursor<?>> cursorList;

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

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

 

3. Executor 执行的过程

 

我们知道 JDBC 里有三种数据库语句:java.sql.Statement/PreparedStatement/CallableStatement,每种语句的执行方式是不一样的,MyBatis 创建了 StatementHandler 抽象来表示数据库语句的处理逻辑,有对应的三种具体实现:SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler

 

RoutingStatementHandler 是个门面模式,构建时根据要执行的数据库语句类型实例化 SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 中的一个类作为目标 delegate,并把调用都转给这个 delegate 的方法。

 

不同的 handler 实现实现了对应的:数据库语句的创建、参数化设置、执行语句。

 

通过这层抽象,MyBatis 统一了 Executor 里的执行流程,以下以 SimpleExecutor 的流程为例:
1. 对于传入的 MappedStatement ms,得到 Configuration configuration
2. configuration 通过 ms 的语句类型得到一个 RoutingStatementHandler 的实例(内部有个 delegate 可以委派) handler,并用 InterceptorChainhandler 实例进行装饰。
3. 通过 SimpleExecutor 持有的 Transaction 实例获取对应的数据库连接 connection。
4. handler 通过数据库连接初始化数据库语句 java.sql.Statement或其子类 stmt,设置超时时间和 fetchSize 。
5. 用 handlerstmt 进行参数化处理(比如 PreparedStatement 设置预编译语句的参数值)。
6. handler 执行相应的 stmt 完成数据库操作。
7. 用 ResultSetHandler 对结果集进行处理。ResultSetHandler 会调用 TypeHandler 来进行 Java 类型与数据库列类型之间转换。

 

// SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();

        // 创建 handler 来负责具体的执行
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

        // 创建数据库语句
        stmt = prepareStatement(handler, ms.getStatementLog());

        // 执行数据库操作
        return handler.<E>query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

// Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

// RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根据SQL语句的执行方式创建对应的 handler 实例
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    // 创建数据库连接
    Connection connection = getConnection(statementLog);

    // 创建数据库语句
    Statement stmt = handler.prepare(connection, transaction.getTimeout());

    // 参数化设置
    handler.parameterize(stmt);
    return stmt;
}

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
        return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
        return connection;
    }
}

// BaseStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        // 由具体的子类来创建对应的 Statement 实例
        statement = instantiateStatement(connection);

        // 通用参数设置
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}

// PreparedStatementHandler
protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
            return connection.prepareStatement(sql, keyColumnNames);
        }
    } else if (mappedStatement.getResultSetType() != null) {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
        return connection.prepareStatement(sql);
    }
}

// PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}

 

4. 问题

 

  1. 只在 XML 里定义 SQL、没有对应的 Java 接口类能否使用 MyBatis ?
    答:可以,通过 SqlSession 的方法来调用 XML 里的 SQL 语句。

  2. Mapper 接口类里可以进行方法重载吗?
    答:不能,因为 MyBatis 里根据 类名 + “.” + 方法名 来查找 SQL 语句,重载会导致这样的组合出现多条结果。

 


欢迎关注我的微信公众号: coderbee笔记



 

 

  • 大小: 26.3 KB
1
0
分享到:
评论

相关推荐

    详解MyBatis Mapper 代理实现数据库调用原理

    详解MyBatis Mapper 代理实现数据库调用原理 MyBatis 是一个流行的Java持久层框架,它提供了一个Mapper 代理机制来实现数据库调用。今天,我们将深入探讨MyBatis Mapper 代理实现数据库调用原理,并通过示例代码来...

    SpringBoot集成MyBatis-Plus实现国产数据库适配.docx

    SpringBoot 集成 MyBatis-Plus 实现国产数据库适配 MyBatis-Plus 是一款在 MyBatis 的基础上进行扩展的开源工具包,只做增强不做改变,引入 MyBatis-Plus 不会对您现有的 Mybatis 构架产生任何影响。MyBatis-Plus ...

    Mybatis Mapper的使用

    Mybatis Mapper通过动态代理机制,使得我们可以直接在XML映射文件中定义SQL,然后在接口中声明对应的方法,系统会在运行时自动生成实现类,这样就消除了对实现类的直接依赖。 下面我们将详细探讨Mybatis Mapper的几...

    springboot mybatis mapper.xml 配置

    在Spring Boot集成MyBatis的过程中,`mapper.xml`配置文件起着至关重要的作用。它用于定义SQL语句,实现数据库的CRUD操作。本示例将深入探讨如何配置`mapper.xml`,并提供新增、修改、删除、查询及分页查询的实践...

    Mybatis的Mapper方式整合elasticsearch的DSL调用,基于接口和代理生成bean注入的方式进行调用

    这样,每次调用`EsSearchMapper`的方法时,实际上是在调用代理类的方法。 4. **实现DSL构建**:在代理类中,根据接口方法的参数和业务逻辑构建DSL查询。可以使用Elasticsearch的Java API来构建DSL。 5. **执行查询...

    Java的MyBatis框架中Mapper映射配置的使用及原理解析

    在MyBatis中,Mapper是实现数据库操作的关键组件,它允许开发者将SQL语句与Java代码分离,使得代码更加清晰、易于维护。Mapper映射配置文件是MyBatis的核心组成部分,用于定义SQL查询和结果映射。 首先,我们来看...

    spring和mybatis整合(mapper代理自动扫描方式实现)

    通过以上步骤,我们就完成了Spring和MyBatis的整合,利用Mapper代理自动扫描的方式实现了数据库操作。这种方式减少了手动创建SqlSessionTemplate或SqlSessionDaoSupport的繁琐工作,提高了开发效率。与传统的基于DAO...

    mybatis简单实现对数据库的增删改查

    在“mybatis简单实现对数据库的增删改查”这个主题中,我们将探讨如何通过MyBatis框架来执行基本的数据库操作。以下是一些关键知识点: 1. **MyBatis安装与配置**: - 首先,你需要在项目中引入MyBatis的jar包或者...

    关于mybatis mapper类注入失败的解决方案

    本文将介绍MyBatis Mapper类注入失败的解决方案,并解释其背后的原理。 Mapper类注入失败的原因 -------------------- 在使用MyBatis时,Mapper类是MyBatis的核心组件之一。它负责将Java对象与数据库表之间的映射...

    mybatis Mapper.xml中传参多选 字符串形式逗号分隔 AND中拼接OR.rar

    Mapper.xml文件是Mybatis的核心组件,它包含了数据库操作的SQL语句和映射规则。 在处理字符串形式的多选参数时,我们通常会先在服务层将这些参数转换为Java集合,如List或Set。例如,如果用户选择的标签以逗号分隔...

    mybatis开发dao之mapper代理方式

    总结来说,MyBatis的Mapper代理方式是通过定义Mapper接口,配合XML映射文件,实现了数据库操作与业务逻辑的解耦。在实际应用中,我们可以根据需求灵活定义Mapper接口,以满足各种复杂的数据库交互场景。通过这种方式...

    spring-boot+tk.mybatis通用mapper

    本教程将深入探讨如何在Spring Boot项目中集成tk.mybatis通用Mapper,实现高效的数据访问。 首先,我们需要理解Spring Boot的核心特性:自动配置。Spring Boot通过扫描类路径并根据存在的依赖来自动配置相应的Bean...

    mybatis通用mapper

    MyBatis通用Mapper是一款在MyBatis框架基础上封装的工具,旨在简化开发过程中对数据库 CRUD(创建、读取、更新、删除)操作的繁琐过程。它通过提供预定义的SQL映射方法,使得开发者可以无需编写大量的XML配置文件,...

    spring+mybatis+mysql实现的用户登录功能

    3. **Spring配置**:设置Spring MVC控制器,接收并处理登录请求,调用MyBatis的Mapper方法查询数据库。 4. **安全验证**:使用Spring Security配置用户认证,比较输入的密码与数据库中存储的哈希值,进行登录验证。 ...

    Sptring Boot整合mybatis(连接数据库测试及md5加密)

    在本项目中,"Spring Boot整合mybatis(连接数据库测试及md5加密)"是一个适合初学者的教程,旨在教你如何将流行的Spring Boot框架与MyBatis ORM(对象关系映射)工具结合,同时实现数据库连接和MD5加密功能。...

    MyBatis Mapper v2.2.1.zip

    这个压缩包包含了MyBatis Mapper的源码,对于学习和理解MyBatis的工作原理以及如何自定义Mapper接口和XML映射文件具有很高的参考价值。 首先,我们来深入了解MyBatis的核心概念: 1. **动态SQL**:MyBatis的一大...

    mybatis自动生成mapper文件

    MyBatis Generator(MBG)是一款强大的工具,用于自动生成MyBatis的Mapper接口、XML映射文件以及对应的实体类。这款工具极大地提高了开发效率,减少了手动编写这些基础代码的工作量,使得开发者能够更专注于业务逻辑...

    mybatis动态创建数据库表

    总结,MyBatis动态创建数据库表的能力主要依赖于与数据库交互的SQL语句,通过自定义Mapper和XML配置文件,可以实现数据库表的创建、数据的插入和更新。在实际应用中,这使得开发更加灵活,可以根据需求动态管理...

    用aspectj拦截mybatis mapper的一种可行方案

    在Java开发中,MyBatis是一个非常流行的持久层框架,用于简化数据库操作。然而,有时候我们需要在特定的Mapper方法执行前后添加自定义的行为,如日志记录、事务控制或性能监控。这时,AspectJ就可以派上用场。...

    mybatis 生成bean,mapper,service代码

    MyBatis 是一款深受开发者喜爱的 Java 持久层框架,它简化了与数据库交互的繁琐工作,通过 XML 或注解方式配置映射规则,实现了 SQL 语句与 Java 代码的分离,大大提高了开发效率。在本项目中,我们关注的是如何使用...

Global site tag (gtag.js) - Google Analytics