« Back to Home

Blog

2012年を迎えて

2012-01-07 15:12:22 +0900

新年おめでとうございます。

2012年を迎えて、昨年の振り返りと今年の展望を軸にご挨拶を申し上げます。

http://www.youtube.com/watch?v=2qjKUVLoDDs

Tokyo CabinetとTokyo Tyrant

2011-12-11 20:31:52 +0900

Tokyo CabinetとTokyo Tyrantについて、社内勉強会のために作ったスライドをSlideShareに公開しました。(スライドはKeynote形式でダウンロード可能です)その解説と補足をしておきます。勉強会の目的は、Tokyo CabinetとTokyo Tyrantを大づかみに理解することと、実際にデータ操作をしてみることでした。

http://www.slideshare.net/akirakoyasu/tokyo-cabinet-tokyo-tyrant-10468880

まずはTokyo Cabinet/Tokyo Tyrantとは何なのか、端的に言えば、

Tokyo Cabinet
ファイルDB
Tokyo Tyrant
Tokyo Cabinetを内包したサーバ

ということになります。(スライド2-8)

Tokyo Cabinet

Tokyo Cabinet(以下TC)はいわゆるkey-valueストアで、4種類のデータ構造を持つことができます。(スライド10-17)

hash
拡張子.tchで示されます。1つのkeyに対して1つのvalueを保持する、単純なマップです。
b+ tree
拡張子.tcbで示されます。1つのkeyに対して複数のvalueを保持します。
fixed-length
拡張子.tcfで示されます。hash同様に1つのkeyに対して1つのvalueを保持しますが、「keyは自然数である」「valueは最大長をもつ」という制約があります。
table
拡張子.tctで示されます。1つのkeyに対して、名前付きの複数のvalueを保持します。

Tokyo CabinetのCLI

TCはデータ操作のためのCLIを備えています。データ構造によっていくらかの違いはありますが、例えばhashDBを操作する場合は以下のようにします。(スライド18-23)

# カレントディレクトリに"casket.tch"というファイル名でhashDBを作成する
tchmgr create casket.tch

# key"hoge"に紐づくvalue"fuga"を挿入する
tchmgr put casket.tch "hoge" "fuga"

# key"hoge"に紐づくvalueを参照する
tchmgr get casket.tch "hoge"

# hashDBのkeyを一覧する
tchmgr list casket.tch

# key"hoge"を削除する
tchmgr out casket.tch "hoge"

これらの他にもいくつかコマンドがありますし、多数のオプションもあります。詳細はmanページを参照してください。

Tokyo CabinetのAPI

TCの操作はネイティブライブラリとして提供され、また、他のプログラム言語から使用するためのAPIが提供されます。基本的に先ほどのCLI(正確にはネイティブライブラリ)に準ずるメソッド・オプションとなるよう作られています。スライドではJavaAPIの例を挙げています。(スライド25)

Tokyo Tyrant

Tokyo Tyrant(以下TT)はTCをデータストレージとして使用するDBサーバです。これは実際にサーバを起動してデータ操作をする手順を見ていただきましょう。(スライド28-36)

まずは1つ目のコンソールでTTを起動します。以下コンソール番号をプロンプトで示します。

# TTを起動する。(デフォルトポート1978)
# データストレージにはカレントディレクトリの"casket.tch"を作成/使用する。
console1:$ ttserver casket.tch

2つ目のコンソールでデータ操作を行いましょう。

# key"hoge"に紐づくvalue"fuga"を挿入する
console2:$ tcrmgr put localhost "hoge" "fuga"
# key"hoge2"に紐づくvalue"fuga2"を挿入する
console2:$ tcrmgr put localhost "hoge2" "fuga2"

# key"hoge"に紐づくvalueを参照する
console2:$ tcrmgr get localhost "hoge"

# hashDBのkeyを一覧する
console2:$ tcrmgr list localhost

# key"hoge"を削除する
console2:$ tcrmgr out localhost "hoge"

このデータ操作の部分は、先ほどのTCを直接使う場合とほとんど同じですね。さて今の状態で、DBにはkey"hoge2"が残っているはずです。コンソール1に戻って、TTを停止します。

^C

ストレージにしていたTCがどうなっているか確認してみましょう。TCを直接操作するので、tcrmgrではなくtchmgrを使用します。

console1:$ tchmgr list casket.tch

