Challenge Engineer Life !

エンジニア人生を楽しみたい!仕事や趣味で学んだ技術的なことを書いていくブログです。

EclipseLinkでレベル2キャッシュの削除

今日は細かいテストをしていて「あれ?」という挙動があり、詳しく追っていった所、EclipseLinkのキャッシュ関連でした。そしてずっとハマっていました(-_-;

本当はスモールな再現コードを作ってのせようと思ったのですが、時間が足りず作れていないので、とりあえずメモだけ。

金魚本にもありますが、JPAには

  • レベル1キャッシュ
  • レベル2キャッシュ

とあるとのことです。

恥ずかしながら、この辺りをあまり細かく意識できていなかったのですが…今日ハマった挙動がイマイチ理解できなかったため、EclipseLinkのCache解説のページをみながら「ちゃんと理解せんと…」と少しゲンナリしてます(-_-;
http://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching

今日ハマった現象は、上記の中でevictAll()を呼んだり、ヒントクエリ(という呼称でいいのかな…?)を付与してみたら解消したので、不具合時の内部イメージと、なぜこれらを呼んで解決するのか、を理解していきたいと思います。

Cache cache = getEntityManager().getEntityManagerFactory().getCache();
if(cache != null){
    cache.evictAll();
}
Query qry = em.createQuery(query);
qry.setHint("javax.persistence.cache.storeMode", "REFRESH");

がんばらねば。。

EclipseLinkで一意制約の例外を拾ってみたけど…

JPAの実装としてEclipseLink(2.3.2)を利用しているのですが、とある画面の仕様で、単純な保存失敗だけではなく、一意制約で保存失敗したよ、というのまで拾う必要があって、まぁ、それはそんなことは簡単じゃろいと思ったのですが…これがなかなか…(-_-;

調べると同じことトライしてる人が結構いた…(^^;

一番上のが自分とほぼ同じ悩みで、INSERT処理はEJBを経由しているため、まずEJBExceptionになっていて…例外を辿っていくと

  • org.eclipse.persistence.exceptions.DatabaseException
  • com.microsoft.sqlserver.jdbc.SQLServerException

となって「制約 'HogeHoge_PKC' の PRIMARY KEY 違反。オブジェクト 'dbo.Hoge' には重複したキーを挿入できません。」と。

Stackoverflowの情報みると、本来JPAの仕様ではEntityExistExceptionを返すようですが、EclipseLinkで(バグなのかよくわからないですが)拾えなかったです…なんか自分ので変なとこあるのかなぁ。ただ、Stackoverflowでも同じようなこと言ってるので、やはりそうなのかなと。

ふむー。そうなると選択肢は

  • INSERT前に重複するキーがあるかチェックする

これは厳密なチェックではないのでやりたくない

  • getCause()で辿っていってSQLServerExceptionのメッセージで判断

こういうことやってる情報をみつけたけど、これは微妙…(-_-;メッセージで判断って…

あとDBベンダ固有の例外を取得するのはあまりなぁ、みたいな情報もチラホラあって、まあ確かに…と思ったり。

で、色々調べましたが、結局最終的には、getCause()で辿って、やはりSQLServerExceptionDatabaseExceptionをみて

DatabaseException dex = (DatabaseException) ex.getCause().getCause();
dex.getDatabaseErrorCode(); //2627がとれる
dex.getErrorCode(); //4002がとれる

みたいにコードで判定しようかなと。

コード細かく調べたいと思ってMSDNみたけど…

http://msdn.microsoft.com/ja-jp/library/ms378831(v=sql.105).aspx

どこに情報あるんすか…。

2013/4/25 追記
今日見直したらDatabaseExceptionはEclipseLinkの例外でしたので取り消し線で修正しましたm(_ _)m
あとgetDatabaseErrorCode()で取得できる「2627」のDBエラーコードはここにありました。
http://msdn.microsoft.com/ja-jp/library/ms152467(v=sql.105).aspx

getErrorCodeはEclipseLinkでSQLExceptionを示す意味のようです。
http://www.eclipse.org/eclipselink/api/1.0/constant-values.html#org.eclipse.persistence.exceptions.DatabaseException.SQL_EXCEPTION

明日この辺りのコードをもう少し詳しく調べてみて、これを使おうかなと思います。
今まで何とかDBの差異を意識せずに(Native Queryとか使わず)作ってきましたが、ここは諦めました。
てか、EclipseLinkでちゃんとEntityExistsExceptionが取れて欲しい。。。

JPQLで自己結合

開発の中で、自己結合(Self Join)したいケースがあって、JPQLでどう書くのかやってみました。
今回、あまり調べず試行錯誤してしまったので、あっているかあまり自信ない…(-_-;

とりあえず簡単なサンプルを作って確認しました。

テーブルは以下のような定義で、ロール(役割)階層を表現しています。

CREATE TABLE ROLE_TBL(
    ROLEID VARCHAR(32) PRIMARY KEY,
    ROLENAME VARCHAR(64),
    UPPERROLE VARCHAR(32),
    CONSTRAINT FK_ROLE FOREIGN KEY(UPPERROLE) REFERENCES ROLE_TBL(ROLEID)
)

イメージは単純に以下のような感じで、ロールに親子関係があるだけ。
親1に対して子供は多。

f:id:kikutaro777:20130422230341j:plain

データは

f:id:kikutaro777:20130422230354j:plain

と絵に合わせて積んでみます。

NetBeansでデータベースからエンティティを自動生成すると
(※今回のサンプルではApache Derby使いました)

@Entity
@Table(name = "ROLE_TBL")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "RoleTbl.findAll", query = "SELECT r FROM RoleTbl r"),
    @NamedQuery(name = "RoleTbl.findByRoleid", query = "SELECT r FROM RoleTbl r WHERE r.roleid = :roleid"),
    @NamedQuery(name = "RoleTbl.findByRolename", query = "SELECT r FROM RoleTbl r WHERE r.rolename = :rolename")})
public class RoleTbl implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 32)
    @Column(name = "ROLEID")
    private String roleid;
    @Size(max = 64)
    @Column(name = "ROLENAME")
    private String rolename;
    @OneToMany(mappedBy = "upperrole")
    private Collection<RoleTbl> roleTblCollection;
    @JoinColumn(name = "UPPERROLE", referencedColumnName = "ROLEID")
    @ManyToOne
    private RoleTbl upperrole;
//以下省略

こんな感じで生成されていました。RoleTblのコレクションと単一のRoleTbl持つんだ…。うーむ。

とりあえず、Entityはそのままにして、クエリで、自分+親、のEntityを一緒に取ることをやってみました。

JPQLクエリは

SELECT r,ru FROM RoleTbl r LEFT OUTER JOIN r.upperrole AS ru

って書いて、NetBeansのJPQLツールで実行すると

f:id:kikutaro777:20130422230725j:plain

取れた(^^;あってんのか。
ちなみに外部結合にしているのは、親のいないCEOも含めて取りたいからです。

最初、WHERE句で「r.upperrole.roleId = ru.roleId」とか色々書いては実行エラーで怒られ続けたけど、rとruは既に関係を持ってるので不要…?と思ったら当たりっぽい。

JPQL、やはり細かい所になってくると難しいです(^^;
JSR317の仕様を調べつつ、トライして、って感じで。

ちょっと自信ないのでググってみよう;

DataTableにEntityのリストを直接紐づけた際の表示順制御

だいぶしょうもないですが、少しだけ迷いが発生したのでメモ(^^;

PrimeFacesで一覧を作る際、DataTableのvalue属性にリストをEL式で紐づけます。
通常、検索系画面などではJPQLでDBから引く際にORDER BYするので、初期の表示順は特に問題になりません。

ただ検索系以外の画面で、非常に単純な一覧(1つのエンティティで表示できてしまうような)の場合には、とある親Entityの子供コレクションを直接紐づけることなんかもあると思います。

具体的には、ParentEntityクラスのフィールドに以下のような定義がある場合

@OneToMany(cascade = CascadeType.ALL, mappedBy = "Parent")
private List<Child> children;

そして、datatableで以下のように定義をするケースです。

<p:dataTable id="dtXxx" var="item" value="#{xxxBean.parent.children}" ...

この場合に、一覧表示での初期表示順を定義する必要が出た場合、EntityクラスにComparableインタフェースを付けてcompareToを定義するのか…?と一瞬思ったのですが、金魚本にある@OrderByを利用して

@OneToMany(cascade = CascadeType.ALL, mappedBy = "Parent")
@OrderBy("childId ASC")
private List<Child> children;

のようにかけば問題ありませんでした。上記例ではChildのEntityが属性としてchildIdを持っていて、その昇順、という意味になります。

危うくEntityに余計な実装するところだった(^^;

JPA2.0のJPQLではON句が使えない

知らなかった…JPA2.0ではON句が使えないのですね…ということでメモ。
JSR317のドキュメントで外部結合のところをみると例えばLEFT JOINでは以下の例があります。
※OUTERはオプションなので記述なくても同じ

SELECT c FROM Customer c LEFT OUTER JOIN c.orders o WHERE c.status = 1

このc.ordersがリレーションしてるものだから書けるようで、例えば別のテーブルで

SELECT e FROM Employee e LEFT JOIN MailingAddress a ON e.address = a.address

みたいなのは書けない…。ただし、EclipseLinkの2.4では、JPA2.1のdraftとして上記ON句の記述ができるようになってるようです。

EclipselinkでのJPQL ON句

さてさてどうしたもんか…。

JPAを利用していてDBスキーマ変更が発生したら…

今回Java EE6開発にあたってDB周辺はJPA(実体はEclipseLink)を利用しています。
ORマッパーは賛否ありますが、(個人的には)慣れるとやっぱり便利だよなー、と(^^;

もちろん性能などは要注意ですが。
(なので性能面に関しては前に書いたエントリ「JPQLを利用する際に行っていること」の手順で確認して使ってます)

Java EE6の場合だと、何でもかんでもEntityベースの操作、というわけでもなく、JPQLを通じてクエリも書けるので、場面にあわせて柔軟な対応ができるのではないかと思います。(最悪Native Queryもあるし)

しかしながら、ORマッパーを利用していると、どうしても避けられない作業として

「DBのスキーマ変更」

に伴うEntityの修正があって、手間に思うこともしばしば。

対応方法はスキーマ変更の影響範囲や大きさ等の状況によって異なると思うのですが、自分は主に以下の手順で行っています。

あ、その前にEntityクラス新規作成時の流れから書かねば。

新規にEntityクラスを作る流れ

  • NetBeansの自動生成機能でEntityクラスを生成

NetBeansだとプロジェクトを右クリックして「新規」->「その他」で以下画面から「持続性」->「データベースからのエンティティ・クラス」です。

f:id:kikutaro777:20130324205354j:plain

先日参加した「WebLogic Server勉強会」の「Java Persistence API入門」ではEclipseでも同じことをデモで紹介(ネットワーク環境からかデモが上手く動かなかったですが…)されていたので、同じ形かと思います。

ほぼこれだけです(^^;

当初は生成されたEntityのクラス名をリファクタリングで変えたり、とか色々していましたが、そうするとスキーマ変更時に手間が増えたりもするので、最近ではなるべく自動生成された素の状態を利用しています。

ただ全部が全部、素で使えるわけでもないので、手動の修正も行っています。

DBのスキーマ変更でEntityクラスを変えるとき

やり方は色々あるかと思います。自分が実際に利用する方法としては

  • 既存のEntityクラスをそのまま手で修正
  • 既存のEntityクラスを削除して、IDEの自動生成機能でEntityクラスを再生成
  • 新しく空のプロジェクトを作ってIDEの自動生成機能でEntityクラスを生成し、WinMergeで既存プロジェクトのEntityと比較しながら既存を修正

の3つです。

軽微な変更は上2つで大体対応可能ですが、少し影響範囲の大きい場合は(ミス防止のため)3つ目の方法でやっています。

既存プロジェクトと、Entity自動生成用の別プロジェクトのフォルダを比較して
f:id:kikutaro777:20130324205528j:plain
Entityクラスごとにチェックしながら既存Entityクラスへ反映
f:id:kikutaro777:20130324205535j:plain

手で修正されているEntityなどが混じっている場合には特に有効です。
間違えてEntity丸ごと消してしまうとえらいことなるので(^^;

その他、可能な修正としては

  • Entityクラスを直接修正して、テーブルへ反映

(方法はJPAでEntityからテーブルスキーマを生成する方法を教えて頂きました!」参照)
等もありますが、実際にはあまり使っていません(^^;

いずれにせよ、テーブル数が多くなってくると中々手間だったりして、何か上手い方法ないのかな、とも思ったり。まだあまり積極的に方法を調べていないので、こんな方法もありますよー、みたいな情報あったら教えて頂けると嬉しいです。

JPQLインジェクションを確認してみました

Java EE6を学んで、JPAを学んで、JPQLを知ったとき

「Native Queryを使っていない場合、通常のSQLインジェクションされにくいよなぁ」

と個人的に思ったりしたことがあります(^^;JPQL構文で攻撃する人は少ないだろうなと。

でもそんな甘い考えが通じない世界なわけで、ちゃんと確認しておこうと思って以下やってみました。

以下のBookテーブル、レコードがあります。

ID 書籍名 著者
1 Horikita MakiMaki
2 Aoi Miyiazaki

プログラムでは以下2つのメソッドを用意しました。

Prepared Statement(プレースホルダ、バインド変数)なしのJPQL
public int queryBookNotParameterized(String param){
    Query query = em.createQuery("SELECT b FROM Book b WHERE b.title = '" + param + "'");
    List<Book> books = query.getResultList();
    return books.size();;
}
Prepared Statement(プレースホルダ、バインド変数)ありのJPQL
public int queryBookParameterized(String param){
    Query query = em.createQuery("SELECT b FROM Book b WHERE b.title = :param");
    query.setParameter("param", param);
    List<Book> books = query.getResultList();
    return books.size();
}
呼び出し
System.out.println(bookFacade.queryBookNotParameterized("' OR 'X' = 'X"));
System.out.println(bookFacade.queryBookParameterized("' OR 'X' = 'X"));
結果

f:id:kikutaro777:20130317011035j:plain

プレースホルダなしの場合はJPQLインジェクションが成功してしまいました(^^;
入力した文字列(' OR 'X' = 'X)は通常よくあるSQLインジェクションと変わらないので、「JPQLだから大丈夫」とはいえない感じです。

同じこと考える人は当然いるわけで、海外にも情報ありました。

というわけで、ちゃんと考えて実装しないと、というお話でした(^^;

JPQLを利用する際に行っていること

今回の開発では初めてJava EE6を触り、初めてORマッパーを触るので、DBまわりや性能は結構気になる所です(^^;

ちょくちょく「ORマッパー使うなんて危ないねぇ」なんて周囲にも言われたりもしますが、JPAやEclipseLinkをキーワードに海外での情報も多く、Java EE6としてもJPAを利用することが標準(ですよね!?)だと思っているので、とりあえずは開発段階で色々と気を付けながら使ってみています。

JPQLはメインで動的クエリを採用(Criteriaを利用したかったのですが…断念)し、非常に簡単なものはNamed Queryを利用してます。

で、タイトルにある「JPQLを利用する際に行っていること」ですが、あまり大それたことではありません。ただ、NetBeans7.3になってから組み込まれたツールのおかげで、作業が劇的に軽減されたので、手順をまとめておこうかなと。

Java Persistence JPQL testing toolでJPQLクエリを組む/あるいはNetBeansの補完を利用しながらJPQLクエリを組む

以前に「NetBeans7.3で嬉しかったこと!」のエントリで書いたのですが、このツールは本当に便利です。

f:id:kikutaro777:20130311221804j:plain

ツール上でも(Ctrl+Shiftかなぁ)補完もちゃんと効きます。ただ、たまにご機嫌斜めになることもあるけど…(^^;気のせい?

f:id:kikutaro777:20130311223228j:plain

プレースホルダ(バインド変数)のところは一旦tool上、手で書いたりします。

実行して結果を確認する

tool上で実行して確認できるので、修正したり、実験したり、色々できます。

f:id:kikutaro777:20130311221813j:plain

実クエリを取得する

SQL」タブ(タブなのかな…?)で実際に利用しているDBMSに合わせたクエリが発行・表示されるので、これをコピーします。前まではログから毎回拾ってて、辛かった…(^^;

f:id:kikutaro777:20130311222057j:plain

DBに実クエリを投げて実行計画解析(explain等)を行う

以下はMS SQL Serverの例ですが、Oracleや他DBでも分析機能があるので、それを利用してコストとかインデックス効いてるかとかチェックします。

f:id:kikutaro777:20130311222107j:plain

で、上記をなるべく大量データで行っています。

あとはArqullian Persistence Extensionでも大量データ+テスト時にテストレコード追加、で確認を行うため、性能的に異常なケースが出れば割と早期に発見は可能かなと(^^;

さらにJMeterSelenium等で負荷試験を行っていく予定です。

実際の運用になると色々問題は出るかもしれないけれど…色々初めてすぎてやってみないとわからない(^^;

JPAで複合主キー・外部キー関連で諦めてしまったパターン

以下のようなテーブル(とあるテーブルを抜粋してシンプルにしたもの)をJPAでEntityをNetBeansから自動生成して、デプロイしようとしたらエラーとなりました。

f:id:kikutaro777:20130307170909j:plain

Caused by: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The @JoinColumns on the annotated element [field hogeEntity] from the entity class [class jp.co.hoge.entity.Foo] is incomplete. When the source entity class uses a composite primary key, a @JoinColumn must be specified for each join column using the @JoinColumns. Both the name and the referencedColumnName elements must be specified in each such @JoinColumn.

色々調べてみると複合主キー(Composite Primary Key)のテーブルがあって、その子テーブルのキーが複合主キーにはいなく単なるカラムとしてあるだけで、Uniqueキー&外部キー(とカスケード)となっている場合、JPAにはじかれてしまう?感じです。

同じようなことトライしてる人は結構たくさんいるっぽい(海外のみ…)ですがあまり良い回答がみつからず…。

二人がかりで結構調べたのですが駄目で、結局スキーマ変えて対応してしまいました orz
JPA力が足りないのか、英語力が足りないのか、そもそもテーブルとしてBad Practiceなのか。

上記では製品と1:1で製品仕様情報のテーブル持っていますが、BLOBサイズが大きいのと、バッチで削除されることもあるため独立したテーブルとなっているとのこと。また、キーを仕様情報IDだけじゃなくて、キー1、キー2、キー3、仕様情報IDの複合主キーにすればいい、というのも
ありますが、キー1、2、3を知らない他テーブルがこのテーブルを使い回しこともあるとのことで、こういう設計になったようです。

DBチームからは「なんでできないんじゃー」と突っ込まれますが、なぜ上記エラーをJPAに怒られるのかイマイチ理解できておらず、何とも。。。
しかし、そんな複雑な仕組みじゃない気がするのだけどなぁ。

もう後戻りできないので今回は諦め方針でしたが、後学のために、もしご存じの方がいたらコメント等頂けると嬉しいです(^^;…なんて

JPAのINSERTでEclipseLinkのbatch-writingを試してみました

今日は開発メンバからJPAに関して

「XXテーブルに明細データとして保存時に50~100レコードくらい追加する処理があるんですけど、for文で回せばいいんすかね?」

と聞かれました。
そういえば金魚本には一括更新と一括削除はあったけど、一括INSERTってなかったような…と(^^;未だに知識が金魚本ベースな自分。

JSR 317のドキュメントも簡単にみてみましたが、やはりbulk updateとbulk deleteしか見当たらない…。記述があったらごめんなさい…(^^;

けれど「bulk」いうキーワードを得て「JPA bulk INSERT」とか「JPA batch INSERT」でググってみました。
が、あまり「これだ!」というのが見つからなかったです。

上記2つ目のページでEclipseLinkにbatch-writingなるプロパティがある、という情報があって、調べてみると、persistence.xmlで以下のような設定ができるとのこと。

<property name="eclipselink.jdbc.batch-writing" value="JDBC"/>
<property name="eclipselink.jdbc.batch-writing.size" value="1000"/>

これを確認してみることにしました。

batch-writing

それぞれの説明ドキュメントは以下にありました。が、質素な感じ…。

とりあえず検証のために簡単な名簿テーブルを作成して、以下のようなプログラムを試しました。

package jp.co.hoge.dbperformance;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named(value = "meiboBean")
@RequestScoped
public class MeiboBean implements Serializable{

    @Inject
    MeiboFacade meiboFacade;
    
    public MeiboBean() {
    }
    
    public  void save(){
        Calendar calendarBefore = Calendar.getInstance();
        System.out.println(
                calendarBefore.get(Calendar.HOUR_OF_DAY) + "時" +
                calendarBefore.get(Calendar.MINUTE) + "分" +
                calendarBefore.get(Calendar.SECOND) + "秒");
        
        //50000件インサート
        for(int i = 0; i < 50000; i++){
            Meibo meibo = new Meibo();
            String id = Integer.toString(i);
            meibo.setId(id);
            meibo.setName("私の名前はななしのごんべい" + i);
            meibo.setAge(i);
            meibo.setAddress("とうきょうとかつしかくかめありこうえんまえ" + i);
            meibo.setBiko1("びこうなのでなんでもいいのです");
            meibo.setBiko2("びこうなのでてきとーてきとーてきとー");
            meibo.setBiko3("びこうなのでなにをいれてもいいんですの");
            meibo.setBirthday("1980/5/5");
            meibo.setCreateDate(new Date());
            meibo.setUpdateDate(new Date());
            meibo.setCreateUser("makimaki"+i);
            meibo.setUpdateUser("makimaki"+i);
            meiboFacade.create(meibo);
        }

        Calendar calendarAfter = Calendar.getInstance();
        System.out.println(
                calendarAfter.get(Calendar.HOUR_OF_DAY) + "時" +
                calendarAfter.get(Calendar.MINUTE) + "分" +
                calendarAfter.get(Calendar.SECOND) + "秒");
    }
}

MeiboFacadeはNetBeansの「エンティティ・クラスのセッションBean」で作ったEJBです。

動作確認の環境は自分の開発マシンのみで、DBもローカルのを使いました。

OS:Windows 7 Professional
APサーバ:GlassFish 3.1.2.2
DBサーバ:Microsoft SQL Server 2008 R2
OR:EclipseLink 2.3.2

って、ここ書いてて検証環境のEclipseLinkが古いことに気づきました…orz
が、とりあえず、やってしまったので、そのままに。

結果

理系ならちゃんと統計的にやれ…と大学時代の教授に怒られそうですが、ここでは1回きりの計測です…。すいません。そしてせめてミリ秒まで出せよ…とか色々突っ込み受けそうですが、半分は自分メモなので、ごめんなさい(^^;

条件 秒数
batch-writingなし 32秒
batch-writingあり size なし 34秒
batch-writingあり size 100 34秒
batch-writingあり size 1000 38秒

なんか微妙…ホントに設定が効いてるのか怪しいので、ログ出力した所

batch-writingありの場合

FINER: Begin batch statements
中略
FINER: End Batch Statements

batchと出てます。

batch-writingなしの場合

FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 11429200
FINEST: persist() operation called on: jp.co.hoge.dbperformance.Meibo[ id=3 ].
FINER: TX beforeCompletion callback, status=STATUS_ACTIVE
FINER: begin unit of work commit
FINER: TX beginTransaction, status=STATUS_ACTIVE
FINEST: Execute query InsertObjectQuery(jp.co.hoge.dbperformance.Meibo[ id=3 ])
FINEST: Connection acquired from connection pool [default].
FINEST: reconnecting to external connection pool
中略
FINEST: Connection released to connection pool [default].
FINER: TX afterCompletion callback, status=COMMITTED
FINER: end unit of work commit
FINER: release unit of work
FINER: client released
FINER: client acquired: 1308253

いつも通りな感じです。

なので一応、設定は効いているのかな、と。

ちなみにこのFINESTを付けた状態での秒数は

条件 秒数
batch-writingなし 93秒
batch-writingあり size 1000 113秒

ほんまかいな…。

なんか自分の検証方法が間違ってる気がしてきました…。

まあ、今回は元々の開発メンバの質問的にあるように「50~100レコード」なので、batchつけなくてもいいかなと。
一括INSERTで良い方法ってあるんだろか~。

2013/12/7 追記
@makingさんから以下コメント頂きました。


上記サンプルだと管理Beanからfor文でEJBのDB処理を呼んでるので、普通に考えると1回ごとコミットされてそうです(^^;これを確認した当時は多分ログで「Begin/End batch statements」を確認して、バッチ処理されてると思い込んでたみたいですが、最後の性能をみても、やはり細かい検証がいりそうだと改めて思いました(いつ検証するかは未定ですが。。)

にほんブログ村 IT技術ブログへ
にほんブログ村 にほんブログ村 IT技術ブログ Javaへ
にほんブログ村