SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

JDBCを使ってデータベースのデータをコピーする

さまざまな用途に応用できるデータベースユーティリティの作成

  • X ポスト
  • このエントリーをはてなブックマークに追加

テーブルおよびカタログ情報にアクセスする

 データベースをコピーするには、データベースに関するカタログ情報を取得する必要があります。具体的には、データベースのテーブルのリストと、各テーブル内のすべての列のリストが必要になります。この情報に基づいて、DataMoverはデータのコピーに必要なSQL文を作成します。

 Databaseクラスには、データベースのすべてのテーブルのリストを返すlistTablesというメソッドがあります。JDBCでテーブルのリストを取得する方法はいくつかあります。多くのデータベースには、テーブルのリストを取得するためのストアドプロシージャやシステムテーブルが用意されています。しかし、ストアドプロシージャやシステムテーブルの名前はデータベースごとに異なるため、テーブルのリストを取得する場合はJavaクラスのDatabaseMetaDataを使うのが最も互換性の高い方法になります。このクラスは、テーブルの名前など、データベースに関する情報を提供します。

 listTablesメソッドは、まずテーブルリストを格納するコレクションを作成します。

Collection<String> result = new ArrayList<String>();

 次に、テーブルのリストを保持するResultSetを作成し、DatabaseMetaDataオブジェクトを取得します。

ResultSet rs = null;
try
{
DatabaseMetaData dbm = 
   connection.getMetaData();

 DatabaseMetaDataクラスにはたくさんのプロパティとメソッドがありますが、この例ではテーブルを取得するだけなので、getTablesメソッドを使います。このメソッドは3つの引数をとります。catalog文字列とschemaPattern文字列(この例ではどちらもnull)、および取得するテーブルの種類を示す文字列配列です。次の例では、"TABLE"という1つの要素を含む文字列配列を作成し、この配列を3つ目の引数としてgetTablesメソッドを呼び出しています。

String types[] = { "TABLE" };
rs = dbm.getTables(null, null, "", types);

 この場合のgetTablesメソッドは、すべてのテーブルのリストが含まれるResultSetを返します。後は、結果に対して繰り返し処理を行い、各テーブル名をresultコレクションに追加するだけです。

  while (rs.next())
  {
    String str = rs.getString("TABLE_NAME");
    result.add(str);
  }
} catch (SQLException e)
{
  throw (new DatabaseException(e));
}

 finallyブロックを用意して、ResultSetが正しく閉じられるようにします。

finally
{
  if( rs!=null )
  {
    try
    {
      rs.close();
    } catch (SQLException e)
    {
    }
  }
}

 最後に、getTablesメソッドはテーブルのリストを返します。

return result;

 テーブルのリストを取得した後は、それらを処理してテーブル内の列のリストを取得します。それにより、適切なCREATE TABLE文に加えて、現在のテーブルに適合するINSERT文とSELECT文も作成できるようになります。テーブルの列のリストを取得するために、DatabaseクラスにはlistColumnsメソッドが含まれています。このメソッドの処理はテーブルリストの取得と似ているので、詳しくは説明しません。

 データベースカタログ情報(テーブルと列)を取得したら、必要なSQL文を生成することができます。

CREATE TABLE文を生成する

 データベースカタログ情報からCREATE TABLE文を生成するルーチンは、さまざまな目的に利用できます。DatabaseクラスのgenerateCreateメソッドは、まずCREATE TABLE文を格納するためのStringBufferを作成します。

StringBuffer result = new StringBuffer();

 次に、SELECT * FROM [table]という文を作成して実行し、得られた結果からテーブル構造のメタデータを取得します。

try
{
StringBuffer sql = new StringBuffer();
sql.append("SELECT * FROM ");
sql.append(table);
ResultSet rs = executeQuery(sql.toString());
ResultSetMetaData md = rs.getMetaData();

 先にCREATE TABLEの部分を作成します。列は後から埋めることができます。

result.append("CREATE TABLE ");
result.append(table);
result.append(" ( ");

 すべての列に対してループ処理を行い、列の名前をCREATE TABLE文に追加します。

for (int i = 1; i <= md.getColumnCount(); i++)
{

 CREATE TABLE文の列はコンマ区切りなので、最初の列を除き、各列名の後ろにコンマを追加する必要があります。

if (i != 1)
  result.append(',');
result.append(md.getColumnName(i));
result.append(' ');

 列の型を取得し、それを文の後ろに追加します。

String type = processType(md.getColumnTypeName(i), 
  md.getPrecision(i));
result.append(type);

 型の後に精度を指定する必要があります。精度が65535を超える場合は、BLOB(Binary Large Object)またはText型なので精度は不要です。そうでない場合は、精度と桁数を指定します。

if (md.getPrecision(i) < 65535)
{
  result.append('(');
  result.append(md.getPrecision(i));
  if (md.getScale(i) > 0)
  {
    result.append(',');
    result.append(md.getScale(i));
  }
  result.append(") ");
} else
    result.append(' ');

 型が数値で、種類が符号なしの場合は、UNSIGNED句を指定します。

if (this.isNumeric(md.getColumnType(i)))
{
  if (!md.isSigned(i))
    result.append("UNSIGNED ");
}

 また、データ型がNULL値を受け付けるかどうかも指定します。

if (md.isNullable(i) == 
  ResultSetMetaData.columnNoNulls)
  result.append("NOT NULL ");
else
  result.append("NULL ");
if (md.isAutoIncrement(i))
  result.append(" auto_increment");
}

 さらに、主キーを指定する必要があります。これはDatabaseMetaDataクラスを使って取得できます。主キーの列には、それを示す文字列を指定します。

DatabaseMetaData dbm = connection.getMetaData();
ResultSet primary = dbm.getPrimaryKeys(
  null, null, table);
boolean first = true;
while (primary.next())
{
  if (first)
  {
    first = false;
    result.append(',');
    result.append("PRIMARY KEY(");
  } else
      result.append(",");
      result.append(primary.getString
       ("COLUMN_NAME"));
} 

if (!first)
  result.append(')');

 終わりのかっこを付けてCREATE TABLE文は完成です。

  result.append(" ); ");
} 

 エラーが発生した場合は、DataMoverがDatabaseExceptionをスローします。

   catch (SQLException e)
   {
     throw (new DatabaseException(e));
   }

 最後に、generateCreateメソッドは完成したCREATE TABLE文を文字列として返します。

