`

spring + ibatis 源码解读

阅读更多
最近对ibatis2.1.0.256这个版本(大部分应用都是用该版本)的源代码以及spring的SqlMapClientTemplate相关的代码看了下. 这个版本的ibatis有一个问题, 就是如果执行sql出错的话, 出错信息非常不友好, 连最基本的sql和paramter都没有, 给查找问题带来不小的麻烦, 本想看看代码能进行一下定制, 由于版本太老, 扩展性太差了, 基本没法扩展, 最后被迫放弃.

题外话, 现在ibatis都已经出到3.0了(为什么我们还不升级?)

发现spring和ibatis的代码风格相差还是很大的. 两相比较就可以看出差距来, 同时也对di有了进一步的理解.

在spring中几乎将所有的类都当一个个bean来看待, 因此所有的依赖要么是通过构造函数注入, 要么通过setter方式注入. 而ibatis则明显没有这样的概念, 很多地方对其他类的依赖都是直接在构造函数中new出来的, 这样给定制带来了麻烦, 可以说, 基本上是没法对ibatis进行扩展. 因此我们可以认为ibatis是一个非常封闭的dao框架(当然后续版本的ibatis有所改善).

基于ibatis这样的架构, 导致spring对ibatis的扩展也非常有限.其中最重要的类在我看来无非两个:SqlMapClientFactoryBean, SqlMapClientTemplate, 前者是一个典型的FactoryBean, 用来将ibatis纳入spring的ioc容器中管理. 这里我们可以借鉴一下spring自己的FactoryBean的做法. 一般的FactoryBean都要实现FactoryBean接口, 在getObject()方法中, 我们可以去具体实现到底要怎么创建所需要的bean, 当然简单的可以直接new, 一般情况下还需要借助其他的接口来获取所需要的bean, 比如这里还实现了InitializingBean接口, 即在bean的定义以及相关的依赖设置完毕之后, 调用了afterPropertiesSet()方法,此时便开始根据配置文件来构造SqlMapClient了, 在根据配置构造SqlMapClient的部分spring调用了ibatis的SqlMapClientBuilder这个构造器来处理的, 从这里开始, ibatis便关闭了我们灵活处理构造SqlMapClient的大门. 对内部的构造过程我们几乎完全无法控制, 如果希望有所改善, 必须复写大量的类.

SqlMapClientTemplate用来对ibatis的SqlMapClient进行包装, 以便在调用ibatis访问数据库的时候, 做一些手脚, 不过这里这里主要是为了将iBatis抛出的SqlException转换成spring统一的DataAccessException异常, 处理异常的CRUD统一入口代码如下:

	public Object execute(SqlMapClientCallback action) throws DataAccessException {
		SqlMapSession session = this.sqlMapClient.openSession();
		Connection ibatisCon = null;
		try {
			Connection springCon = null;
			try {
				....
				return action.doInSqlMapClient(session);
			}
			catch (SQLException ex) {
				throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
			}
			finally {
				DataSourceUtils.releaseConnection(springCon, getDataSource());
			}
		}
		finally {
			if (ibatisCon == null) {
				session.close();
			}
		}
	}

因此如果你不喜欢spring的exception处理方式, 也可以不用它. 直接用SqlMapClient即可. spring之所以能将所有的操作归结到一个入口, 这个与SqlMapClientCallback接口的使用有很大的关系. 这里我们可以见到spring里面典型的接口->匿名类的用法.spring对匿名类用的那可是相当的多, 这个也是我们值得借鉴的一个地方, 不过匿名类不可滥用, 一般来说匿名类都是很简单的调用(10行左右), 如果有大段的代码调用, 那么匿名类就不合适了, 这里基本都很简单, 都是一个简单的转发, 即将对SqlMapClientTemplate的调用转发给SqlMapClient, 比如下面的代码:

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

			}

		});

	}


另外这里需要说一下的是, SqlMapClient是ibatis用来访问数据库的一个入口, 即一个SqlMapClient表示一个数据库. 所有的CRUD操作都要经过这个接口.

下面我们就来历数ibatis的种种让人崩溃的做法.

ibatis主要做了两件事, 一个就是解析sqlmap配置文件, 就是我们配置的一个sql语句和javabean与表字段之间的映射关系, 这里我们不做多展开.另外一个就是调用jdbc驱动, 执行数据库操作.

这里我们先从解析开始.ibatis的解析入口是SqlMapClientBuilder类. 这个builder类将解析的过程封装到了SqlMapConfigParser中.代码如下:
  public static SqlMapClient buildSqlMapClient(Reader reader) {

//    return new XmlSqlMapClientBuilder().buildSqlMap(reader);

    return new SqlMapConfigParser().parse(reader);

  }


万恶的new出现了, 这里我们已经没法对parser进行控制了.

接着我们来看看SqlMapConfigParser构造器:
  public SqlMapConfigParser() {

    this(null, null);

  }
  public SqlMapConfigParser(XmlConverter sqlMapConfigConv, XmlConverter sqlMapConv) {

    super(new Variables());

    parser.setValidation(true);

    parser.setEntityResolver(new SqlMapClasspathEntityResolver());
    vars.sqlMapConfigConv = sqlMapConfigConv;

    vars.sqlMapConv = sqlMapConv;
    vars.delegate = new SqlMapExecutorDelegate();

    vars.typeHandlerFactory = vars.delegate.getTypeHandlerFactory();

    vars.client = new SqlMapClientImpl(vars.delegate);
    ...

  }


又是大量的new操作, 再一次无语了.

而且只有get方法, 没有set方法, 再一次关闭了注入依赖的可能.

在SqlMapConfigParser中有两个类需要注意, 即SqlMapExecutorDelegate和SqlMapClientImpl, SqlMapExecutorDelegate这个封装了从配置文件解析的一些东东, SqlMapClientImpl所需要的一些配置就是从SqlMapExecutorDelegate获取的, 因此SqlMapClientImpl注入了SqlMapExecutorDelegate, 并在需要的时候转调SqlMapExecutorDelegate, SqlMapClientImpl因为是操作数据库的入口, 还需要负责管理事务, session等, 里面有一个很重要的东东:SqlExecutor, 这个也是在SqlMapExecutorDelegate中实现的.如同名字一样, 它是用来执行sql语句的.这个似乎是我们用来控制底层数据库操作的一个入口, 如果能进行配置将是一件幸福的事儿.它是在SqlMapExecutorDelegate中创建的, 很遗憾, 也是在构造函数中new出来的:
  public SqlMapExecutorDelegate() {

    mappedStatements = new HashMap();

    cacheModels = new HashMap();

    resultMaps = new HashMap();

    parameterMaps = new HashMap();
    requestPool = new ThrottledPool(RequestScope.class, DEFAULT_MAX_REQUESTS);

    sessionPool = new ThrottledPool(SessionScope.class, DEFAULT_MAX_SESSIONS);
    sqlExecutor = new SqlExecutor();

    typeHandlerFactory = new TypeHandlerFactory();

    dataExchangeFactory = new DataExchangeFactory(typeHandlerFactory);

  }


SqlExecutor是一个具体类, 而且没有setter方法, 再一次崩溃.

看到这里, 我们几乎完全失去了对ibatis的底层操作进行控制的可能, 我们继续看SqlExecutor是如何被使用的.

所有的sql语句的执行都是封装在ibatis的一个个MappedStatement中, 大部分的逻辑都是放在GeneralStatement中, 这里我们拿出查询的处理代码:
  protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults)

      throws SQLException {

    ErrorContext errorContext = request.getErrorContext();

    errorContext.setActivity("preparing the mapped statement for execution");

    errorContext.setObjectId(this.getId());

    errorContext.setResource(this.getResource());
    try {

      parameterObject = validateParameter(parameterObject);
      Sql sql = getSql();
      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);
      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);
      errorContext.setMoreInfo("Check the output parameters.");

      if (parameterObject != null) {

        postProcessParameterObject(request, parameterObject, parameters);

      }
      errorContext.reset();

      sql.cleanup(request);

      notifyListeners();

    } catch (SQLException e) {

      errorContext.setCause(e);

      throw new NestedSQLException(errorContext.toString(), e.getSQLState(), e.getErrorCode(), e);

    } catch (Exception e) {

      errorContext.setCause(e);

      throw new NestedSQLException(errorContext.toString(), e);

    }

  }
  protected void sqlExecuteQuery(RequestScope request, Connection conn, String sqlString, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {

    getSqlExecutor().executeQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback);

  }



在这段代码中, 有sql语句, 参数的校验, 另外就是调用sqlExecutor执行了, 基本上很简单, 这里不得不暴露一个ibatis对异常处理非常糟糕的做法, 就是隐藏信息中,没有包含出错的sql语句和对应的参数, 我们仅仅只能知道哪个sqlmap文件配置的哪个sql有问题, 但是不知道最终的sql是个什么样子. 而我们实际开发过程中很大一部分都是因为bad sql导致的. 不能呈现bad sql 自然给解决问题带来不小的难度, 我曾经想了不少问题都没法解决.

另外, 对于在日志中输入正常的sql, 参数, 执行结果的做法, 这里ibatis用到了动态代理.比如这个PreparedStatementLogProxy类:
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (EXECUTE_METHODS.contains(method.getName())) {
        if (log.isDebugEnabled()) {
          log.debug("{pstm-" + id + "} PreparedStatement: " + removeBreakingWhitespace(sql));
          log.debug("{pstm-" + id + "} Parameters: " + getValueString());
          log.debug("{pstm-" + id + "} Types: " + getTypeString());
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return ResultSetLogProxy.newInstance(rs);
        } else {
          return method.invoke(statement, params);
        }
      } else if (SET_METHODS.contains(method.getName())) {
        if ("setNull".equals(method.getName())) {
          setColumn(params[0], null);
        } else {
          setColumn(params[0], params[1]);
        }
        return method.invoke(statement, params);
      } else if ("getResultSet".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return ResultSetLogProxy.newInstance(rs);
      } else {
        return method.invoke(statement, params);
      }
    } catch (Throwable t) {
      throw ClassInfo.unwrapThrowable(t);
    }
  }

它的log很特殊是跟java.sql的一些类相关的:
  private static final Log log = LogFactory.getLog(PreparedStatement.class);

因此很多人在问如何打印日志, 实际上就是要将java.sql这个package下相关类的DEBUG开关打开即可.

4
1
分享到:
评论

相关推荐

    spring+ibatis事务的配置

    很好的spring+ibatis事务的配置文档.

    struts2+spring+ibatis+mysql

    "Struts2+Spring+Ibatis+MySQL" 是一个经典的Java Web开发框架组合,用于构建高效、可扩展的企业级应用程序。这个组合集成了强大的MVC(Model-View-Controller)框架Struts2、依赖注入与面向切面编程的Spring框架、...

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis是两种常见的Java Web应用程序集成框架,它们分别基于ORM框架Hibernate和轻量级数据访问框架Ibatis。这两种框架结合Spring,旨在提供一个强大的、可扩展的、易于...

    maven搭建SpringMVC+spring+ibatis

    在IT行业中,构建高效、可扩展的Web应用是至关重要的,而"Maven搭建SpringMVC+Spring+Ibatis"的组合则提供了一种强大的解决方案。本文将深入探讨这些技术及其集成,帮助你理解和掌握如何利用它们来构建现代化的Java ...

    struts2+spring+Ibatis框架包

    这个“struts2+spring+iBatis框架包”集成了这三个框架,使得开发者能够快速构建基于MVC(Model-View-Controller)模式的Web应用。 Struts2作为MVC框架,负责处理应用程序的控制逻辑。它通过Action类和配置文件定义...

    spring+ibatis+oracle分页缓存源码

    总的来说,"spring+ibatis+oracle分页缓存源码"项目展示了如何在Spring管理的环境中,利用iBatis和Oracle数据库实现高效的数据分页和缓存策略。通过理解和实践这些技术,开发者可以构建出更加健壮、响应快速的Web...

    struts+spring+ibatis做的一个增删改查例子

    在Struts+Spring+iBATIS的架构中,iBATIS负责与数据库交互,通过SQL映射文件(sqlmap.xml)定义SQL查询、插入、更新和删除操作。它与Spring整合后,可以在Spring的事务管理下执行数据库操作,确保数据的一致性。 在...

    JSF+Spring+Ibatis示例

    JSF+Spring+Ibatis示例,对学习JAVA企业应用开发有巨大的帮助!

    maven3+struts2+spring+ibatis

    maven3+struts2+spring+ibatis,本来是用maven3+struts2+spring+hibernate但考虑到hibernate在多表级联查询的时候执行效率不高,所以改用性能更好不过sql比较麻烦的的ibatis,本项目只有登录和插入数据,仅供参考: ...

    spring+struts2+ibatis整合的jar包

    在Java Web开发中,Spring、Struts2和iBatis是三个非常重要的框架,它们各自在不同的层面上提供了强大的功能。Spring是一个全面的后端应用框架,提供了依赖注入(DI)、面向切面编程(AOP)、事务管理等功能;Struts...

    struts+spring+ibatis的Demo

    这个"struts+spring+ibatis的Demo"压缩包文件提供了这三个框架集成使用的示例代码,旨在帮助开发者理解和学习如何将它们有效地结合在一起。 **Struts 2框架** Struts 2是一个基于MVC设计模式的Web应用框架,它继承...

    spring+struts+ibatis

    标题中的"spring+struts+ibatis"是指一种经典的Java Web开发架构,也被称为SSM框架。这个架构结合了Spring框架、Struts2框架和iBatis(现在称为MyBatis)来构建高效且可维护的Web应用。下面将详细阐述这三个框架以及...

    webwork+spring+ibatis很适合初学者的实例

    "webwork+spring+ibatis" 的实例通常会展示如何将这三个框架集成到一个完整的Web项目中。这个实例可能包含以下部分: 1. **环境配置**:安装和配置Java开发环境,如JDK,以及相关的开发工具,如IDEA或Eclipse。 2. ...

    Struts+Spring+Ibatis环境配置(一) - zwjxf的专栏 - 博

    Struts+Spring+Ibatis环境配置(一) - zwjxf的专栏 - 博

    struts2 + spring + ibatis 实例

    struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例

    spring+struts2+ibatis简单登录实例--特别适新人学习

    一个简单的spring+struts+ibatis整合的实例,实现了用户登录,用户登录成功则显示欢迎信息,失败则显示用户名或密码错误,该实例非常简单基础,特别适合新人学习,工程包含了必要的资源包,部署到服务器中及可运行,...

    struts+spring+ibatis框架

    在"struts+spring+ibatis"的整合应用中,Spring通常作为核心,管理Struts的Action以及iBatis的数据访问对象(DAO)。Struts处理HTTP请求,将请求转发给Spring管理的Action,Action再通过Spring的依赖注入获取到...

    各种系统架构图及其简介(Spring+IBatis+Struts1+Struts2+Hibernat)

    各种系统架构图及其简介(Spring+IBatis+Struts1+Struts2+Hibernat)

    Struts+Spring+Ibatis示例

    这个"Struts+Spring+Ibatis示例"提供了一个基础的整合应用,帮助开发者理解这三者如何协同工作。 **Struts** 是一个基于 Model-View-Controller (MVC) 设计模式的Java web框架,主要负责处理用户请求,控制应用程序...

    Struts2+Spring+Ibatis整合的简单人事管理系统

    Struts2+Spring+Ibatis整合的简单人事管理系统 没分了,转载过来的,有需要的看看吧,我觉得不错~~

Global site tag (gtag.js) - Google Analytics