IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Java technology | Web development | Open source  >

Grails をマスターする: GORM: おかしな名前の真面目な技術

データベースと Grails を理解する

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 初級

Scott Davis (scott@aboutgroovy.com), Editor in Chief, AboutGroovy.com

2008年 2月 12日

どんな優れた Web フレームワークにも、確固としたパーシスタンス・ストラテジーは必要です。連載「Grails をマスターする」の第 2 回目では、Scott Davis が GORM (Grails Object Relational Mapping) API を紹介します。この記事を読めば、Grails アプリケーションでテーブル間の関係を作成し、データ検証ルールを設定し、さらにリレーショナル・データベースを変更するのがいかに簡単であるかがわかるはずです。

先月から始まった連載「Grails をマスターする」の第 1 回目では、Grails という名前の新しい Web フレームワークを紹介しました。Grails にはモデル・ビュー・コントローラーによりコンサーン (関心事) を分離するという概念や「Convention over Configuration (設定より規約)」といった最近のプラクティスが採り入れられています。これらのプラクティスを組み込み scaffold 機能と結び付けた Grails では、最低限の機能を持った Web サイトをわずか数分で立ち上げることができます。

今回の記事では Grails によって容易になるもう 1 つの領域として、GORM (Grails Object Relational Mapping) API を使って永続性を実現する方法に焦点を当てます。最初にオブジェクト・リレーショナル・マッパー (ORM) を紹介し、1 対多の関係を作成する方法を説明します。次に、アプリケーションが「ガーベッジ・イン/ガーベッジ・アウト」シンドローム (ゴミのような情報を入力してもゴミのような情報しか得られないということ) に悩まされないようにするためのデータ検証について説明し、続いて Grails の ORM ドメイン固有言語 (DSL) の実例を紹介します。この DSL によって、POGO (Plain Old Groovy Object) を裏で永続化させる方法を微調整できます。そして最後に、いかに簡単にリレーショナル・データベースを交換できるかを実演します。格好の交換対象となるのは、JDBC ドライバーと Hibernate ダイアレクトを使ったあらゆるデータベースです。

この連載について

Grails は、Spring や Hibernate などのよく知られた Java 技術に「Convention over Configuration」といった現代のプラクティスを盛り込んだ最新の Web 開発フレームワークです。Groovy で作成された Grails は既存の Java コードをシームレスに統合するだけでなく、スクリプト言語ならではの柔軟性と動的機能を与えてくれます。Grails を学んだら、Web 開発に対する今までの見方がまったく違ってくるはずです。

ORM の定義

リレーショナル・データベースは 1970年代後半から出回っていますが、ソフトウェア開発者は今でもリレーショナル・データベースからデータを効率的に出し入れするのに苦労しています。今日のソフトウェアがベースとしているのは、データベースの使用方法として最も人気のあるリレーショナル理論ではなく、オブジェクト指向の原則だからです。

ORM は、データベースとコードとの間で何度もデータを移し変える苦労を軽減するために急成長したプログラムです。この問題に対処するためによく使われる Java API としては Hibernate、TopLink、JPA (Java Persistence API) の 3 つが挙げられますが、どの 1 つとして完璧ではありません (「参考文献」を参照)。この問題はあまりにも根強いため、問題自体に「Object-Relational Impedance Mismatch (オブジェクトとリレーショナルのインピーダンス・ミスマッチ)」というキャッチフレーズが付いているほどです (「参考文献」を参照)。

GORM は Hibernate を覆うシン Groovy ファサードです (「Gibernate」より「GORM」のほうが言いやすいからだと思います)。つまり、既存の Hibernate の芸当はすべて変わらず有効だということですが (HBM マッピング・ファイルとアノテーションの両方が完全にサポートされるなど)、この記事では GORM がもたらす興味深い機能に焦点を当てます。

オブジェクト指向データベースと Grails

一部の開発者はオブジェクトをネイティブにサポートするデータベースを使うことで、オブジェクト・リレーショナルのインピーダンス・ミスマッチという問題をなくそうとしています。Ted Neward による developerWorks の連載「多忙な Java 開発者のための db4o ガイド」は、この問題に真っ向から取り組み、最近のオブジェクト指向データベースの実例を紹介しています。エンタープライズ開発者が Grails 用の db4o プラグインを作成しているのは、オブジェクト指向データベースの人気がリレーショナル・データベースと同じくらいまで上がっていることを証明していて、素晴らしいことです。しかし今のところは GORM と従来のリレーショナル・デーベースを使うのが、Grails で永続性を実現する上で最善の方法となります。