TC上でも、key"hoge2"が残っていることが確認できたはずです。

Tokyo TyrantのAPI

公式にはPerlとRuby用のAPIが提供されています。Java用のAPIとしては、tokyotyrant-javaが有名です。TCのAPIに似たAPIで操作が可能です。(スライド38)

レプリケーション

TTはレプリケーション機能を備えています。スライドでは大雑把にしか触れていませんが、レプリケーションについてはまとまった記述がなかなか見つからず苦労したので、ここはスライドから少し離れるところもありますが、詳しく解説しましょう。

仕組みとしてはMySQLとほぼ同じです。(マネして作った、と作者が書かれていました。)具体的には、

  1. スレーブサーバはレプリケーションのタイムスタンプを保持しており、それを自身のサーバIDと共にマスタサーバへ通知する。
  2. マスタサーバは、通知されたタイムスタンプより後、かつ通知されたサーバID以外の更新ログをスレーブへ送信する。
  3. スレーブサーバは更新ログを適用し、レプリケーションのタイムスタンプを更新する。

この動作を繰り返してレプリケーションが行われます。(スライド39-42)

初めからレプリケーションを開始する場合

データが無い初期状態からレプリケーションを行うのは、極めて簡単です。

  • マスタサーバではサーバID(-sid)・更新ログディレクトリ(-ulog)を、
  • スレーブサーバではサーバID(-sid)・マスタのホスト名(-mhost)・マスタのポート番号(-mport)・タイムスタンプファイルのパス(-rts)を、

それぞれ指定して起動するだけです。

途中からレプリケーションを開始する場合

問題はデータが既に存在する場合ですね。仮に、意識的な設定を何もしていないTTが既に稼働していて、突然レプリケーションをしようと思い立ったとしましょう。稼働中のTTをマスタにし、スレーブとなるTTを新たに追加することを考えます。(スライド43-46 ここをとても大雑把にしていますが)

1)マスタサーバを設定する

一番気になるのはTTを停止する必要があるのかどうか、というところです。TT起動時のオプションを確認し、不幸にしてサーバID(-sid)と更新ログディレクトリ(-ulog)とが指定されていなかった場合は、TTを一度停止する必要があります。そして例えば以下のようなオプションを追加して再起動します。以降、マスタ/スレーブでの操作をプロンプトで示します。

# 更新ログディレクトリを作成
master:$ mkdir /path/to/ulog
# TTを起動
master:$ ttserver -sid 1 -ulog /path/to/ulog /path/to/casket.tch

これでまずはマスタとなるTTが手に入りました。

2)DBファイルをコピーする

次はDBファイルのスナップショットを作成します。先ほど説明したように、レプリケーションの鍵になるのはタイムスタンプです。DBファイルのコピーをとると同時に、そのDBファイルのタイムスタンプを知る必要があります。

そのためにはコピーのためのシェルスクリプトをマスタサーバに用意し、TTの実行ユーザに実行権限を与えます。

master:$ cat ttcopy.sh
#!/bin/sh
SRCPATH="$1"
DSTPATH="$SRCPATH.$2"
cp -f "$SRCPATH" "$DSTPATH"

その上で以下のコマンドを発行すると、マスタサーバ上にDBファイルのコピーが作成されます。コピー中はTT全体がロックされることに注意してください。

someconsole:$ tcrmgr copy master.host '@/path/to/ttcopy.sh'
master:$ ls -l /path/to/dbdir/
-rw-r--r-- 1 ttserver ttserver 528704 12 11 18:40 casket.tch
-rw-r--r-- 1 ttserver ttserver 528704 12 11 18:40 casket.tch.1323596455089588

元のDBファイル名に数字が付いたファイルが出来ていますね。この数字がDBファイルのタイムスタンプです。

3)スレーブサーバを起動する

先ほどコピーしたDBファイルをスレーブサーバに移動させ、タイムスタンプファイルを作成します。

# DBファイルの末尾についたタイムスタンプを.rtsファイルに書く
slave:$ echo "1323596455089588" > slave.rts

# DBファイル名からタイムスタンプを除去する
slave:$ mv casket.tch.1323596455089588 casket.tch

そして例えば以下のようなオプションを付けて、スレーブとなるTTを起動します。

slave:$ ttserver -sid 2 -mhost master.host -mport 1978 
        -rts /path/to/slave.rts /path/to/casket.tch

