Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。

六步流程:

  • 加载驱动(5.x驱动包不需要这步了)
  • 建立连接
  • 创建Statement
  • 执行SQL语句
  • 获取结果集
  • 关闭资源

这里只取后面几步分析下,基本上都是从Executor开始。DefaultSqlSession被每个Mapper持有,将各种基于SQL的操作转移到调用Executor的query和update方法。

Executor的类图如下:

以ReuseExecutor的doQuery方法为例:

  1. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  2. Configuration configuration = ms.getConfiguration();
  3. StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
  4. Statement stmt = prepareStatement(handler, ms.getStatementLog());
  5. return handler.<E>query(stmt, resultHandler);
  6. }

建立连接

上面ReuseExecutor的doQuery方法调用其prepareStatement方法:

  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  2. Statement stmt;
  3. BoundSql boundSql = handler.getBoundSql();
  4. String sql = boundSql.getSql();
  5. if (hasStatementFor(sql)) {
  6. stmt = getStatement(sql);
  7. } else {
  8. Connection connection = getConnection(statementLog);
  9. stmt = handler.prepare(connection);
  10. putStatement(sql, stmt);
  11. }
  12. handler.parameterize(stmt);
  13. return stmt;
  14. }

然后调用getConnection方法获取连接:

  1. protected Connection getConnection(Log statementLog) throws SQLException {
  2. Connection connection = transaction.getConnection();
  3. if (statementLog.isDebugEnabled() || connectionLog.isDebugEnabled()) {
  4. return ConnectionLogger.newInstance(connection, statementLog);
  5. } else {
  6. return connection;
  7. }
  8. }

这个连接是从Transaction对象中获取的。

当我们和Spring一起使用时,当然由Spring统一管理,回顾DefaultSqlSessionFactory类中的openSessionFromDataSource方法,这个连接就是找Spring要的(DataSourceUtils#getConnection),要过来放到了SpringManagedTransaction中

创建Statement

  • Statement createStatement()

创建Statement 对象,Statement接口提供基本执行SQL语句的能力。

  • PreparedStatement prepareStatement(String sql)

创建PreparedStatement对象,PreparedStatement接口继承了Statement接口,提供SQL语句接受输入参数的能力。

  • CallableStatement prepareCall(String sql)

创建CallableStatement对象,CallableStatement接口继承了PreparedStatement接口,提供执行存储过程的能力。

针对这三种情况,Mybatis提供了三个StatementHandler实现:

并创建了一个RoutingStatementHandler作为路由(代理类),根据MappedStatement中的statementType来确定创建哪个作为实际的StatementHandler实现。对外提供一致接口,实际调用交给委托类。

还是接着看上面的prepareStatement方法,它获取连接后就接着调用StatementHandler的prepare方法:

  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  2. Statement stmt;
  3. BoundSql boundSql = handler.getBoundSql();
  4. String sql = boundSql.getSql();
  5. if (hasStatementFor(sql)) {
  6. stmt = getStatement(sql);
  7. } else {
  8. Connection connection = getConnection(statementLog);
  9. stmt = handler.prepare(connection);
  10. putStatement(sql, stmt);
  11. }
  12. handler.parameterize(stmt);
  13. return stmt;
  14. }

这个来自BaseStatementHandler的prepare方法又是调用子类的instantiateStatement方法来实例化Statement:

  1. protected Statement instantiateStatement(Connection connection) throws SQLException {
  2. String sql = boundSql.getSql();
  3. if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
  4. String[] keyColumnNames = mappedStatement.getKeyColumns();
  5. if (keyColumnNames == null) {
  6. return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
  7. } else {
  8. return connection.prepareStatement(sql, keyColumnNames);
  9. }
  10. } else if (mappedStatement.getResultSetType() != null) {
  11. return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  12. } else {
  13. return connection.prepareStatement(sql);
  14. }
  15. }

这里最终调用connection的prepareStatement方法,回到了我们传统的操作。

执行SQL语句

在Executor的实现类中,doUpdate和doQuery方法都会调用下面两个方法:

handler.update(stmt);

handler.query(stmt, resultHandler);

SimpleStatementHandler的query方法:

  1. public <E> List<E> query(Statement statement, ResultHandler resultHandler)
  2. throws SQLException {
  3. String sql = boundSql.getSql();
  4. statement.execute(sql);
  5. return resultSetHandler.<E>handleResultSets(statement);
  6. }

PreparedStatementHandler的query方法:

  1. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  2. PreparedStatement ps = (PreparedStatement) statement;
  3. ps.execute();
  4. return resultSetHandler.<E> handleResultSets(ps);
  5. }