1 対多の関係を作成する方法

POGO をデータベース・テーブルに保存するという問題は軽く見られがちです。実際、1 つの POGO を 1 つのテーブルにマッピングするのであれば事は至って簡単で、POGO プロパティーはテーブルの列にぴったり対応します。しかし、互いに関連する 2 つの POGO を設定するなどしてオブジェクト・モデルを少しでも複雑にすると、途端に事が難航してきます。

その一例として、先月の記事で着手した Trip Planner の Web サイトを見てください。当然のことながら、このアプリケーションで重要な役割を果たすのは Trip という POGO です。テキスト・エディターで grails-app/domain/Trip.groovy (リスト 1 を参照) を開いてください。


リスト 1. Trip クラス
                
class Trip { 
  String name
  String city
  Date startDate
  Date endDate
  String purpose
  String notes
}

リスト 1 の属性はそれぞれ簡潔かつ単純に Trip テーブルの対応するフィールドにマッピングされます。前回の記事で説明したように、Grails が起動すると grails-app/domain ディレクトリーに保存されたすべての POGO に、対応するテーブルが自動的に作成されます。Grails がデフォルトで使用するのは組み込みの HSQLDB データベースですが、この記事を読み終えるまでには、自分で選んだリレーショナル・デーベースを使えるようになっているはずです。

旅行には飛行機が付き物なので、道理として Airline クラス (リスト 2 を参照) も作成します。


リスト 2. Airline クラス
                
class Airline { 
  String name
  String url
  String frequentFlyer
  String notes
}

ここで、この 2 つのクラスをリンクさせなければなりません。Xyz 航空を利用したシカゴへの旅行をスケジュールするには、Java コードでの場合と同じように Groovy でそれを表現します。つまり、Trip クラスに Airline プロパティーを追加するということです (リスト 3 を参照)。この手法は、Object Composition (オブジェクトの合成) と呼ばれます (「参考文献」を参照)。


リスト 3. Trip クラスへの Airline プロパティーの追加
                
class Trip { 
  String name
  String city
  ...
  Airline airline
}

ソフトウェア・モデルとしてはこれで十分ですが、リレーショナル・データベースでは多少異なる手法を採ります。テーブル内のすべてのレコードには主キーと呼ばれる一意に決まる ID があるので、Trip テーブルに airline_id フィールドを追加すればレコード同士をリンクできるようになります (この例では、「Xyz 航空」のレコードを「シカゴ旅行」のレコードにリンクします)。これは 1 対多の関係と呼ばれます。ただし、1 つの航空会社には複数の旅行が関連付けられるはずです (1 対 1 および多対多の関係の例は、Grails のオンライン・マニュアルに記載されています。「参考文献」を参照してください。)。

このデータベース・スキーマ案には 1 つだけ問題があります。データベースの標準化 (「参考文献」を参照) には成功したかもしれませんが、テーブル内の列がソフトウェア・モデルと一致しなくなってしまったからです。そこで Airline フィールドを AirlineId フィールドに置き換えたとすると、実装の詳細 (データベースで POGO を永続化しているということ) がオブジェクト・モデルに漏れてしまうことになります。Joel Spolsky は著書のなかで、これを Law of Leaky Abstractions (抽象化の破綻の法則) と呼んでいます (「参考文献」を参照)。

GORM では、オブジェクト・モデルを Groovy でつじつまが合うように表現できるようにすることによって、この抽象化の破綻という問題を軽減しています。GORM は裏でリレーショナル・データベースの問題に対処してくれますが、この後すぐに説明するように、デフォルト設定は簡単に上書きすることができます。GORM はデータベースの詳細を隠す不透明な抽象化層ではなく、半透明の階層として、型にとらわれずに正しいことを行おうとします。その一方で、その振る舞いをカスタマイズする必要がある場合にはそれを邪魔することはしません。そのため、オブジェクト・モデルとリレーショナル・モデル、双方の長所を生かすことができます。

Trip POGO には既に Airline プロパティーを追加しているので、1 対多の関係を完成するために今度は Airline POGO に hasMany 設定を追加します (リスト 4 を参照)。


リスト 4. Airline で作成する 1 対多の関係
                
class Airline { 
  static hasMany = [trip:Trip]