これでレプリケーションが開始されました。

まとめ

今回はスライドを基にTokyo CabinetとTokyo Tyrantの基本と、まとまった情報の少ないTokyo Tyrantのレプリケーションについて解説・補足しました。他のkey-valueストアについても言えることですが、(あくまでRDBMSではないという意味で)限定された状況下では高いパフォーマンスを発揮します。使いどころ次第で便利に使えます。

ところで、昨年5月にTokyo Cabinetの後継となるKyoto Cabinetがリリースされています。Tokyo製品群についても継続してメンテナンスはしていくと表明されているので、まだしばらくは使っていても大丈夫でしょう。機会(と時間)があればKyoto製品群にも触れてみようと思っています。

SDKで身近になるAmazon Web Service

2011-12-04 16:00:07 +0900

このエントリはJava Advent Calendar 2011 第4日目のエントリです。
« 第3日目のエントリ第5日目のエントリ »

今日はAWSについて書こうと思います。というのは、最近ノーリスクとかマッシュアップとかいって、従量コストで利用できるWebモジュールの需要が(私の中で)高まってるからなのですが・・・。

AWSと言えばEC2やS3が有名ですが、他にもいろいろなサービスが提供されています。今日はその中でもシンプルで、単体でも役立ちそうなサービスをJava用のSDKから使う方法について紹介します。

  • Amazon Simple Storage Service (S3)
  • Amazon SimpleDB
  • Amazon Simple Email Service (SES)

この全てのサービス名に"Simple"と付いているのは偶然です。これを基準に選んだわけではありません。本当です。

なお記載のコードを実行するためには、AWSのアカウント登録と各サービスの有効化が必要です。また一応、従量課金サービスですので使い方によって料金が発生することがあります。

AWS SDK for Java

AWSはRESTやSOAPで制御することができますが、SDKを通せばREST/SOAPのリクエスト・レスポンスの処理はSDKに任せて、Javaから手軽にサービスを利用できます。

SDKを使用するには、AWSのページからダウンロードしてクラスパスに含めるか、あるいはmavenを使うならpom.xmlに以下のdependencyを追加します。

<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk</artifactId>
	<version>1.2.12</version>
</dependency>

認証

それぞれのサービスを利用するために、認証が必要になります。AWSのアカウント画面からアクセスキーIDとシークレットアクセスキーを確認してください。これは共通で使いますので、以下のように定数として定義しておきます。

public static final String AWS_ACCESSKEY_ID = "1234567890ABCDEFGHIJ";
public static final String AWS_SECRET_ACCESSKEY
    = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn";

Amazon Simple Storage Service (S3)

まずはご存知S3から使ってみることにします。S3は、キーに紐づくオブジェクトを格納できるストレージで、かなり大きなオブジェクトも格納できるのが特徴です。今は5テラバイトまで格納できるようです。

Amazon S3

例としてXMLファイルを格納し、それをまた取り出してみましょう。バケット名はS3のサービス全体で一意でなければならないことに注意してください。

// 認証オブジェクト
AWSCredentials credential
	= new BasicAWSCredentials(AWS_ACCESSKEY_ID, AWS_SECRET_ACCESSKEY);

// S3クライアントを作成
AmazonS3Client client = new AmazonS3Client(credential);

// S3でユニークなバケット名
String bucketName = "xxxxxxxx";
// バケットを作成
client.createBucket(bucketName);

// 格納
client.putObject(bucketName, "indian teas", new File("/path/to/indianTeas.xml"));

// 取り出し
S3Object s3Object = client.getObject(bucketName, "indian teas");

// 標準出力へ出力
InputStream is = s3Object.getObjectContent();
ReadableByteChannel from = Channels.newChannel(is);
WritableByteChannel to = Channels.newChannel(System.out);
try {
	ByteBuffer buf = ByteBuffer.allocate(0x1000);
	while (from.read(buf) != -1) {
		buf.flip();
		while (buf.hasRemaining()) {
			to.write(buf);
		}
		buf.clear();
	}
} catch (IOException e) {
	e.printStackTrace();
}

// 削除
client.deleteObject(bucketName, "indian teas");

// バケットを削除
client.deleteBucket(bucketName);

client.shutdown();

このコードを実行すると、わらわらとAmazonHttpClientのログが出力された後、取り出したXMLの内容が出力されます。