CallableStatementHandler的query方法:

  1. public <E> List<E> query(Statement statement, ResultHandler resultHandler)
  2. throws SQLException {
  3. CallableStatement cs = (CallableStatement) statement;
  4. cs.execute();
  5. List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
  6. resultSetHandler.handleOutputParameters(cs);
  7. return resultList;
  8. }

这里都是直接调的Statement或PreparedStatement的execute方法。

返回结果集

上面的三种StatementHandler,最终都会处理结果集,CallableStatementHandler有点特殊,其它两种都是调用ResultSetHandler的handleResultSets方法来处理结果集。ResultSetHandler在基类BaseStatementHandler的构造函数中通过configuration对象来创建:

this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

具体的实现有两种,FastResultSetHandler和NestedResultSetHandler,且NestedResultSetHandler继承了FastResultSetHandler,重写了handleRowValues方法。

在FastResultSetHandler的handleResultSets方法中:

  1. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  2. final List<Object> multipleResults = new ArrayList<Object>();
  3. final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  4. int resultMapCount = resultMaps.size();
  5. int resultSetCount = 0;
  6. ResultSet rs = stmt.getResultSet();
  7. while (rs == null) {
  8. // move forward to get the first resultset in case the driver
  9. // doesn't return the resultset as the first result (HSQLDB 2.1)
  10. if (stmt.getMoreResults()) {
  11. rs = stmt.getResultSet();
  12. } else {
  13. if (stmt.getUpdateCount() == -1) {
  14. // no more results. Must be no resultset
  15. break;
  16. }
  17. }
  18. }
  19. // 验证是否定义了resultType或者resultMap
  20. validateResultMapsCount(rs, resultMapCount);
  21. while (rs != null && resultMapCount > resultSetCount) {
  22. final ResultMap resultMap = resultMaps.get(resultSetCount);
  23. ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
  24. handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
  25. rs = getNextResultSet(stmt);
  26. cleanUpAfterHandlingResultSet();
  27. resultSetCount++;
  28. }
  29. return collapseSingleResultList(multipleResults);
  30. }

处理结果行:

  1. protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
  2. final DefaultResultContext resultContext = new DefaultResultContext();
  3. skipRows(rs, rowBounds);
  4. while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
  5. final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
  6. Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
  7. callResultHandler(resultHandler, resultContext, rowValue);
  8. }
  9. }

skipRows就是跳过行,基于查询结果的分页使用,其中会直接调用ResultSet的absolute方法:

rs.absolute(rowBounds.getOffset());

而shouldProcessMoreRows方法就是判断是否应该获取更多的行:

  1. protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
  2. return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
  3. }

也对应了分页的limit字段,这种分页可以说是伪分页,查出来再分页。所以我们一般使用插件的形式来实现分页,基于sql的动态替换。

具体针对行的处理在getRowValue方法中:

  1. protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
  2. final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
  3. Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
  4. if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
  5. final MetaObject metaObject = configuration.newMetaObject(resultObject);
  6. boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
  7. if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
  8. final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
  9. foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
  10. }
  11. final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
  12. foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
  13. foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
  14. resultObject = foundValues ? resultObject : null;
  15. return resultObject;
  16. }
  17. return resultObject;
  18. }

根据ResultMap提供的相关参数(返回的类型等),构造一个空的对象,然后根据属性名到ResultSet中获取结果,再通过MetaObject给set进对象里。

具体的取值是通过TypeHandler:

final Object value = typeHandler.getResult(rs, columnName);

每种类型的都对应一个TypeHandler的实现,以Integer类型的为例(IntegerTypeHandler):

  1. @Override
  2. public Integer getNullableResult(ResultSet rs, String columnName)
  3. throws SQLException {
  4. return rs.getInt(columnName);
  5. }

框架就是这样,底层就是最基本的原理和最简单的接口,而这些接口通常就是所谓的标准。它所做的事就是解放我们的双手,把能省的都给省了,让我们专注于业务代码的编写。

但我们也应该知其所以然。一是出于好奇心,这是程序员的本能;二是为了学习其优秀的思想和设计理念,以后可以为我所用,这可称之为野心吧。

