シーケンスはロールバックされません

知らなかった><

S2JDBC使っていて、ロールバックしてもシーケンスがインクリメントされたままになってるなぁと思って調べてた。
一応調べたことを書きます。

DB

PostgreSQL 8.1.11

company
CREATE SEQUENCE company_id_seq;
CREATE TABLE company (
  company_id INTEGER DEFAULT nextval('company_id_seq'),
  company_name VARCHAR(255) NOT NULL,
  PRIMARY KEY(company_id)
);

確認方法

テストクラスで、①.ロールバックさせる。②.トランザクション管理しないでinsertする。メソッドををそれぞれ作って実行した。

public void testTransactionFalseTx() throws Exception {
	// companyName が not null のため 制約違反
	Company entity = new Company();
	jdbcManager.insert(entity).execute();
}

public void testTransactionTrue() throws Exception {
	Company entity = new Company();
	entity.companyName = "A";
	jdbcManager.insert(entity).execute();
}

結果

company_id company_name
2 A

ロールバックしてるのにシーケンスがインクリメントされてる><

ログ確認

2009-01-31 19:54:23,933 [main] DEBUG org.seasar.extension.jta.TransactionImpl - トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1233399263933/0, BranchId=]
2009-01-31 19:54:23,978 [main] DEBUG org.seasar.extension.jdbc.query.AutoInsertImpl - select nextval('COMPANY_ID_SEQ')
2009-01-31 19:54:24,046 [main] DEBUG org.seasar.extension.dbcp.impl.ConnectionPoolImpl - 物理的なコネクションを取得しました
2009-01-31 19:54:24,048 [main] DEBUG org.seasar.extension.dbcp.impl.ConnectionPoolImpl - 論理的なコネクションを取得しました。tx=[FormatId=4360, GlobalId=1233399263933/0, BranchId=]
2009-01-31 19:54:24,080 [main] DEBUG org.seasar.extension.jdbc.query.AutoInsertImpl - insert into COMPANY (COMPANY_ID, COMPANY_NAME) values (1, null)
2009-01-31 19:54:24,090 [main] DEBUG org.seasar.extension.jta.TransactionImpl - トランザクションロールバックしました。tx=[FormatId=4360, GlobalId=1233399263933/0, BranchId=]
2009-01-31 19:54:24,090 [main] DEBUG org.seasar.extension.dbcp.impl.ConnectionWrapperImpl - 論理的なコネクションを閉じました。tx=[FormatId=4360, GlobalId=1233399263933/0, BranchId=]
2009-01-31 19:54:24,091 [main] DEBUG org.seasar.extension.dbcp.impl.ConnectionWrapperImpl - 物理的なコネクションを閉じました

ちゃんとロールバックしてる><

もしかして…

同じシーケンスから数を取得するといった同時トランザクションの障害を 避けるために、nextval 演算はロールバックされることがありません。 つまり一度値が取りだされると、もしその nextval を行なったトランザクション が後でアボートしても、その値は使用済であると仮定されます。これは アボートされたトランザクションは未使用の"穴"を割り振られた値のシーケンス に残す可能性があります。setval 演算もロールバックされることはありません。

http://www.sraoss.co.jp/PostgreSQL/Manual/PostgreSQL-7.1-ja/sql-createsequence.html

シーケンスはロールバックされないのが正解でした。
危なく、Seasarメーリングリストに投稿するところだった。

主キー(id)の自動生成について

自動生成の方法

今回はPostgerSQLを使用しているため、シーケンスを使用して自動生成するようにしました。

例えば会社マスタの場合

table
create SEQUENCE company_id_seq;

create TABLE {
    company_id INTEGER DEFAULT nextval('company_id_seq') PRIMARY KEY,
    ...
}
entity
public class company {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COMPANY_ID_SEQ")
    @SequenceGenerator(name = "COMPANY_ID_SEQ", sequenceName = "COMPANY_ID_SEQ")
    public Integer company_id;

}

これで、idは自動生成されて登録されます。

問題点

S2JDBCのinsertBatchやupdateBatchではパラメータにエンティティのListや配列を渡すことで複数登録、追加処理できます。


この複数登録、追加のときにPostgreSQL内部のシーケンスがインクリメントされずに、insertBatchやupdateBatchを二回目移行実行すると、キー重複でエラーになってしまいました。


