ibatis源码学习1_整体设计和核心流程
背景
介绍ibatis实现之前,先来看一段jdbc代码:
- Class.forName("com.mysql.jdbc.Driver");
- String url = "jdbc:mysql://localhost:3306/learnworld";
- Connection con = DriverManager.getConnection(url, "root","learnworld");
- String sql = "select * from test";
- PreparedStatement ps = con.prepareStatement(sql);
- ResultSet rs = ps.executeQuery();
- while(rs.next()){
- System.out.println("id=" + rs.getInt(1)+". score=" + rs.getInt(2));
- }
上面这段代码大家比较熟悉,这是一个典型的jdbc方式处理流程: 建立连接->传递参数->sql执行->处理结果->关闭连接。
问题
上面的代码中包含了很多不稳定的因素,可能会经常发生修改:
1. 数据源和事务管理等
2. sql语句
3. 入参和结果处理
如果这些发生变化,我们需要直接修改代码,重新编译、打包、发布等。
DIY
下面从我们自己DIY的视角来考虑如何设计框架,应对这些问题。框架的核心思想是抽取共性,封装可能出现的变化。
如何处理数据源变化?
将数据源连接等过程固定,将数据源中易变的信息封装在一起(如driverClassName, url等),放在配置文件中。
如何处理sql语句的变化?
将sql语句统一放在配置文件中,为每条sql语句设置标识,在代码中使用标识进行调用。
如何应对入参和结果处理的变化?
将参数传递,结果映射到java bean等统一封装在配置文件中。
结论: 将不变的流程固化到代码中,将变化的信息封装在配置文件中。
核心接口
ibatis抽取了以下几个重要接口:
1. SqlMapExecutor
该接口是对SQL操作行为的抽象,提供了SQL单条执行和批处理涉及的所有操作方法。
2. SqlMapTransactionManager
该接口是对事务行为的抽象,提供了事务执行过程中的涉及的所有方法。
3. SqlMapClient
该接口定位是SQL执行客户端,是线程安全的,用于处理多个线程的sql执行。它继承了上面两个接口,这意味着该接口具有SQL执行、批处理和事 务处理的能力,如果你拥有该接口的实现类,意味着执行任何SQL语句对你来说是小菜一碟。该接口的核心实现类是SqlMapClientImpl。
4. SqlMapSession
该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程sql执行过程的session信息。该接口的核心实现类是SqlMapSessionImpl。
5. MappedStatement
该接口定位是单条SQL执行时的上下文环境信息,如SQL标识、SQL、参数信息、返回结果、操作行为等。
6. ParameterMap/ResultMap
该接口用于在SQL执行的前后提供参数准备和执行结果集的处理。
以上接口的类图关系如下(部分):
这里必须要强调SqlMapExecutorDelegate这个类,他是一个执行代理类,在ibatis框架中地位非常重要,因为他耦合了用户端的操作行为和执行环境,他持有执行操作的所需要的所有数据,同时管理着执行操作依赖的环境。
初始化过程
1. 读取和解析sqlmap配置文件。
2. 注册Statement对象。
3. 创建SqlMapClientImpl对象。
下面是一个Spring中sqlMapClient的bean配置:
- <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <property name="dataSource" ref="dataSource" />
- <property name="configLocation" value="classpath/sqlmap/sqlmap-ibatis.xml" />
- </bean>
下面看一下SqlMapClientFactoryBean的初始化方法afterPropertiesSet(),用于构建sqlMapClient对象:
- public void afterPropertiesSet()throws Exception {
- ...
- this.sqlMapClient = buildSqlMapClient(this.configLocations,this.mappingLocations, this.sqlMapClientProperties); //初始化核心方法,构建sqlMapClient对象
- ...
- }
看一下buildSqlMapClient()的实现:
- protected SqlMapClient buildSqlMapClient(
- Resource[] configLocations, Resource[] mappingLocations, Properties properties)
- throws IOException {
- ...
- SqlMapClient client = null;
- SqlMapConfigParser configParser = new SqlMapConfigParser();
- for (int i =0; i < configLocations.length; i++) {
- InputStream is = configLocations[i].getInputStream();
- try {
- client = configParser.parse(is, properties); //通过SqlMapConfigParser解析配置文件,生成SQLMapClientImpl对象
- }
- catch (RuntimeException ex) {
- throw new NestedIOException("Failed to parse config resource: " + configLocations[i], ex.getCause());
- }
- }
- ...
- return client;
- }
初始化的核心是通过配置文件构建SQLMapClientImpl对象和其内部的各个属性,这里就不详述了,详细构建过程将另文分析。
SQL执行过程
下面以一个select语句的执行过程,分析一下以上各ibatis核心接口相互如何配合。
1. dao调用SqlMapClientTemplate的query()方法:
- public Object queryForObject(final String statementName,final Object parameterObject)
- throws DataAccessException {
- return execute(new SqlMapClientCallback() {
- public Object doInSqlMapClient(SqlMapExecutor executor)throws SQLException {
- return executor.queryForObject(statementName, parameterObject);
- }
- });
- }
2. execute()方法中展示了执行过程中的核心逻辑:获取session -> 获取connection -> 执行sql ->释放connection -> 关闭session。
部分源码如下:
- public Object execute(SqlMapClientCallback action)throws DataAccessException {
- ...
- SqlMapSession session = this.sqlMapClient.openSession();//获取session信息
- Connection ibatisCon = null; //获取Connection
- try {
- Connection springCon = null;
- DataSource dataSource = getDataSource();
- boolean transactionAware = (dataSourceinstanceof TransactionAwareDataSourceProxy);
- try {
- ibatisCon = session.getCurrentConnection();
- if (ibatisCon == null) {
- springCon = (transactionAware ?
- dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
- session.setUserConnection(springCon);
- ...
- }
- }
- ...
- // Execute given callback...
- try {
- return action.doInSqlMapClient(session);//执行SQL
- }
- ...
- finally {
- // 关闭Connection
- try {
- if (springCon !=null) {
- if (transactionAware) {
- springCon.close();
- }
- else {
- DataSourceUtils.doReleaseConnection(springCon, dataSource);
- }
- }
- }
- if (ibatisCon == null) {
- session.close(); //关闭session
- }
- }
3. action.doInSqlMapClient(session)调用SqlMapSessionImpl().queryForObject()方法。
注意: 这里调用对象主体为SqlMapSessionImpl,表示在线程session中执行sql(session是ThreadLocal的),而不在负责整体协调的SqlMapClientImpl中执行sql语句。
源码如下:
- public Object queryForObject(final String statementName,final Object parameterObject)
- throws DataAccessException {
- return execute(new SqlMapClientCallback() {
- //这里的SqlMapExecutor对象类型为SqlMapSessionImpl
- public Object doInSqlMapClient(SqlMapExecutor executor)throws SQLException {
- return executor.queryForObject(statementName, parameterObject);
- }
- });
- }
4. SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理:
- public Object queryForObject(String id, Object paramObject)throws SQLException {
- return delegate.queryForObject(session, id, paramObject);
- }
5. SqlMapExecutorDelegate的queryForObject()方法代码如下:
- public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject)throws SQLException {
- Object object = null;
- MappedStatement ms = getMappedStatement(id); //MappedStatement对象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件构建而成
- Transaction trans = getTransaction(session); // 用于事务执行
- boolean autoStart = trans == null;
- try {
- trans = autoStartTransaction(session, autoStart, trans);
- RequestScope request = popRequest(session, ms); // 从RequestScope池中获取该次sql执行中的上下文环境RequestScope
- try {
- object = ms.executeQueryForObject(request, trans, paramObject, resultObject); // 执行sql
- } finally {
- pushRequest(request); //归还RequestScope
- }
- autoCommitTransaction(session, autoStart);
- } finally {
- autoEndTransaction(session, autoStart);
- }
- return object;
- }
6. MappedStatement携带了SQL语句和执行过程中的相关信息,MappedStatement.executeQueryForObject()方法部分源码如下:
- public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject)
- throws SQLException {
- try {
- Object object = null;
- DefaultRowHandler rowHandler = new DefaultRowHandler();
- executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);//执行sql语句
- //结果处理,返回结果
- List list = rowHandler.getList();
- if (list.size() > 1) {
- throw new SQLException("Error: executeQueryForObject returned too many results.");
- } else if (list.size() >0) {
- object = list.get(0);
- }
- return object;
- }
- ....
- }
7. MappedStatement.executeQueryWithCallback()方法包含了参数值映射、sql准备和sql执行等关键过程,部分源码如下:
- protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler,int skipResults, int maxResults)
- throws SQLException {
- ...
- try {
- parameterObject = validateParameter(parameterObject); //验证入参
- Sql sql = getSql(); //获取SQL对象
- errorContext.setMoreInfo("Check the parameter map.");
- ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);// 入参映射
- errorContext.setMoreInfo("Check the result map.");
- ResultMap resultMap = sql.getResultMap(request, parameterObject); //结果映射
- request.setResultMap(resultMap);
- request.setParameterMap(parameterMap);
- errorContext.setMoreInfo("Check the parameter map.");
- Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject); //获取参数值
- errorContext.setMoreInfo("Check the SQL statement.");
- String sqlString = sql.getSql(request, parameterObject); //获取拼装后的sql语句
- errorContext.setActivity("executing mapped statement");
- errorContext.setMoreInfo("Check the SQL statement or the result map.");
- RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);
- sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback); //sql执行
- errorContext.setMoreInfo("Check the output parameters.");
- if (parameterObject != null) {
- postProcessParameterObject(request, parameterObject, parameters);
- }
- errorContext.reset();
- sql.cleanup(request);
- notifyListeners();
- ....
- }
8. 到了执行中最核心的一步,也是最后一步: MappedStatement.sqlExecuteQuery()方法,它负责sql的最后执行,内部调用了SqlExecutor.executeQuery()方法,部分源码如下:
- public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,int skipResults, intmaxResults, RowHandlerCallback callback)throws SQLException {
- ...
- PreparedStatement ps = null;
- ResultSet rs = null;
- setupResultObjectFactory(request);
- try {
- errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
- Integer rsType = request.getStatement().getResultSetType();
- //初始化PreparedStatement,设置sql、参数值等
- if (rsType != null) {
- ps = prepareStatement(request.getSession(), conn, sql, rsType);
- } else {
- ps = prepareStatement(request.getSession(), conn, sql);
- }
- setStatementTimeout(request.getStatement(), ps);
- Integer fetchSize = request.getStatement().getFetchSize();
- if (fetchSize != null) {
- ps.setFetchSize(fetchSize.intValue());
- }
- errorContext.setMoreInfo("Check the parameters (set parameters failed).");
- request.getParameterMap().setParameters(request, ps, parameters);
- errorContext.setMoreInfo("Check the statement (query failed).");
- ps.execute(); //执行
- errorContext.setMoreInfo("Check the results (failed to retrieve results).");
- // ResultSet处理
- rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);
- } finally {
- try {
- closeResultSet(rs);
- } finally {
- closeStatement(request.getSession(), ps);
- }
- }
- }
上面这段代码大家会非常熟悉,和本文开始处的代码很相似,ibatis归根到底,是对JDBC操作一定程度上的封装而已。
下面在总体上概括sql的一般执行过程:
SqlMapClientImpl接到请求后,创建SqlMapSessionImpl对象(ThreadLocal,保证线程安 全),SqlMapSessionImpl交由内部的代理类SqlMapExecutorDelegate执行,代理类获取相应的MappedStatement,交由MappedStatement对象执行,MappedStatement交由SqlExecutor执行,最终使 用JDBC方式执行sql。
小结
ibatis源码规模较小,整体设计思路清晰,阅读ibatis源码可以按以下思路进行:
1. 了解ibatis框架的整体目标,用于解决哪些问题。
2. ibatis如何解决这些问题,带着问题去学习。
3. 了解ibatis框架的核心接口和整体设计思路。
4. 抓住ibatis核心流程: 初始化和请求处理流程。
5. 详细ibatis框架的关键细节实现,如ibatis中的配置文件解析,参数和结果映射等。
ibatis源码学习1_整体设计和核心流程的更多相关文章
- ibatis源码学习4_参数和结果的映射原理
问题在详细介绍ibatis参数和结果映射原理之前,让我们先来思考几个问题.1. 为什么需要参数和结果的映射?相对于全自动的orm,ibatis一个重要目标是,通过维护POJO与SQL之间的映射关系,让 ...
- spring源码学习——spring整体架构和设计理念
Spring是在Rod Johnson的<Expert One-On-One J2EE Development and Design >的基础上衍生而来的.主要目的是通过使用基本的java ...
- Mybatis源码学习之整体架构(一)
简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...
- 【iScroll源码学习04】分离IScroll核心
前言 最近几天我们前前后后基本将iScroll源码学的七七八八了,文章中未涉及的各位就要自己去看了 1. [iScroll源码学习03]iScroll事件机制与滚动条的实现 2. [iScroll源码 ...
- RecyclerView源码分析(一)--整体设计
RecyclerView这个控件出来已经有一段时间了,如果看这篇文章的你,还没有使用过这个控件.那请先去学习怎样使用.不然看也白看.这里奉上一些关于介绍RecyclerView使用方法的优秀博客: 鸿 ...
- ibatis源码学习2_初始化和配置文件解析
问题在详细介绍ibatis初始化过程之前,让我们先来思考几个问题. 1. ibatis初始化的目标是什么?上文中提到过,ibatis初始化的核心目标是构造SqlMapClientImpl对象,主要是其 ...
- Hadoop源码学习笔记之NameNode启动场景流程五:磁盘空间检查及安全模式检查
本篇内容关注NameNode启动之前,active状态和standby状态的一些后台服务及准备工作,即源码里的CommonServices.主要包括磁盘空间检查. 可用资源检查.安全模式等.依然分为三 ...
- Hadoop源码学习笔记之NameNode启动场景流程四:rpc server初始化及启动
老规矩,还是分三步走,分别为源码调用分析.伪代码核心梳理.调用关系图解. 一.源码调用分析 根据上篇的梳理,直接从initialize()方法着手.源码如下,部分代码的功能以及说明,已经在注释阐述了. ...
- Hadoop源码学习笔记之NameNode启动场景流程三:FSNamesystem初始化源码剖析
上篇内容分析了http server的启动代码,这篇文章继续从initialize()方法中按执行顺序进行分析.内容还是分为三大块: 一.源码调用关系分析 二.伪代码执行流程 三.代码图解 一.源码调 ...
随机推荐
- github提交表情包
emoji-list emoji表情列表 目录 人物 自然 事物 地点 符号 人物 :bowtie: :bowtie: :smile: :smile: :laughing: :laughing: :b ...
- MySQL 基本语法(1.表字段操作,2表记录管理 3.运算符管理4.SQL查询 5.约束6.索引
.表字段的操作 .语法:alter table 表名 执行动作; .添加字段(add) .添加到末尾 alter table 表名 add 字段名 数据类型; .添加到第一列 alter table ...
- onlevelwasloaded的调用时机
并不是任何场景加载完成后都会触发该事件,必须使用场景加载API才能触发onlevelwasloaded函数,场景加载API如 scenemanager.loadscene等,additive模式不触发 ...
- overflow与underflow
是新近的firefox浏览器中支持overflow, underflow这两个事件,当某一元素的大小超出父元素的显示范围就会触发overflow事件,如果从超出显示再变回不超出的状态则触发underf ...
- 如何设置tomcat,直接通过IP 访问
找到tomcat的主目录,进入conf文件夹,找到server.xml文件,并打开: 修改tomcat的监听端口为80端口: 在server.xml文件中找到: <Connector por ...
- 从0开始用spring boot编写分布式配置中心-peppa
欢迎大家一起来编写peppa github地址: github 交流群: 目前市面上比较流行的分布式配置中心有disconf.apollo,用起来还是比较方便的,然而由于在权限管理这块做得不够好,导致 ...
- Android 自定义组件之 带有悬浮header的listview
最近做项目遇到一个需求,要做一个带有悬浮header的listview,即,当listview滑动时,header消失,静止时header浮现. 这个需求看似简单,实际做起来还是会遇到不少的困难,特此 ...
- 【bzoj3239】Discrete Logging
[吐槽] 这题和[bzoj]2480一毛一样. 就是输入顺序和输出变了一下. 传送门:http://www.cnblogs.com/chty/p/6043707.html
- RGB颜色核对的网址
http://www.917118.com/tool/color_3.html 首页 人民币大写转换 颜色总览 颜色中文名称对照表 CMYK颜色对照表 RGB颜色对照表 色阶板 颜色代码表 调色板 ...
- Python实现目录文件的全量和增量备份
目标: 1.传入3个参数:源文件路径,目标文件路径,md5文件 2.每周一实现全量备份,其余时间增量备份 1.通过传入的路径,获取该路径下面的所有目录和文件(递归) 方法一:使用os.listdir ...