  String name
  String url
  String frequentFlyer
  String notes
}

静的 hasMany 設定とは Groovy ハッシュマップのことで、キーは trip、値は Trip クラスとなります。Airline クラスに他にも 1 対多の関係を設定する必要がある場合は、キーと値のペアをコンマで区切ったリストを大括弧のなかに入れてください。

削除のカスケード

モデルがこのままの状態だと、最終的にはデータベースに親が含まれないことになります。Airline を削除すると、存在しない親を指す Trip レコードが残ってしまうためです。このようなシナリオを避けるには、対応する static belongsTo のハッシュマップを 1 対多の多の側のクラスに追加するという方法があります。

ここで、新しい 1 対多の関係が実際にどのように機能するかを調べため、grails-app/controllers に簡単な AirlineController クラスを作成します (リスト 5 を参照)。


リスト 5. AirlineController クラス
                
class AirlineController { 
  def scaffold = Airline
}
      

前回の記事を思い出してください。def scaffold は実行時に、Grails に対して基本メソッドである list()save()edit() を作成するよう動的に指示します。さらに、GSP (GroovyServer Page) ビューも作成するよう Grails に動的に指示を与えます。したがって、必ず TripControllerAirlineController の両方に def scaffold が含まれるようにしてください。また、grails generate-all と入力して作成した GSP 成果物 (trip または airline ディレクトリー) が grails-app/views に 1 つでも残っている場合は、その成果物を削除してください。この例では、Grails がコントローラーとビュー両方の scaffold を動的に作成することが必須だからです。

ドメイン・クラスとコントローラーはこれで用意できたので、ここで Grails を起動します。grails prod run-app と入力してアプリケーションを実動モードで実行してください。万事順調に行けば、以下のメッセージが表示されます。

Server running. Browse to http://localhost:8080/trip-planner

ポート競合のエラーを回避する方法

別のアプリケーションがポート 8080 で実行中になっている場合は、先月の記事でポートを変更する方法を確認してください。この記事では問題を避けるため、ポート 9090 で Grails インスタンスを実行します。

ブラウザーには、AirlineControllerTripController 両方のためのリンクが表示されているはずです。 AirlineController をクリックして、Xyz Airlines の詳細を入力します (図 1 を参照)。


図 1. 1 対多の関係: 「1」の側
1 対多の関係: 「1」の側

アルファベット順に表示されるフィールドが好みでないとしても気にしないでください。この設定は次のセクションで上書きするからです。

今度は新しい旅行を作成します (図 2 を参照)。Airline のコンボ・ボックスに注目してください。ここには Airline テーブルに追加するすべてのレコードが表示されます。主キーの「漏えい」についてはご心配なく。次のセクションで、よりわかりやすいラベルを追加する方法を説明します。


図 2. 1 対多の関係: 「多」の側
1 対多の関係: 「多」の側



上に戻る


Naked Object

これまでの説明で、Airline POGO (静的 hasMany) にヒントを追加すると、裏でテーブルが作成される仕組みにも、フロントエンドで生成されるビューにも影響することがわかったはずです。ドメイン・オブジェクトを修飾するこの Naked Objects パターン (「参考文献」を参照) は、Grails 全体で広範に使用されます。この情報を直接 POGOに追加することで、外部 XML 構成ファイルが不要になります。すべての情報を一箇所に集めることは、生産性にとって何よりのメリットです。

例えば、Airline クラスに toString メソッドを追加するだけで、コンボ・ボックスには主キーが漏えいによって表示されることがなくなります (リスト 6 を参照)。


リスト 6. Airline への toString メソッドの追加
                
class Airline { 
  static hasMany = [trip:Trip]

  String name
  String url
  String frequentFlyer
  String notes
    
  String toString(){
    return name
  }
}

これで、今後は航空会社の名前がコンボ・ボックスに表示されるようになります。しかし、ここで何よりも感心させられる点は、Grails がまだ実行中であれば Airline.groovy を保存するだけで変更内容が反映されるということです。ブラウザーで新しい Trip を作成して実際に確認してみてください。ビューは動的に生成されるため、サーバーをリブートする必要なく、正しいビューが表示されるまでテキスト・エディターとブラウザーの間を素早く行ったり来たりできます。

次はアルファベット順に並んだフィールドの順序付けを修正します。それには、static constraints ブロックという別の構成を POGO に追加します。このブロックに、リスト 7 に記載した順でフィールドを追加してください (これらの制約は、テーブル内の列の順序には影響しません。影響されるのはビューだけです)。