Amazon SimpleDB

次はSimpleDBに挑戦してみましょう。SimpleDBは、複数の属性(カラムに当たります)を持つデータセット(レコード)を格納できるデータベースです。スキーマ定義は不要で、各データセットの属性を自由に設定できます。

Amazon SimpleDB

以下のコードは簡単なデータ操作を行うものです。

// 認証オブジェクト
AWSCredentials credential
	= new BasicAWSCredentials(AWS_ACCESSKEY_ID, AWS_SECRET_ACCESSKEY);

// SimpleDBクライアントを作成
AmazonSimpleDBClient client = new AmazonSimpleDBClient(credential);

// DBドメイン名
String domain = "testDomain";
// ドメインの作成
client.createDomain(new CreateDomainRequest(domain));

// 属性の異なるデータセットを挿入
client.putAttributes(new PutAttributesRequest()
	.withDomainName(domain)
	.withItemName("Darjeeling")
	.withAttributes(
		new ReplaceableAttribute("country", "India", Boolean.FALSE),
		new ReplaceableAttribute("area", "north-east", Boolean.FALSE)));

client.putAttributes(new PutAttributesRequest()
	.withDomainName(domain)
	.withItemName("Uva")
	.withAttributes(
		new ReplaceableAttribute("country", "Sri Lanka", Boolean.FALSE),
		new ReplaceableAttribute("island", "Ceylon", Boolean.FALSE),
		new ReplaceableAttribute("area", "south-east", Boolean.FALSE)));

// 参照(非SQL)
SelectResult result = client.select(new SelectRequest("select * from testDomain"));

// データセットを表示
List<Item> items = result.getItems();
for (Item item : items) {
	System.out.printf("%s:", item.getName());
	for (Attribute attr : item.getAttributes()) {
		System.out.printf(" %s=%s,", attr.getName(), attr.getValue());
	}
	System.out.println();
}

// DBドメインを削除
client.deleteDomain(new DeleteDomainRequest(domain));

client.shutdown();

参照するときの非SQLというのが気になりますね。これはSQLを簡略化したような構文で参照条件を記述するものなのですが、詳しくはAWSのドキュメントをご覧ください。"Amazon SimpleDB Quick Reference Card"で検索すればチートシートが見つかると思います。

さて上記のコードを実行すると、運が良ければ以下の出力が得られます。(ログは除いています)

Darjeeling: area=north-east, country=India,
Uva: area=south-east, country=Sri Lanka, island=Ceylon,

たまに入れたものが全部出てこないこともあります。これはconsistency、いわゆる読み取り一貫性がオプション扱いになっているからです。SelectRequestオブジェクトのconsistentReadをtrueにすれば、参照前に挿入した全てのデータセットを参照できるようになります。

Amazon Simple Email Service (SES)

SESを使うと簡単にメールを送信できます。これには1つ準備が必要になります。AWS管理コンソールのSES画面から、送信者のメールアドレス認証を行っておいてください。同じ画面にProduction Accessの申し込みボタンがありますが、テストだけならそちらの方は必要ありません。

認証を済ませたら、早速送信してみましょう。送信者と受信者には認証したメールアドレスを設定します。

// 認証オブジェクト
AWSCredentials credential
	= new BasicAWSCredentials(AWS_ACCESSKEY_ID, AWS_SECRET_ACCESSKEY);

// SESクライアントを作成
AmazonSimpleEmailServiceClient client
	= new AmazonSimpleEmailServiceClient(credential);

// 管理コンソールで認証済みのアドレス
String verifiedAddress = "mail@example.com";

// メール送信
client.sendEmail(new SendEmailRequest()
	.withSource(verifiedAddress)
	.withDestination(new Destination()
		.withToAddresses(verifiedAddress))
	.withMessage(new Message()
		.withSubject(new Content("AWS Simple Email test"))
		.withBody(new Body()
			.withText(new Content("AWS SESのテストです。"))
			.withHtml(new Content("<h1>AWS SES</h1><p>テストです。</p>")))));

client.shutdown();

これでしばらく待つと、メールが届くはずです。

今から使えるAWS

AWSにはTomcat互換のアプリケーションコンテナ、Elastic Beanstalkというサービスもあります。そこから今回のS3・SimpleDB・SESを利用すれば、簡単にいろいろなWebサービスを構築できそうですね。