return result.toString();
著者注
 CREATE TABLE文を実行する前に、既存のコピー先データベースのテーブルを削除する必要があります。さもないと、generateCreateメソッドはエラーをスローします。Databaseクラスには、指定されたテーブル名に基づいてDROP TABLE文を生成するgenerateDropメソッドが含まれています。
 

データをコピーする

 コピー先データベースにテーブルを作成したら、そこにデータをコピーします。それには、SELECT文とINSERT文を作成する必要があります。SELECT文はコピー元データベースからテーブルのデータを読み取ります。INSERT文はコピー先データベースにデータを書き込みます。

 まず、SELECT文、INSERT文、およびINSERT文のVALUES句を格納するための3つのStringBufferのインスタンスを作成します。

StringBuffer selectSQL = new StringBuffer();
StringBuffer insertSQL = new StringBuffer();
StringBuffer values = new StringBuffer();

 DataMoverは列を取得し、それぞれの状態を表示します。

Collection<String> columns = 
  source.getColumns(table);

System.out.println("Begin copy: " + table);

 次に、SELECT文とINSERT文の先頭部分を作成します。

selectSQL.append("SELECT ");
insertSQL.append("INSERT INTO ");
insertSQL.append(table);
insertSQL.append("(");

 さらに、すべての列に対してループ処理を行い、SELECT文とINSERT文を作成します。最初の列名を除くすべての列名の後ろにコンマを挿入します。

boolean first = true;
for (String column : columns)
{

  if (!first)
  {
    selectSQL.append(",");
    insertSQL.append(",");
    values.append(",");
  } else
    first = false;

 各文に列名を追加し、VALUES句に疑問符(?)を追加します。疑問符が必要なのは、パラメータ化されたSQLを使用しているからです。この部分を後から実際の値に置き換えます。文をこのように作成すると、データベースでINSERT文をプリコンパイルして時間を節約することができます。

  selectSQL.append(column);
  insertSQL.append(column);
  values.append("?");
}

 次に、FROM句とVALUES句を追加します。

selectSQL.append(" FROM ");
selectSQL.append(table);

insertSQL.append(") VALUES (");
insertSQL.append(values);
insertSQL.append(")");

 ここまででSELECT文とINSERT文が完成し、SELECT文を実行してレコードのリストを取得できます。

// now copy
PreparedStatement statement = null;
ResultSet rs = null;

try
{
  statement = target.prepareStatement(
    insertSQL.toString());
  rs = source.executeQuery(selectSQL.toString());

 サンプルでは、状態レポート用に行数をカウントしています。

int rows = 0;

 すべてのレコードに対してループ処理を行い、INSERT文を実行します。次に進むごとに値を増分します。

while (rs.next())
{
  rows++;

 それぞれのINSERT文に、個々の列データをコピーします。

for (int i = 1; i <= columns.size(); i++)
{
  statement.setString(i, rs.getString(i));
}

 その後、INSERT文を実行します。

  statement.execute();
}

 最後に、状態情報を表示して終了します。

  System.out.println("Copied " + rows + " rows.");
  System.out.println("");
} 

 最初に述べたように、このサンプルはデータベース間でデータをコピーする簡単なユーティリティですが、私はこれを、より大きなデータベースユーティリティを作成するための土台としてよく利用しています。このユーティリティには、もっと複雑なデータベースコピーアプリケーションを作成するために必要な多くの関数が含まれていることがわかるでしょう。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Jeff Heaton(Jeff Heaton)

ライター、人工知能(AI)研究者、元大学教員。AI、仮想世界、スパイダー、ボットなどの話題を取り上げて執筆した書籍は10冊以上。Java、.Net、Silverlightを対象に、高度なニューラルネットワークおよびAIフレームワークの提供を目的とするオープンソースイニシアチブ、Encogプロジェクトを統括している。また、個人のWebサイトを管理し、人工知能とスパイダー/ボットプログラミングをはじめとする話題について情報発信を行っている。メールの宛先はjheaton@heatonresearch.com。Sun認定Javaプログラマ兼IEEEシニアメンバー。ミズーリ州セントルイスのワシントン大学情報管理修士号を持ち、セントルイス在住。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/239 2008/08/22 19:35

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング