ibatis infinite loop when getFirstResultSet

前几天上线后老大发现几台负载非常高,dump线程状态后发现多个线程死循环在同一处,于是发现了ibatis的这个bug:

https://issues.apache.org/jira/browse/IBATIS-384

https://issues.apache.org/jira/browse/IBATIS-587

在mysql数据库上,没有结果集时,stmt.getUpdateCount()会返回0,而非-1.
ibatis 2.4.3

  private ResultSet getFirstResultSet(StatementScope scope, Statement stmt) throws SQLException {
    ResultSet rs = null;
    boolean hasMoreResults = true;
    while (hasMoreResults) {
      rs = stmt.getResultSet();
      if (rs != null) {
        break;
      }
      hasMoreResults = moveToNextResultsIfPresent(scope, stmt);
    }
    return rs;
  }

  private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
    boolean moreResults;
    // This is the messed up JDBC approach for determining if there are more results
    moreResults = !(((moveToNextResultsSafely(scope, stmt) == false) && (stmt.getUpdateCount() == -1)));
    return moreResults;
  }

  private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
    if (forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
      return stmt.getMoreResults();
    }
    return false;
  }

moreResults恒为真,程序出现死循环。

在mybatis 2.5的代码里,这部分已经修改为:

  private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
    boolean moreResults;
    // This is the messed up JDBC approach for determining if there are more results
    boolean movedToNextResultsSafely = moveToNextResultsSafely(scope, stmt);
    int updateCount = stmt.getUpdateCount();

    moreResults = !(!movedToNextResultsSafely && (updateCount == -1));

    //ibatis-384: workaround for mysql not returning -1 for stmt.getUpdateCount()
    if (moreResults == true){
        moreResults = !(!movedToNextResultsSafely && !isMultipleResultSetSupportPresent(scope, stmt));
    }

    return moreResults;
  }

  private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
    if (isMultipleResultSetSupportPresent(scope, stmt)) {
      return stmt.getMoreResults();
    }
    return false;
  }

  /**
   * checks whether multiple result set support is present - either by direct support of the database driver or by forcing it
   */

  private boolean isMultipleResultSetSupportPresent(StatementScope scope,
          Statement stmt) throws SQLException {
      return forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets();
  }

这部分条件判断实在很极致了。

新的实现当getUpdateResult是0时,moreResults恒为真,这时再进行一个判断,如果是由于isMultipleResultSetSupportPresent为false导致了moveToNextResultsSafely为false,那么实际moreResults应是false

MyBatis 2.5还有一个issue没有解决,离发布还有一些时间,这个问题只好签出新版本代码自己build了

Maven recipe #1: Test Java code with groovy

问题: 受够了,不想写Java了,写个含有数据的map还要new出来一个一个put进去,想用groovy解决单元测试
解决: gmaven+groovy eclipse插件可以解决这个需求
在pom.xml中添加gmaven的依赖,注意,仅用来测试。老大不让生产代码里有不可靠的东西。

...
        <dependency>
            <groupId>org.codehaus.groovy.maven.runtime</groupId>
            <artifactId>gmaven-runtime-default</artifactId>
            <version>1.0-rc-3</version>
            <scope>test</scope>
        </dependency>
...
            <plugin>
                <groupId>org.codehaus.groovy.maven</groupId>
                <artifactId>gmaven-plugin</artifactId>
                <version>1.0-rc-3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generateStubs</goal>
                            <goal>compile</goal>
                            <goal>generateTestStubs</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

在eclipse中新建source folder, src/test/groovy,在其中创建groovy test case即可

Maven recipe #0

问题: 多个root pom的dependencyManagement有重复的内容,希望统一管理。
解决:
新建一个空pom.xml,在dependencyManagement中指定这些依赖,如

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>info.sunng</groupId>
    <artifactId>root</artifactId>
    <packaging>pom</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <dependencyManagement>

        <dependencies>
            <dependency>
                <groupId>info.sunng</groupId>
                <artifactId>X</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

将这个pom.xml部署到你的Maven私服。

在其他root pom中添加这样的dependencyManagement

....
            <dependency>
                <groupId>info.sunng</groupId>
                <artifactId>root</artifactId>
                <version>0.0.1-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
....

My First Hello World Web App Using Compojure

Compojure是一个用Clojure写成的类似Sinatra的Web框架。Leiningen是一个新的Clojure构建工具,它用Maven来处理依赖管理,而通过封装Lancet(基于Ant)来实现build-in的task。

以上是背景介绍。以下是HelloWorld。

创建一个目录作为工程目录

mkdir compojure-app
cd compojure-app

像创建build.xml和pom.xml一样创建project.clj

(defproject info.sunng/compojure-app "0.0.1"
        :description "A demo app running on compojure framework"
        :dependencies [[org.clojure/clojure "1.1.0"]
                        [org.clojure/clojure-contrib "1.1.0"]
                        [compojure "0.3.2"]]
        :dev-dependencies [[leiningen/lein-swank "1.1.0"]]
        :main info.sunng.compojureapp.helloworld)

首行定义了项目的groupId, artifactId和version,其后的是maven风格的依赖定义,最后我们还定义了程序的主类。

这个helloworld只有一个文件,被放在src/info/sunng/compojureapp/目录下

(ns info.sunng.compojureapp.helloworld (:gen-class) (:use compojure))

(defroutes example-routes
    (GET "/" "Hello W0rld")
    (ANY "*" [404 "Page Not Found"]))

(defn -main []
    (run-server {:port 8080} "/*" (servlet example-routes)))

中间就是Sinatra风格的URL映射定义,最后在主类中通过一个run-server方法以嵌入式的方式运行一个jetty

回到工程目录,执行构建

lein deps
lein compile
lein uberjar

通过uberjar可以将依赖通通打入一个jar包中,接着就可以通过
java -jar compojure-app-standalone.jar
启动你的Web程序了,很酷吧

除了这种方式,还可以通过
lein swank
启动一个swank server(project.clj中定义了dev-dependency),再用Emacs的SLIME交互式地运行程序。

参考

We are using Hudson for continuous integration

经历了持续两周的人肉集成,今天上午抢得一台Linux机器,终于尝试用hudson来替代人肉构建。

Hudson的安装和配置远比想象的简单,只要下载发布的war包,在相应的目录执行
nohup java -jar hudson.war > hudson.log 2>&1 &
即可启动到后台

hudson的web图形界面可以胜任几乎全部工作。我们主要使用maven来构建项目,hudson提供了非常强大的功能:邮件提醒(通过插件支持twitter/jabber/irc提醒);自动构建,除了基本的定时构建以外,hudson还会自动解析其管理的项目之间的依赖关系,从而实现级联的构建,这个功能非常震撼。

刚刚上手以后我还安装了两个插件。一个是build-timeout插件,可以之间一次构建的超时时间:我们的项目中有老大写的交互式的maven配置插件,一旦这个插件在自动构建时运行会阻塞构建的流程。另一个是scp发布插件,可以自动scp一个文件到远程服务器上,我用这个插件来把构建版本发布到运行环境中,只要在适当的时候重启一下运行环境的服务器就可以实现部署了。不过scp插件由于上游依赖的问题貌似不支持putty生成的privatekey,这是暂时的美中不足。

配置了整整一天,终于有了CI工具,我就彻底解放出来可以做其他事了。