余談ですが、AWSで頻繁に使われる3文字略語はどうも苦手です。全然覚えられません・・・。

lucene-gosenで形態素解析

2011-11-27 00:54:03 +0900

形態素解析とは、文を最小の意味を持つ単位(形態素)に分解して、それぞれの形態素の品詞等を判別することを言います。今日はJavaで形態素解析をしてみましょう。

Javaから使える形態素解析ライブラリの一つにlucene-gosenがあります。特徴として、多くの形態素解析ライブラリがJNIを使用するのに対し、lucene-gosenはJNIを使用せず、Javaで完結していることが挙げられます。JNIを使用したくない/使用できない環境、例えばGoogle App Engine(GAE/J)などでも使うことができます。

準備

準備はごく簡単です。プロジェクトのWebページから、辞書付きのjar(naist-chasenかipadic、どちらでも構いません)をダウンロードしてクラスパスに含めるだけです。

参考までに、現行バージョンは1.2.0でした。

解析

早速使ってみましょう。コードは以下のようになります。

StringTagger tagger = SenFactory.getStringTagger(null);
try {
	List<Token> list = new ArrayList<Token>();
	list = tagger.analyze("すもももももももものうち", list);

	for (Token token : list) {
		System.out.println("======================================");
		System.out.printf("surface: %s%n", token.getSurface());
		System.out.printf("start: %s, length: %s%n", token.getStart(), token.getLength());
		System.out.printf("cost: %s%n", token.getCost());
		Morpheme morpheme = token.getMorpheme();
		System.out.printf("basicForm: %s%n", morpheme.getBasicForm());
		System.out.printf("cForm: %s, cType: %s%n",
				morpheme.getConjugationalForm(), morpheme.getConjugationalType());
		System.out.printf("partOfSpeech: %s%n", morpheme.getPartOfSpeech());
		System.out.printf("pron: %s, read: %s%n",
				morpheme.getPronunciations(), morpheme.getReadings());
		System.out.printf("additionalInfo: %s%n", morpheme.getAdditionalInformation());
	}
} catch (IOException e) {
	e.printStackTrace();
}

これを実行すると以下の出力が得られます。

======================================
surface: すもも
start: 0, length: 3
cost: 4636
basicForm: *
cForm: *, cType: *
partOfSpeech: 名詞-一般
pron: [スモモ], read: [スモモ]
additionalInfo: null
======================================
surface: も
start: 3, length: 1
cost: 6038
basicForm: *
cForm: *, cType: *
partOfSpeech: 助詞-係助詞
pron: [モ], read: [モ]
additionalInfo: null
======================================
surface: もも
start: 4, length: 2
cost: 10311
basicForm: *
cForm: *, cType: *
partOfSpeech: 名詞-一般
pron: [モモ], read: [モモ]
additionalInfo: null
======================================
surface: も
start: 6, length: 1
cost: 11713
basicForm: *
cForm: *, cType: *
partOfSpeech: 助詞-係助詞
pron: [モ], read: [モ]
additionalInfo: null
======================================
surface: もも
start: 7, length: 2
cost: 15986
basicForm: *
cForm: *, cType: *
partOfSpeech: 名詞-一般
pron: [モモ], read: [モモ]
additionalInfo: null
======================================
surface: の
start: 9, length: 1
cost: 16655
basicForm: *
cForm: *, cType: *
partOfSpeech: 助詞-連体化
pron: [ノ], read: [ノ]
additionalInfo: null
======================================
surface: うち
start: 10, length: 2
cost: 18621
basicForm: *
cForm: *, cType: *
partOfSpeech: 名詞-非自立-副詞可能
pron: [ウチ], read: [ウチ]
additionalInfo: null

おお、うまく解析できましたね!

JAXBの簡単な解説(2)

2011-11-20 05:27:34 +0900

前回はまず使ってみるということで、単純なJavaオブジェクトからXMLへ変換する方法を解説しました。

今回は、XMLからJavaオブジェクトへ変換する方法と、また一歩進んでバインディングを制御するアノテーションについて解説します。

XMLからJavaオブジェクトへの変換

データ構造としては前回と同じものを使いましょう。

会社(従業員*)
従業員(名前,給料)

これらを表すJavaクラスも前回と基本は同じなのですが、オブジェクトの中身を見たいのでtoString()を追加しています。

Campanyクラス

public class Company {
	private List<Employee> employees;

