torutkのブログ

ソフトウェア・エンジニアのブログ

SQLite3データベースをGrailsから使う

id:torutk:20090622の続きで、GrailsからSQLite3データベースを使う方法の模索です。

GrailsはデータベースマッピングHibernateを使っています。Hibernateは残念ながらSQLiteをサポートしていません。そこで、HibernateSQLiteを扱わせるため、org.hibernate.dialect.Dialectクラスを派生したSQLiteDialectクラスを用意します。

次に、Grailsアプリケーション実行時にこのSQLiteDialectを読み込ませる設定を行います。

SQLiteDialect

誰か既に作成していないかと探してみると、2つほど見つかりました(以下URL)。
http://code.google.com/p/hibernate-sqlite/
http://github.com/gwenn/sqlite-dialect/blob/73d07f0990ecfd9b9a869066b8f7b3e023af39ff/org/hibernate/dialect/SQLiteDialect.java

ここでは前者のサイトからダウンロードし、dialect/SQLiteDialect.class 1つだけをJARファイル化します。

~$ jar cvf sqlitedialect.jar dialect
~$

このsqlitedialectと、id:torutk:20090611で記載のsqlitejdbcの2つのJARファイルを、Grailsのプロジェクトディレクトリのlib下にコピーします。

grails-app/conf/DataSource.groovy

次に、SQLiteを使うよう記述を変更します。

dataSource {
  pooled = true
  driverClassName = "org.sqlite.JDBC"
  username = ""
  password = ""
  dialect = dialect.SQLiteDialect.class
}
hibernate {
  cache.use_second_level_cache=true
  cache.use_query_cache=true
  cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
  development {
    dataSource {
      dbCreate = "create-drop" // one of 'create', 'create-drop','update'
      url = "jdbc:sqlite::memory:"
    }
  }
  test {
    dataSource {
      dbCreate = "update"
      url = "jdbc:sqlite::memory:"
    }
  }
  production {
    dataSource {
      dbCreate = "update"
      url = "jdbc:sqlite:sqlite.db"
    }
  }
}
  • dialectの記述では、.classを付けないものを見かけますが、.classを付けないと、CouldNotDetermineHibernateDialectException例外がスローされてしまいます。この例外は、HibernateDialectDetectionFactoryBean.javaでスローされるもので、に記述されています。そこから先は追えていません。
    • (2009/12/2追記) grails-1.2.0-SNAPSHOTでは、dialectの記述に.classがあるとCouldNotDetermineHibernateDialectException例外が発生していました。試しに.classを削除すると起動しました。
  • "jdbc:sqlite::memory:"は、SQLiteのインメモリデータベースを指定するものであり、再起動すると消えます。

実動(production)モードでは2回目に起動するとエラー

さて、一見調子がよさそうに見えたのですが、実動モードで起動、すなわちSQLiteのファイルデータベースを指定した場合、2回目の起動でSQLException例外がスローされます。

Caused by: java.sql.SQLException: not yet implemented
        at org.sqlite.MetaData.getImportedKeys(MetaData.java:503)
        at org.hibernate.tool.hbm2ddl.TableMetadata.initForeignKeys(TableMetadata.java:141)
        at org.hibernate.tool.hbm2ddl.TableMetadata.<init>(TableMetadata.java:57)
        at org.hibernate.tool.hbm2ddl.DatabaseMetadata.getTableMetadata(DatabaseMetadata.java:113)

SQLiteJDBCのソースをダウンロードし、org.sqlite.MetaDataのgetImportedKeysメソッドを見ると、

    public ResultSet getImportedKeys(String c, String s, String t)
        throws SQLException { throw new SQLException("not yet implemented"); }

となっており、確かに例外が発生します。しかし、これでは困るので、他にJDBCドライバがないかと探してみたところ、上述では試していないSQLiteDialectクラスのもう一つの作成者(gwenn氏)が、SQLiteJDBCを作成・公開していました。
http://github.com/gwenn/sqlitejdbc/tree
gwenn氏のSQLiteJDBCは、上記でエラーになったhttp://www.zentus.com/sqlitejdbc/のVer.054からフォークしたものとのことです。

gwenn版では、MetaData.getImportedKeysメソッドが実装されています。

public ResultSet getImportedKeys(String catalog, String schema,
       String table) throws SQLException {
    return getForeignKeys(null, table, false);
}

そこで、gwenn氏のsqlitejdbcを使用することにします。
まず、上記URLをWebブラウザで開き、[download]ボタンをクリックします。ZIPかTARかを選択する画面が出てくるので、とりあえずZIPを選択して保存します。

次に、ダウンロードしたファイルを展開して、makeを実行し、生成されるjarファイルにネイティブライブラリを追加します。この手順は、Makefileの先頭のコメントに記述されてます。

~$ unzip gwenn-sqlitejdbc-XXXXX.zip
~$ cd gwenn-sqlitejdbc-XXXXX
gwenn-sqlitejdbc-XXXXX$ make
    :
gwenn-sqlitejdbc-XXXXX$ cd build
build$ mv Default-i686/libsqlitejdbc.so linux-x86.lib
build$ jar uf sqlite-jdbc-v054-native.jar linux-x86.lib
build$ 

このSQLiteJDBCのJARをプロジェクトのlib下にあるものと置き換えで、再度実動モードで実行したら、例外はスローされることなく起動しました。