調べてみると、create SEQUENCE [sequence_name]でシーケンスを作ったときにインクリメントの増分が1何だけど、@SequenceGeneratorのallocationSizeがデフォルトで50のために、シーケンスがインクリメントされないみたい。

解決方法

S2JDBCのサイトにはcreate sequence [sequence_name] increment by 50 にするようにかいてあったけど、@SequenceGeneratorのallocationSizeを1にして対応した。

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COMPANY_ID_SEQ")
@SequenceGenerator(name = "COMPANY_ID_SEQ", sequenceName = "COMPANY_ID_SEQ", allocationSize = "1")
public Integer company_id;

ValidateのActionMessageを指定したキーで登録する

ActionMessages

SAStrutsではActionクラスでオリジナルの検証をメソッドにして@Executeのvalidate属性にしていします。検証がNGだった場合はActionMessagesにメッセージを詰め込んで、ActionMessagesExceptionをスローします。スローされた、ActonMessagesExceptionはActionMessagesThrowsInterceptorにより、リクエストに登録されます。


そのときの、キーは

String org.apache.struts.Globals.ERROR_KEY = "org.apache.struts.action.ERROR"

になります。


また、メッセージは

String org.apache.struts.action.ActionMessages.GLOBAL_MESSAGE = "org.apache.struts.action.GLOBAL_MESSAGE"

をキーに登録されます。

メッセージの登録キーを指定したい

メッセージの登録キーを指定することで、Validator(アノテーション)で検証した際のメッセージとActionクラスからスローした場合のメッセージを同じキー*1でとれたら、良いんじゃというアドバイスがあったので、ActionMessagesExceptionをちょっと改良してコンストラクタでメッセージのキーを指定できるようにして使うことにしました。*2

jspからは

<html:messages id="error" message="false"
               property="name">
               <span class="error_font">${error}</span>
</html:messages>

のような形で表示できます。((ErrorsタグよりもMessagesタグのほうがデザインの自由度があがる))

*1:html:textタグなどのproperty

*2:Interceptorも改良版用のものを作成

@maxbytelengthで勉強になったこと

@maxbytelength

SAStrutsの検証用のアノテーションのひとつに@maxbytelengthという、バイト単位で最大文字列の検証をしてくれるアノテーションがあります。


使い方は、maxbytelength属性にバイト数を指定して検証します。

@maxbytelength(maxbytelength = 1000)
public string contents;

はまったこと

maxbytelength属性に指定したバイト数*1で検証がうまくできない。

原因・対策

結論から言うと、charset属性にsjisを指定すれば期待通りに検証してくれる。


maxbytelengthでは"".getByte()でバイトの長さを取得して検証をしています。charsetを指定すると"".getByte(charset)という形でバイトに変換します。


デフォルトで"".getByte()は半角1Byte、全角2Byteの長さ*2になると思ってました。


String#getBytes()ではまる。 - うなの日記のコメントに書いてあったのですが、システムプロパティの"file.encoding"からデフォルトの文字セットを取得するみたいです。


今回はファイルのエンコーディングUTF-8にしていたので、"".getByte("UTF-8")という形でバイトに変換されていたため期待通りに検証されなかったみたいです。


試しに"".getByte()を"sjis"、"UTF-8"、"Unicode"で実行してみました。

"あああああ".getBytes("UTF-8");
"あああああ".getBytes("Unicode");
"あああああ".getBytes("sjis");


結果
UTF-8では漢字や仮名の表現に3Byte必要なため15Byte
Unicodeは2Byteで表現するが、先頭にBOMが付くため12Byte
sjisは2Byteのため10Byte

[ 0xE3 0x81 0x82 0xE3 0x81 0x82 0xE3 0x81 0x82 0xE3 0x81 0x82 0xE3 0x81 0x82 ]
15
[ 0xFE 0xFF 0x30 0x42 0x30 0x42 0x30 0x42 0x30 0x42 0x30 0x42 ]
12
[ 0x82 0xA0 0x82 0xA0 0x82 0xA0 0x82 0xA0 0x82 0xA0 ]
10

調べていて知ったこと

Unicodeは、16ビットで文字を表現します。16ビットということは、216 = 65536種類の文字を表現できます。実際に何文字が定義されているのかは、Unicodeのバージョンによって異なりますが、Javaで採用されている Unicode 2.1では、約4万字程度の文字が割り当てられています※1。

