`
wen866595
  • 浏览: 264554 次
  • 性别: 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 代理实现数据库调用原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    springBoot结合Mybatis的使用,其中包括了Mybaits的xml配置使用、Mybatis部分源码分析等内容

    通过controller层调用service层业务逻辑,再通过service层调用mapper来操作数据库,基于MVC设计模式进行开发demo。 其中使用德鲁伊作为数据库连接池,并且配置了mybatis配置类并交给SpringBoot管理。 在demo中通过...

    通过MyBatis读取数据库数据并提供rest接口访问

    主要介绍了通过MyBatis读取数据库数据并提供rest接口访问 的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

    mybatis和mybatis plus比较详解

    相比之下,MyBatis Plus则提供了更为丰富的功能,如自动注入基本CRUD操作、强大的条件构造器、支持Lambda形式调用等,这些特性使得开发者能够更高效地进行数据库操作。 ## 2.2 使用方式 MyBatis在使用时需要开发者...

    springmybatis

    mybatis实战教程mybatis in action之七实现mybatis分页源码下载 mybatis实战教程mybatis in action之八mybatis 动态sql语句 mybatis实战教程mybatis in action之九mybatis 代码生成工具的使用 mybatis ...

    5A推荐毕业设计:基于SSM实现的科研管理系统

    整合 MyBatis:配置 MyBatis 数据库连接等相关配置,编写 SQL Mapper 接口和 SQL 映射文件,实现对数据库的操作。 RESTful API 设计:采用 RESTful 风格设计 API,提供清晰的接口,方便前端调用。 前端部分 前端...

    mybatis-generator-core-1.3.2Java工程,可直接导入eclipse生产model和mapper以及到

    mybatis-generator-core-1.3.2自动生成数据库表中对应的dao和model以及mapper文件,灰常方便,压缩包是个Java工程,解压后可以直接导入到自己的eclipse中,然后修改一下唯一的配置文件generatorConfig.xml里面的...

    springboot+shiro+mybatis-plus纯净版框架(附带所需数据库sql)

    mybatis-plus实现了简易版的controller,service,mapper自动生成和分页(3.4.0) 在com.example.demo.mp.MpGenerator直接执行main方法(velocity 2.3) 备注:生成的mapper需要手动加上注解@Mapper,否则会报错 整个...

    MyBatis-CMEU是基于javafx8开发的一款图形界面的Mybatis逆向工程;

    Mybatis-CMEU全称为:Mybatis Config Mapper Util ;是基于javafx8开发的一款图形界面的Mybatis逆向工程;该工具支持Oracle , SqlServer , MySQL , PostgreSql数据库的逆向生成;

    mybatis-config.xml

    mybatis通过配置文件关联到各实体类的Mapper文件,Mapper文件中配置了每个类对数据库所需进行的sql语句映射。在每次与数据库交互时,通过sqlSessionFactory拿到一个sqlSession,再执行sql命令。

    mybatis基本使用

    以下是一个基本的 MyBatis 测试使用的描述: 1. 环境准备 首先,确保你已经安装了 Java 和 Maven,并且有一个可用的数据库环境(如 ...通过 SqlSession,你可以获取 Mapper 接口的实例,并调用其方法执行数据库操作。

    springboot+mybatis+内置tomcat示例.rar

    mybatis.mapper-locations=classpath:mapping/*Mapper.xml mybatis.type-aliases-package=entity #spring.profiles=development #server.address=127.0.0.1 #端口设置 server.port=8092 #日志配置 ...

    MyBatis 需要注意的地方junit注解

    (1)配置sqlSessionFactory指定要操作的数据库,以及mapper.xml的所在目录 (2)配置指定的dao层接口的目录 3.mybatis的注意事项 1.xml中的sql不得有分号 2.sql语句操作的表明和列名 3.xml中的小于号:$lt;大于...

    深入剖析MyBatis SQL执行流程:从配置到查询结果的全程追踪

    在SqlSession中,通过动态代理机制获取Mapper对象。这些Mapper对象是通过MapperProxyFactory生成的,负责将接口方法调用转换为SQL语句的执行。 当执行Mapper方法时,MyBatis使用MapperProxy的invoke方法创建或获取...

    tool-mybatis:mybatis工具类

    tool-mybatis mail list:朱伟亮 &lt;&gt; mybatis工具提供通用增删改查方法。 主键id生成,noid生成。...修复多层集成自com.gxws.tool.mybatis.mapper.Mapper接口不能调用实现方法的bug。 去除了原有的parent依赖。

    mybatis-salulu.zip

    MyBatis单表查询简单功能手写实现代码 和mybatis官网的使用方式一样,基本实现了相同的功能 1,读取配置文件 2,构建session工厂 3,打开sqlsession ...5,调用mapper对象的方法类执行sql,对数据库进行操作

    基于SSM框架实现电子商城系统带sql数据库文件

    使用spring实现业务对象管理,使用spring MVC负责请求的转发和视图管理,mybatis作为数据对象的持久化引擎。 1)持久层:dao层(mapper)层 作用:主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装...

    Mybatis plus 基于 springBoot 源码

    支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作 支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板...

    基于SSM框架实现电子商城系统带sql数据库文件分享

    使用spring实现业务对象管理,使用spring MVC负责请求的转发和视图管理,mybatis作为数据对象的持久化引擎。 1)持久层:dao层(mapper)层 作用:主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装...

    Mybatis 增强工具包 - 只做增强不做改变,简化CRUD操作

    依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring,损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作,通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD ...

Global site tag (gtag.js) - Google Analytics