	public List<Employee> getEmployees() {
		return employees;
	}

	public void setEmployees(List<Employee> employees) {
		this.employees = employees;
	}

	@Override
	public String toString() {
		String str = "Company [n";
		if (employees != null) {
			for (Employee e : employees) {
				str += " " + e + "n";
			}
		}
		str += "]";
		return str;
	}
}

Employeeクラス

public class Employee {
	private String name;
	private int salary;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getSalary() {
		return salary;
	}

	public void setSalary(int salary) {
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "Employee [name=" + name + ", salary=" + salary + "]";
	}
}

では、XMLをこのクラスのオブジェクトに変換するプログラムがどうなるか見てみましょう。実際的にはXMLをファイルから読み込むなどすると思いますが、この例ではその場で文字列を作っています。

public class Sample2 {

	public static void main(String[] args) {
		String xml = "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"
				+ "<company>"
				+ "    <employees>"
				+ "        <name>taro</name>"
				+ "        <salary>1000</salary>"
				+ "    </employees>"
				+ "    <employees>"
				+ "        <name>hanako</name>"
				+ "        <salary>800</salary>"
				+ "    </employees>"
				+ "</company>";

		// アンマーシャル
		Company c = JAXB.unmarshal(new StringReader(xml), Company.class);

		System.out.println(c);
	}
}

これも前回同様ごく簡単ですね。JAXB#unmarshall()を呼ぶだけです。

実行すると以下の出力が得られます。

Company [
 Employee [name=taro, salary=1000]
 Employee [name=hanako, salary=800]
]

これで無事にXMLからJavaオブジェクトへ変換できました。

バインディングを制御する

さてこのように特に設定をしなくても、自動的にJavaオブジェクトとXMLとが(正確にはJavaクラスとXML Schemaとが)対応付けられて相互に変換できるわけですが、このままでは都合が悪いこともあります。

前回も触れたように、XML上、従業員の要素名が「employees」になっています。これはなぜかと言うとCompanyクラスのプロパティ名が「employees」だからです。もちろんプロパティ名を「employee」に変えれば要素名も変わるのですが、いつも都合良くプログラムを変えたり、XMLを変えたりできるとは限りません。

そこで登場するのがアノテーションです。Companyクラスを以下のようにしてみましょう。

public class Company {
	private List<Employee> employees;

	@XmlElementWrapper
	@XmlElement(name="employee")
	public List<Employee> getEmployees() {
		return employees;
	}

	public void setEmployees(List<Employee> employees) {
		this.employees = employees;
	}

	@Override
	public String toString() {
		String str = "Company [n";
		if (employees != null) {
			for (Employee e : employees) {
				str += " " + e + "n";
			}
		}
		str += "]";
		return str;
	}
}

名前からだいたい想像がつくかもしれませんね。このクラスを使ってJavaオブジェクトからXMLへの変換(前回のSample1)を行ってみると、以下のような出力が得られます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <employees>
        <employee>
            <name>taro</name>
            <salary>1000</salary>
        </employee>
        <employee>
            <name>hanako</name>
            <salary>800</salary>
        </employee>
    </employees>
</company>

つまりそれぞれのアノテーションは以下のような働きをします。

@XmlElementWrapper
(主にコレクション要素の)外側をラップするXML要素を生成します。
@XmlElement
JavaBeanプロパティをXML要素としてマークします。nameプロパティでXML要素名を指定できます。

Employeeクラスの方も少し変えてみましょう。せっかくXMLなのですから属性を使いたいときもあるでしょう。

public class Employee {
	private String name;
	private int salary;

	@XmlAttribute
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@XmlAttribute
	public int getSalary() {
		return salary;
	}

	public void setSalary(int salary) {
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "Employee [name=" + name + ", salary=" + salary + "]";
	}
}

これで再び変換を実行すると、出力は以下のように変わります。

<company>
    <employees>
        <employee salary="1000" name="taro"/>
        <employee salary="800" name="hanako"/>
    </employees>
</company>
@XmlAttribute
JavaBeanプロパティをXML属性としてマークします。

次回へ

ここまでで、JavaオブジェクトとXMLとの間をかなり自由に行き来できるようになりました。

次回は先延ばしにしていたXML Schemaが登場する予定です。JAXBのコマンド(CLI)を使ってJavaクラスとXML Schemaの変換を行います。