从JDBC看Mybatis的设计的更多相关文章

  1. jdbc、Mybatis、Hibernate介绍(非原创)

    文章大纲 一.jdbc介绍二.Mybatis介绍三.Hibernate介绍四.jdbc.Mybatis.Hibernate比较五.参考文章   一.jdbc介绍 1. jdbc编程步骤 (1)加载数据 ...

  2. 深入浅出MyBatis:JDBC和MyBatis介绍

    JDBC相关概念 Java程序都是通过JDBC连接数据库的,通过SQL对数据库编程,JDBC是由SUN公司提出的一些列规范,只定义了接口规范,具体实现由各个数据库厂商去实现,它是一种典型的桥接模式. ...

  3. SpringBoot 整合jdbc和mybatis

    摘要 该文章主要为记录如何在SpringBoot项目中整合JDBC和MyBatis,在整合中我会使用简单的用法和测试用例,毕竟该文章目的是为了整合,而不是教大家如何去使用.希望大家多多包涵. 通用配置 ...

  4. jdbc hibernate myBatis比较

    jdbc hibernate myBatis比较 jdbc 优点:性能高,易掌握 缺点:代码繁琐 hibernate 优点:不用写sql,代码简洁 缺点:性能不好 自动生成的sql效率低下(复杂业务) ...

  5. jdbc、Mybatis插入数据主键回显的实现方法

    插入数据的时候,往往需要获取主键值.但是有时候主键是自增长的那么,就不太适用手动添加主键值了,此时需要一种可以回显主键参数的方法, 下面以jdbc.mybatis的实现举例 此时使用的是jdbc的话或 ...

  6. [转载]JDBC/Spring/MyBatis性能比较

    原文地址:JDBC/Spring/MyBatis性能比较作者:tom_lt 测试目的: 比较JDBC,SpringJdbc和MyBatis的性能.   测试用例: 1. 查询:查询一张10000条数据 ...

  7. 从强制解包看 Swift 的设计

    从强制解包看 Swift 的设计 不知道大家有没有发现,在一个 Objective-C 和 Swift 混编的 App 中,当把一个 OC 中的参数转到 Swift 时,Swift 会自动把这个变量进 ...

  8. 原理分析之一:从JDBC到Mybatis

    原理分析之一:从JDBC到Mybatis Mybatis学习(一)原生态的JDBC编程总结 -----系列 深入浅出MyBatis-快速入门

  9. MyBatis缓存设计

    和大多数ORM框架一样,为了尽可能减少数据库的访问,MyBatis设计支持缓存功能.设计上通过Cache接口提供SPI(服务提供接口),可以让第三方缓存提供具体的缓存实现,比如使用ehcache.Re ...

随机推荐

  1. Ubuntu 下安装sqlite3 及常用SQL 语句

    安装sqlite3命令如下: sudo apt-get install sqlite3 创建或者打开已有的数据库文件: sqlite3 test.db 进入数据库后,可以进行以下常用SQL语句操作: ...

  2. PHP扩展--taint检测隐藏漏洞

    简介 Taint 可以用来检测隐藏的XSS code, SQL注入, Shell注入等漏洞, 并且这些漏洞如果要用静态分析工具去排查, 将会非常困难, 比如对于如下的例子: <?php echo ...

  3. C++面试中可能考察的基础知识(1)

    1 C++中允许函数的嵌套调用,但不允许函数的嵌套定义 2 构建派生类对象时,先调用基类的构造函数,在调用成员对象的构造函数,最后调用派生类构造函数. 3 volatile关键字 volatile提醒 ...

  4. 通过cordova将vue项目打包为webapp

    准备工作:需要之前配置好vue-cli脚架构,安装好cordova环境.下面开始对vue.js项目进行打包,打包环境为Android. 可以看下我的github:https://github.com/ ...

  5. Go语言 6 结构体、方法和接口

    文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ Go学习群:415660935 结构体(struct)是由一系列具有相同类型或不同类 ...

  6. bootstrap通过ajax请求JSON数据后填充到模态框

    1.   JSP页面中准备模态框 <!-- 详细信息模态框(Modal) --> <div> <div class="modal fade" id=& ...

  7. perl6 struct2-045 EXP

    测试站点: http://www.yutian.com.cn/index.action http://www.hjxzyzz.com:8088/pfw/login.action 代码如下: use v ...

  8. pytesser模块WindowsError错误解决方法

    在使用pytesser做图片文字识别时遇到 WindowsError: [Error 2] 错误,报错内容如下: Traceback (most recent call last): File &qu ...

  9. redis的备份恢复

    说明:默认rdb方式保存,redis支持主从和哨兵等,但是在某些情况下我们会单机跑,所以有时候我们就会需要设计到备份恢复 环境:原始redis:192.168.1.200 新redis:192.168 ...

  10. c#操作pdf文件系列之创建文件

    1.我使用的工具是vs2013,引用的第三方程序集itextpdf 具体安装方法,可以通过nuget搜索iTextSharp然后进行安装. 2具体代码如下 创建两个不同pdf文件,每个地方什么意思代码 ...