文字コードに関する議論には、いろいろと奥深いものがありますが、Javaの初心者プログラマーとしては、とりあえず以下のポイントくらいを理解しておけばよいと思います。

Unicode文字は1文字が16ビット
●最初の128文字はASCIIコードと一致
●コンピュータで通常使っている日本語文字が含まれている

ASCIIコードでは、1文字を7ビットで表現し、アルファベットや数字、記号、制御文字などを定義していますが、それらは、Unicodeの最初の 128文字と一致しています。どの文字が含まれているのかだけでなく、その順番も同じなので、7ビットのASCIIコードに、上位ビットとして0を9個追加して16ビットにすれば、Unicodeでの表現と同じになります。

例えば、「A」という文字は、ASCIIコードでは7ビットで「100 0001」と表しますが、この上位に0を9個追加して、「0000 0000 0100 0001」とすれば、Unicodeの「A」になります。

itarchitect.jp


最初の128文字は、ASCIIコードと同じということで、たまにASCIIコードと文字を比較したりするサンプルがあるのはそのためなんですね。

*1:このときの想定では半角1Byte、全角2Byte

*2:つまりsjisでバイトに変換

今日買った本

読むだけじゃなく、読んで参考になったことを人に話したり思い出したりすることが大事。


応用情報技術者試験合格する。

気になるテレビ

タイノッチ

TBS 「タイノッチ」


キャストがいい。

国分太一
イノッチ
千原ジュニア
平岩紙


紙ちゃんは何かのドラマで見たときあるな、と思ってたらhttp://www.tbs.co.jp/tokkyuu3/に出てましたね。


何か気になる。

ザ・スリーシアター

ザ・スリーシアター(特番) - フジテレビ

ウッチャンの番組は全部面白いですね。


個人的には、ロッチが面白いと思う。

お試かっ

tv asahi|テレビ朝日

居酒屋でメニューのTOP10を当てる企画は毎回面白い。

特に、ブラマヨ

RIPとOSPF

ルーティングプロトコル(RIPとOSPF)のメモ。

RIP(Routing Information Protocol)

RIPは、IP用のルーティング・プロトコルとして、非常に古くから使用されています。このプロトコルは、UDPのブロードキャスト・データ・パケットを用いて、経路情報を隣接ルータにアナウンスします。この中には、「メトリック」と呼ばれるあて先ネットワークまでのディスタンスを表す情報(ルータのホップ数)が含まれており、ルータを超えるごとに1つずつ加算されます。RIPは、このメトリックを利用してネットワーク・トポロジを把握するため、「ディスタンス・ベクタ・アルゴリズム」に基づいたルーティング・プロトコルと呼ばれています。

  RIPでは、メトリックがより少ない経路情報が最適経路として使用されます。最大メトリックは15となっており、これを超えた場合は到達不能と見なされます。RIPでは、これらのすべての経路情報を30秒周期で隣接ルータにアナウンスすることにより、情報の更新を行います。

ルーティング・プロトコルの役割を理解する:IPルーティング入門(1) - @IT

OSPF(Open Shortest Path First)

OSPFは、IETFのOSPFワーキング・グループで開発されたIP専用のプロトコルです。OSPFでは、各ルータが「リンクステート」と呼ばれる情報要素を作成し、IPマルチキャストを用いてほかの全OSPFルータに配信します。これを受信したルータは、このリンクステート情報に基づき、ほかのルータがどこに存在し、どのように接続されているのかというLSDBを作成し、ネットワーク・トポロジを把握します。このため、OSPFは「リンクステート・アルゴリズム」に基づいたルーティング・プロトコルと呼ばれています。

  OSPFでは、コスト値(主にインターフェイス帯域幅により決定)の低い経路情報が最適経路として使用されます。また、一度リンクステート情報が交換されると、この情報に更新がない場合は、基本的にはHelloパケットによる生存確認のみを行います。そして、更新があった場合には、その差分情報だけを交換します。

ルーティング・プロトコルの役割を理解する:IPルーティング入門(1) - @IT

RIPとOSPFをサポートしているルータ


CentreCOM AR415Sに無線のアクセスポインタとして無線LAN|CentreCOM WR540APSがよさそう。


Ciscoルータの勉強にLoading...がおすすめ。