从JDBC看Mybatis的设计
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
六步流程:
- 加载驱动(5.x驱动包不需要这步了)
- 建立连接
- 创建Statement
- 执行SQL语句
- 获取结果集
- 关闭资源
这里只取后面几步分析下,基本上都是从Executor开始。DefaultSqlSession被每个Mapper持有,将各种基于SQL的操作转移到调用Executor的query和update方法。
Executor的类图如下:
以ReuseExecutor的doQuery方法为例:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
建立连接
上面ReuseExecutor的doQuery方法调用其prepareStatement方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
然后调用getConnection方法获取连接:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled() || connectionLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog);
} else {
return connection;
}
}
这个连接是从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方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
这个来自BaseStatementHandler的prepare方法又是调用子类的instantiateStatement方法来实例化Statement:
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);
}
}
这里最终调用connection的prepareStatement方法,回到了我们传统的操作。
执行SQL语句
在Executor的实现类中,doUpdate和doQuery方法都会调用下面两个方法:
handler.update(stmt);
handler.query(stmt, resultHandler);
SimpleStatementHandler的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
PreparedStatementHandler的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
CallableStatementHandler的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
CallableStatement cs = (CallableStatement) statement;
cs.execute();
List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
resultSetHandler.handleOutputParameters(cs);
return resultList;
}
这里都是直接调的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方法中:
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
int resultSetCount = 0;
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
// 验证是否定义了resultType或者resultMap
validateResultMapsCount(rs, resultMapCount);
while (rs != null && resultMapCount > resultSetCount) {
final ResultMap resultMap = resultMaps.get(resultSetCount);
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
rs = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
return collapseSingleResultList(multipleResults);
}
处理结果行:
protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
final DefaultResultContext resultContext = new DefaultResultContext();
skipRows(rs, rowBounds);
while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
callResultHandler(resultHandler, resultContext, rowValue);
}
}
skipRows就是跳过行,基于查询结果的分页使用,其中会直接调用ResultSet的absolute方法:
rs.absolute(rowBounds.getOffset());
而shouldProcessMoreRows方法就是判断是否应该获取更多的行:
protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
}
也对应了分页的limit字段,这种分页可以说是伪分页,查出来再分页。所以我们一般使用插件的形式来实现分页,基于sql的动态替换。
具体针对行的处理在getRowValue方法中:
protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(resultObject);
boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
}
final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
resultObject = foundValues ? resultObject : null;
return resultObject;
}
return resultObject;
}
根据ResultMap提供的相关参数(返回的类型等),构造一个空的对象,然后根据属性名到ResultSet中获取结果,再通过MetaObject给set进对象里。
具体的取值是通过TypeHandler:
final Object value = typeHandler.getResult(rs, columnName);
每种类型的都对应一个TypeHandler的实现,以Integer类型的为例(IntegerTypeHandler):
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getInt(columnName);
}
框架就是这样,底层就是最基本的原理和最简单的接口,而这些接口通常就是所谓的标准。它所做的事就是解放我们的双手,把能省的都给省了,让我们专注于业务代码的编写。
但我们也应该知其所以然。一是出于好奇心,这是程序员的本能;二是为了学习其优秀的思想和设计理念,以后可以为我所用,这可称之为野心吧。
从JDBC看Mybatis的设计的更多相关文章
- jdbc、Mybatis、Hibernate介绍(非原创)
文章大纲 一.jdbc介绍二.Mybatis介绍三.Hibernate介绍四.jdbc.Mybatis.Hibernate比较五.参考文章 一.jdbc介绍 1. jdbc编程步骤 (1)加载数据 ...
- 深入浅出MyBatis:JDBC和MyBatis介绍
JDBC相关概念 Java程序都是通过JDBC连接数据库的,通过SQL对数据库编程,JDBC是由SUN公司提出的一些列规范,只定义了接口规范,具体实现由各个数据库厂商去实现,它是一种典型的桥接模式. ...
- SpringBoot 整合jdbc和mybatis
摘要 该文章主要为记录如何在SpringBoot项目中整合JDBC和MyBatis,在整合中我会使用简单的用法和测试用例,毕竟该文章目的是为了整合,而不是教大家如何去使用.希望大家多多包涵. 通用配置 ...
- jdbc hibernate myBatis比较
jdbc hibernate myBatis比较 jdbc 优点:性能高,易掌握 缺点:代码繁琐 hibernate 优点:不用写sql,代码简洁 缺点:性能不好 自动生成的sql效率低下(复杂业务) ...
- jdbc、Mybatis插入数据主键回显的实现方法
插入数据的时候,往往需要获取主键值.但是有时候主键是自增长的那么,就不太适用手动添加主键值了,此时需要一种可以回显主键参数的方法, 下面以jdbc.mybatis的实现举例 此时使用的是jdbc的话或 ...
- [转载]JDBC/Spring/MyBatis性能比较
原文地址:JDBC/Spring/MyBatis性能比较作者:tom_lt 测试目的: 比较JDBC,SpringJdbc和MyBatis的性能. 测试用例: 1. 查询:查询一张10000条数据 ...
- 从强制解包看 Swift 的设计
从强制解包看 Swift 的设计 不知道大家有没有发现,在一个 Objective-C 和 Swift 混编的 App 中,当把一个 OC 中的参数转到 Swift 时,Swift 会自动把这个变量进 ...
- 原理分析之一:从JDBC到Mybatis
原理分析之一:从JDBC到Mybatis Mybatis学习(一)原生态的JDBC编程总结 -----系列 深入浅出MyBatis-快速入门
- MyBatis缓存设计
和大多数ORM框架一样,为了尽可能减少数据库的访问,MyBatis设计支持缓存功能.设计上通过Cache接口提供SPI(服务提供接口),可以让第三方缓存提供具体的缓存实现,比如使用ehcache.Re ...
随机推荐
- mysql数据库使用sql命令窗口查询的数据,改成sql语句导入到mysql数据库中
1.查询语句为select * from t_table;导出的数据格式如下: 2.将数据文本备份,然后使用NOTEPAD++打开,然后只拷贝数据到新建txt中,然后进行如下替换: 1)将“ | ”分 ...
- redis-cluster 集群搭建详细指南及常见问题集合
只当个搬运工吧 搭建篇:https://www.cnblogs.com/mafly/p/redis_cluster.html 测试能用 常见问题: 1 redis操作key时出现以下错误 (erro ...
- Css Sprite 图片等比缩放图片大小
图片大小80*40,即每张图片大小40*40,如何以20*20显示图片?1. 首先看下如何以40*40显示第二张图片: 正常显示css代码 .sprite { background-image: ur ...
- CSS与HTML结合
CSS与HTML结合的4中方式: 1.每个HTML标签都有style属性 2.当页面中有多个标签具有相同样式时,可定义style标签封装样式以复用 <style type=”text/css”& ...
- 【BZOJ】4764: 弹飞大爷 LCT
[题意]给定n个数字ai,表示大爷落到i处会被弹飞到i+ai处,弹飞到>n或<1处则落地.m次操作,修改一个ai,或询问大爷落到x处经过几次落地(或-1).n,m<=10^5,|ai ...
- form表单token错误
添加一段代码试试: <input type="hidden" name="_token" value="{{ csrf_token() }}&q ...
- python学习笔记(十)之格式化字符串
格式化字符串,可以使用format方法.format方法有两种形式参数,一种是位置参数,一种是关键字参数. >>> '{0} {1}'.format('Hello', 'Python ...
- 【译】DTD - Entities
原文:DTD - Entities 实体用于定义XML文档中特殊字符的快捷方式. 实体主要有四种类型: 内置实体(Built-in entities) 字符实体(Character entities) ...
- Go语言 2 变量、常量和数据类型
文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ Go学习群:415660935 2.1 变量 变量是对一块内存空间的命名,程序可以通 ...
- WHY学习python?
1.python更容易上手 2.功能库很多,不用重复造轮子 3.能干的事情很多(网站开发,爬虫,自动化运维,数据分析,游戏开发,人工智能) 网站开发:豆瓣,知乎 网站框架:django (姜狗) py ...