リスト 7. Airline でのフィールド順序の変更
                
class Airline { 
  static constraints = {
    name()
    url()
    frequentFlyer()
    notes()  
  }

  static hasMany = [trip:Trip]
    
  String name
  String url
  String frequentFlyer
  String notes
  
  String toString(){
    return name
  }
}

上記の変更を Airline.groovy ファイルに保存してからブラウザーで新しい航空会社を作成すると、フィールドはリスト 7 で指定した順序で表示されます (図 3 を参照)。


図 3. カスタマイズしたフィールド順序
カスタマイズしたフィールド順序

POGO でフィールド名を 2 回も入力させられるのでは DRY (Don't Repeat Yourself) の原則 (「参考文献」を参照) に違反しているじゃないか、と責めるのはちょっと待ってください。フィールド名を別のブロックに抽出しているのには正当な理由があるからです。リスト 7 の static constraints ブロックに含まれる括弧は、そのうち空ではなくなります。




上に戻る


データ検証

static constraints ブロックはフィールドの順序を指定するだけでなく、検証ルールを設定するためにも使えます。例えばこのブロックで、String フィールドに長さの制限を設けたり (デフォルトは 255 文字)、String の値を特定のパターン (E メール・アドレスや URL) に確実に一致させることができます。さらに、フィールドをオプションあるいは必須にすることもできます。有効な検証ルールの完全な一覧については、Grails のオンライン・マニュアル (「参考文献」にリンクを記載) を参照してください。

リスト 8 に、この制約ブロックに検証ルールを追加した Airline クラスを示します。


リスト 8. Airline へのデータ検証の追加
                
class Airline { 
  static constraints = {
    name(blank:false, maxSize:100)
    url(url:true)
    frequentFlyer(blank:true)
    notes(maxSize:1500)  
  }

  static hasMany = [trip:Trip]
    
  String name
  String url
  String frequentFlyer
  String notes

  String toString(){
    return name
  }
}

変更した Airline.groovy ファイルを保存して、ブラウザーで新しい航空会社を作成してください。検証ルールに違反している場合には警告メッセージが表示されます (図 4 を参照)。


図 4. 検証による警告
検証による警告

警報メッセージは、grails-app/i18n ディレクトリーにある messages.properties ファイルで変更することができます。デフォルト・メッセージは複数の言語でローカライズされていることに注意してください (クラス別、フィールド別のカスタム・メッセージを作成する方法については、Grails オンライン・マニュアルの検証に関するセクションを参照してください)。

リスト 8 に記載された制約のほとんどはビュー層にしか影響しませんが、パーシスタンス層に影響する制約もいくつかあります。例えば、データベース内の name 列には 100 文字の長さ制限が設けられています。notes フィールドは、(255 文字を超えるフィールドの場合) ビュー内で入力フィールドからテキスト域に変わるだけでなく、VARCHAR 列から TEXT 列、CLOB列、または BLOB 列にも変わるようになっています。このような制約を左右するのは、裏で使用しているデータベースの種類とその Hibernate ダイアレクト次第です。つまり、もう当たり前のように聞こえるかもしれませんが、上書きすることができるのです。




上に戻る


Grails の ORM DSL

Hibernate のデフォルトを上書きするには、HBM マッピング・ファイルまたはアノテーションという便利な構成方法を使用できますが、Grails では Naked Object 様式の 3 つ目の方法を用意しています。それは、static mapping ブロックを POGO に追加してデフォルトのテーブル名とフィールド名を上書きするという方法です (リスト 9 を参照)。


リスト 9. GORM DSL の使用
                
class Airline { 
  static mapping = {
    table 'some_other_table_name'
    columns {
      name column:'airline_name'
      url column:'link'
      frequentFlyer column:'ff_id'
    }
  }

  static constraints = {
    name(blank:false, maxSize:100)
    url(url:true)
    frequentFlyer(blank:true)
    notes(maxSize:1500)  
  }

  static hasMany = [trip:Trip]
    
  String name
  String url
  String frequentFlyer
  String notes

  String toString(){
    return name
  }
}

このようなマッピング・ブロックが特に役立つのは、既存のレガシー・テーブルを新しい Grails アプリケーションで使用する場合です。ここでは基本的なことにしか触れませんが、ORM DSL を使用すると、テーブル名とフィールド名の単純な再マップだけでなく遥かに多くのことを行えます。その例として、列ごとにデフォルト・データ型を上書きしたり、主キーを作成する方法を調整したり、さらには複合主キーを指定したりすることまで可能です。他にも Hibernate キャッシュ設定を変更したり、外部キーの関連用に使用するフィールドを調整するなど、さまざまなことを行えます。

重要な点は、これらの設定はすべて POGO という 1 つの場所に集約されるということです。




上に戻る


DataSource.groovy について

これまでに行ったすべての作業は、個々のクラスをカスタマイズすることに重点が置かれていました。ここで、一歩離れて全体的な変更を加えることにします。すべてのドメイン・クラスで共有されるデータベース固有の構成は、grails-app/conf/DataSource.groovy という 1 つの共有ファイル (リスト 10 を参照) に保存されます。このファイルをテキスト・エディターで開き、ファイル全体に目を通してください。


リスト 10. DataSource.groovy
                
dataSource {
  pooled = false
  driverClassName = "org.hsqldb.jdbcDriver"
  username = "sa"
  password = ""
}
hibernate {
  cache.use_second_level_cache=true
  cache.use_query_cache=true
  cache.provider_class='org.hibernate.cache.EhCacheProvider'
}
// environment specific settings
environments {
  development {
    dataSource {
      dbCreate = "create-drop" // one of 'create', 'create-drop','update'
      url = "jdbc:hsqldb:mem:devDB"
    }
  }
  test {
    dataSource {
      dbCreate = "update"
      url = "jdbc:hsqldb:mem:testDb"
    }
  }
  production {
    dataSource {
      dbCreate = "update"
      url = "jdbc:hsqldb:file:prodDb;shutdown=true"
    }
  }
}

dataSource ブロックでは、データベースに接続するために使用する driverClassNameusername、および password を変更することができます。hibernate ブロックではキャッシュ設定を調整できます (Hibernate のエキスパートでない限り、ここでの調整は必要ありません)。しかしまさに興味深いことが起こるのは、environments ブロックです。

前回の記事で説明したように、Grails の実行モードには開発 (development)、テスト (test)、実動 (production) の 3 つがあります。grails prod run-app と入力すると、Grails は production ブロックに含まれるデータベース設定を使用します。usernamepassword を環境別に調整したいのであれば、ただ単に dataSource ブロックの設定を個々の environment ブロックにコピーして、これらの設定の値を変更すればいいだけの話です。すると、environment ブロックの設定が dataSource ブロックの設定を上書きします。

url 設定は JDBC 接続ストリングです。実動モードでは、HSQLDB はファイル・ベースのデータ・ストアを使用します。一方、開発およびテスト・モードでは HSQLDB はメモリー内のデータストアを使用します。前回、サーバーが再起動しても Trip レコードを維持したいのであれば実動モードで実行するように言いましたが、これで、開発モードとテスト・モードで同様のことを行う方法もわかったはずです。つまり、production ブロックの url 設定をコピーすればいいだけです。もちろん、Grails に DB2 や MySQL、あるいはその他の従来からのファイル・ベースのデータベースを指定するという方法でも、レコードの消失という問題は解決できます (DB2 および MySQL の場合の設定はこの後すぐに説明します)。

dbCreate の値によっても、異なる環境で振る舞いを変えることができます。dbCreate は基礎となる hibernate.hbm2ddl.auto 設定の別名で、この設定により裏で Hibernate がテーブルを管理する方法が決まります。dbCreate が create-drop に設定されている場合、Hibernate は起動時にテーブルを作成し、シャットダウン時に破棄します。この値を create に変更すると、Hibernate は新しいテーブルを作成し、必要に応じて既存のテーブルを変更しますが、再起動されるたびにすべてのレコードが削除されることになります。実動モードでのデフォルト値、update では、再起動が行われてもすべてのデータが維持されるとともに、必要に応じてテーブルが作成または変更されます。

レガシー・データベースに対して Grails を使用している場合には、できるだけ dbCreate 値をコメント・アウトしてください。この値をコメント・アウトすれば、Hibernate がデータベース・スキーマに影響を与えることはありません。これによって、基礎となるデータベースにデータ・モデルを同期させる作業が必要になることにもなりますが、誰が許可もなくデータベース・テーブルを変更し続けているのか突き止めようと躍起になっている DBA からの怒りの E メール件数は劇的に減るはずです。

独自のカスタム環境を追加するのは簡単です。例えば会社のプログラムがベータ版だとした場合、DataSource.groovy 内の他のブロックの隣に beta ブロックを作成します (データベース関連ではない設定の場合、grails-app/conf/Config.groovy にenvironments ブロックを追加することもできます)。これで grails -Dgrails.env=beta run-app と入力すれば、Grails が beta モードで起動することになります。




上に戻る


データベースの変更

dbCreate 設定を使用して Hibernate にテーブルを管理させる場合、まずデータベースを作成してログインし、JDBC ドライバーを lib ディレクトリーにコピーして DataSource.groovy で設定を調整するという簡単な 3 段階のプロセスで、Grails に新しいデータベースを指定することができます。

データベースとユーザーの作成手順は、製品によってまちまちです。DB2 の場合にはステップバイステップのチュートリアルがあるので、オンラインでこれに従ってください (「参考文献」を参照)。データベースとユーザーを作成したら、DataSource.groovy を調整してリスト 11 に示す値を使用するようにします (このリストに示した値は、データベースには trip という名前が付けられているという前提に従っています)。


リスト 11. DataSource.groovy の DB2 用の設定
                
  driverClassName = "com.ibm.db2.jcc.DB2Driver"
  username = "db2admin"
  password = "db2admin"
  url = "jdbc:db2://localhost:50000/trip"

MySQL が既にインストールされている場合は、リスト 12 のステップに従って root ユーザーとしてログインし、trip データベースを作成します。


リスト 12. MySQL データベースの作成
                
$ mysql --user=root
mysql> create database trip;
mysql> use trip;
mysql> grant all on trip.* to grails@localhost identified by 'server';
mysql> flush privileges;
mysql> exit
$ mysql --user=grails -p --database=trip

データベースとユーザーを作成したら、リスト 13 に示す値を使用するように DataSource.groovy を調整します。


リスト 13. DataSource.groovy の MySQL 用の設定
                
  driverClassName = "com.mysql.jdbc.Driver"
  username = "grails"
  password = "server"
  url = "jdbc:mysql://localhost:3306/trip?autoreconnect=true"

データベースの作成、JDBC ドライバー JAR の lib ディレクトリーへのコピー、そして DataSource.groovy での値の調整が済んだら、もう 1 度 grails run-app と入力します。これで、Grails は HSQLDB 以外のデータベースを使用するようになります。




上に戻る


まとめ

GORM のツアーはこれで完了です。このツアーを通して ORM とは何か、そして検証およびテーブルの関係を管理する方法、HSQLDB を任意のデータベースに置き換える方法が十分に理解できたはずです。

この連載の次回の記事で焦点を当てるのは、Web 層です。GSP およびさまざまな Groovy TagLib の詳細を掘り下げるとともに、GSP を個々の部分 (複数のページで再利用できるマークアップのフラグメント) に分割する方法を紹介します。さらには、scaffold によって生成されるビューに使われるデフォルト・テンプレートをカスタマイズする方法も説明します。

それまではゆっくり、時間をかけて Grails をマスターしてください。



参考文献

学ぶために

製品や技術を入手するために
  • DB2: DB2 9.5 データ・サーバーのテスト・ドライブをダウンロードしてください。

  • Grails: Grails の最新リリースをダウンロードしてください。


議論するために


著者について

Scott Davis

Scott Davis は国際的に知られた著者、講演者、そしてソフトウェア開発者です。彼の著書には、『Groovy Recipes: Greasing the Wheels of Java』、『GIS for Web Developers: Adding Where to Your Application』、『The Google Maps API』、『JBoss At Work』などがあります。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



はいいいえわからない
 


 


12345
不充分・不完全である大変素晴らしい
 


この記事を共有する

はてなブックマーク はてなブックマーク livedoorクリップ livedoorクリップ del.icio.us del.icio.us Buzzurl(バザール) Buzzurl(バザール) Choix! Choix!
Saafブックマーク Saafブックマーク FC2ブックマーク FC2ブックマーク MM/memo MM/memo ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
CZブックマーク CZブックマーク newsing newsing




上に戻る


Java およびすべての Java 関連の商標およびロゴは、Sun Microsystems, Inc. の米国およびその他の国における商標です。 他の会社名、製品名およびサービス名等はそれぞれ各社の商標です。

    日本IBMについて プライバシー お問い合わせ