Challenge Engineer Life !

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

JPAのCDI Entity Listenersを試してみた

久しぶりにJPAネタです。

Java EE 7のJPA 2.1からEntity ListenerでCDIが利用できるようになりました。

EclipseLinkの例だと@EJBで書かれてます。
EclipseLink/Release/2.5/JPA21 - Eclipsepedia

この例だと、うーんロガーをわざわざEJBでかぁ…(^^;という感じが個人的にしたので、前にきしださんが書いていたプロデューサー使う方法でやってみました。d.hatena.ne.jp

LoggerProducerはそのまんまお借りして

@Named
@Dependent
public class LoggerPoducer {

    @Produces
    public Logger getLogger(InjectionPoint ip){
        return Logger.getLogger(ip.getMember().getDeclaringClass().getName());
    }
}

EclipseLinkの例にあったLoggerEntityLisetenerの@EJBを@Injectへ

public class LoggerEntityListener {
   @Inject //ProducerでInjectされる
   private Logger logger;
    
   @PrePersist
   public void prePersist(Object object) {
       logger.info("prePersist");
   }
   
   @PostPersist
   public void postPersist(Object object){
       logger.info("postPersist");
   }
   
   @PreDestroy
   public void preDestroy(){
       logger.info("preDestroy");
   }
   
   @PostConstruct
   public void postConstruct(){
       logger.info("postConstruct");
   }
}

そして、対象のEntityクラスに@EntityListenersでLoggerEntityListenerを指定しました。

@Entity
@EntityListeners({LoggerEntityListener.class})

EclipseLinkは2.5.2、APサーバはGlassFish 4.1で試したのですが…なぜかloggerがnullで上手くインジェクトされませんでした。

色々調べていたら以下に辿り着き

github.com

上記に以下リンクがありますが、GF4.1の問題っぽい?現象としてはこれと全く同じでした。
[GLASSFISH-21195] CDI Injection in Entity Listener - NullPointerException - Java.net JIRA

さらに読むとEclipseLink2.5.2の問題じゃないかみたいな。
Bug 438105 – Logic bug in EntityListenerInjectionManagerImpl

で、最終的には、最新のPayaraにはEclipseLink2.6が入って、それで動くよとのこと。

ということで、初めてPayaraをダウンロードしました。中はホント完全にGlassFishなんですね(^^;
Pre-Release Builds

利用したのは「Payara-web 4.1.153 (Web Profile)」です。

そして実行すると…動きました!

f:id:kikutaro777:20150618225652j:plain

GlassFishをPayaraに変えるのは全く手間がなさそうです。普通に動くし、今回のようにPayaraのほうが対応が早いケースがあるのを知ると、ちょっと真面目にPayara追っていかないとなーと。

前から既に追っているはすぬまさんのブログで勉強していかねば!

www.coppermine.jp

既にめっちゃPayaraについて書かれている(*´Д`)さすがすぎる

はすぬまさんのブログで「Payara」のタグがついている記事の検索結果
Programming Studio - 検索結果

CDI Entity Listenersについての日本語参考情報は以下。

etc9.hatenablog.com

qiita.com

今更ながらJPAの楽観ロックを確認してみました

色んなところで既に書かれてるので、今日のは完全に自分向けのメモ(^^;

JPAの楽観ロック、知識としては知っていたのですが試したことなくて、ちょうどやる必要が出たので、まずは簡単なサンプルで確認してみました。

プログラムは営業系のシステムをイメージして、商談データがあって、2人の人が同じデータを取得して変更して更新してしまった、という流れです。

環境

環境はWindows 7上のNetBeans 7.3でJava EE 6 & JDK7、GlassFish3.1.2でEclipseLink2.3.2です。データベースはMicrosoft SQL Server 2008 R2。

テーブルは超簡易で以下のようなものです。

商談ID 商談名 Version 作成日 更新日
0 商談1だよ 0 2013-10-10 19:51:55.793 2013-10-10 19:51:56.060

Versionカラムは楽観ロック用に設けたものです。

動作

まず結果から。

サンプルアプリをデプロイして、ブラウザを2つ起動してアクセスします。

f:id:kikutaro777:20131010204737j:plain

f:id:kikutaro777:20131010204743j:plain

1つ目のテキストにDBにある商談ID「0」を指定して「読込」ボタンを押します。
DBから読み込んで商談名が表示されます。

f:id:kikutaro777:20131010204836j:plain

f:id:kikutaro777:20131010204840j:plain

IEのブラウザは「商談2なんです」と名称を書き換えます。
もう1つのFirefoxでは「商談3なんだよ!」と名称を書き換えます。

f:id:kikutaro777:20131010205017j:plain

f:id:kikutaro777:20131010205021j:plain

で、各々で「保存」ボタンを押下!

NetBeansではブレイクポイントで複数のスレッドが止まると以下のような歯車マークになります。
f:id:kikutaro777:20131010205241j:plain

NetBeansのマルチスレッドデバッグは過去に書いてました。
NetBeansでマルチスレッドデバッグ

oppFacade.edit(myOpp)という所でEJB側でDB保存処理されます。
成功したのでDBを確認します。

f:id:kikutaro777:20131010205402j:plain

保存されてますね。そしてVersionカラムの値が「1」とカウントアップされてます(^^)
ここに関しては後程。

ではもう1つのFirefoxから来たほうのスレッドに切り替えて

f:id:kikutaro777:20131010205504j:plain

保存処理を実行してみます。

f:id:kikutaro777:20131010205511j:plain

するとエラーに!

GlassFishをみると

f:id:kikutaro777:20131010205626j:plain

OptimisticLockExceptionとなっています(^^)取得した時点でVersionが0で、書き込もうとしたらVersionが既に1となっていたため、「誰かが書き込んだやろ」と判定した流れかと。

EJB使うとBean側ではjavax.ejb.EJBExceptionに成り代わってしまうのが悲しいですが、EJB側でちゃんとcatchすればいける…のかな多分(^^;未確認

@Versionアノテーション

やったことは単純で、2つだけです。

  • テーブルのカラムにintの「Version」を設ける
  • Entityの上記カラムに該当する項目に@Versionのアノテーションを付与する
@Column(name = "Version")
@Version
private int version;

だけです。すんなり思い通りに動いたので良かった(^^)

ちなみにEclipseLinkの楽観ロックに関する記述は以下でした。
http://wiki.eclipse.org/EclipseLink/Examples/JPA/Locking#Optimistic_Locking
最後の1文に「java.sql.Timestamp型も使えるけど、numeric typeをおススメします」と書いてあるのが気になる(^^;

DBのXMLデータ型なるものをJPAで扱える???お試し編

今日のはモヤっとした投稿になります…(^^;

2013/7/7追記
と書いてたのですが、モヤっとしてたことを「しんさんの出張所」ブログの以下記事が色々と明確にして下さいました(^^;ありがとうございます。

RDBXMLを扱う
http://d.hatena.ne.jp/shin/20130706

次の案件の話を聞いていたら、あるDBテーブルのカラムでXML型を使うみたいな話がありました。
恥ずかしながら一部のリレーショナルDBに「XML型」なるものがあることを初めて知ったのですが…。ちなみにMS SQL Serverです。
http://msdn.microsoft.com/ja-jp/library/ms189887(v=sql.90).aspx

で、これを使いたいという話があって

「え、そもそも、そんな型の値をJPAで取れるの?」

みたいな疑問が(^^;

とりあえず、いつも通りにテーブルからエンティティ作成の流れで試してみると動くものはできました。

まずはテーブル定義。型「xml」!個人的に見慣れないので何か斬新(^^;

create table xmlTable(
    id int,
    xmlVal xml,
    primary key(id)
)

で、適当にMSDNにあったサンプル的なレコード作成しました。

INSERT INTO XmlTable values(1,'')

ここからNetBeansでお決まりのウィザード「データベースからのエンティティ・クラス」を実行

f:id:kikutaro777:20130705210840j:plain
f:id:kikutaro777:20130705210845j:plain
f:id:kikutaro777:20130705210849j:plain

以下のようなEntityクラスが生成されました。

package jp.co.hoge.entity;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author kikutaro777
 */
@Entity
@Table(name = "xmlTable")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "XmlTable.findAll", query = "SELECT x FROM XmlTable x"),
    @NamedQuery(name = "XmlTable.findById", query = "SELECT x FROM XmlTable x WHERE x.id = :id"),
    @NamedQuery(name = "XmlTable.findByXmlVal", query = "SELECT x FROM XmlTable x WHERE x.xmlVal = :xmlVal")})
public class XmlTable implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "id")
    private Integer id;
    @Size(max = 1073741823)
    @Column(name = "xmlVal")
    private String xmlVal;

    public XmlTable() {
    }

    //setter,getter略
    //hashCode、equals、toString略    
}

で、Entityからレコード取得すると…

f:id:kikutaro777:20130705210929j:plain

と、普通に取れるわけですが、これだと色々うまみがない気が(^^;

この辺りをみるとEclipseLinkではDescriptorCustomizerクラスを使ってゴニョゴニョゴニョと書いてあってDocument型に入れてたりする。

というか…

そもそもXML型の利点って何なんだろう(^^;?

ってなってきたので、そこからやりなおさねば…。

今日聞いた話では、ツリーのような形式のデータを保存、復元することが目的で、その表現でXMLを利用する…という流れだったのですが、まず個人的には、オブジェクトをそのままシリアライズしてBLOBに突っ込んだりしてもいいんじゃないのかなぁ、と思ったり。

そしたらXMLファイルとして吐きたい、という要望もある様子。

うーん、なんか色々モヤモヤしてる…やはりXML型の具体的な使われ方とかをもっと調べてみなきゃ。

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を利用する際に行っていること

今回の開発では初めて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に怒られるのかイマイチ理解できておらず、何とも。。。
しかし、そんな複雑な仕組みじゃない気がするのだけどなぁ。

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

EclipseLinkのログ出力でギブアップしたこと…

なんか初めて諦めてしまったことな気がします…(-_-;

EclipseLinkのログ出力でやりたかったこと

去年のエントリ「EclipseLinkのログをSLF4J&Logbackにて出力したい」で、JPQLのクエリからEclipseLinkで生成された実クエリをログ出力できることを確認しました。

これ以降の作業を棚上げしていたのですが、実際にやりたいこととしては以下のようなログ出力を出力することでした。


2013-01-29 17:11:29.296,INFO,search,kikutaro777,**画面のID**,10.xx.xx.xx,商談検索を実行
2013-01-29 17:11:30.174,DEBUG,logic,kikutaro777,**画面のID**,10.xx.xx.xx,実行検索クエリ -> 'SELECT t0.OPPORTUNITYNAME, ...
bind => [kikutaro777] '

画面ログと内部ロジックのログは分けるとか、そういうレベルの議論もありますが…。

ログ自体はSLF4JとLogbackの組合せで使っていて、EclipseLinkでそのログに上記のようなフォーマットで出したいというものです。

ASP.NETだと多分簡単に上記のようなログを出せるのですが、Java EEだとちょっと悩んでしまったり…。

結論から書くと、ちょっと時間的にもギブアップをして(一応、色々調べてみたのですが…)、クエリ部分は以下のようなログとなりそうです。


2013-01-29 17:11:29.296,INFO,search,kikutaro777,**画面のID**,10.xx.xx.xx,商談検索を実行
2013-01-29 17:11:30.174,DEBUG,,,,,実行検索クエリ -> 'SELECT t0.OPPORTUNITYNAME, ...
bind => [kikutaro777] '

要はビューサイド、Beanサイドから得る情報は出さない、というノリ…。
まあ、クエリまでログ出すのは、基本的に障害分析とか性能分析のためなので…。

なんだか自分のコーディング力がない気がプンプンしますが、とりあえず、やってみたことだけまとめます。

イメージ

すさまじくざっくりしたショボンな絵ですが…自分はこんな感じでJava EE6内部のイメージをしています。

f:id:kikutaro777:20130129194531p:plain

例えば「商談情報を検索する」みたいなもので、画面で検索ボタン押して、その画面のBackingBeanがEJBのDB検索処理ロジックを呼び出して…みたいな。EJBはステートレスセッションなのでプール上にインスタンスがいて…みたいな。

試した実装

SLF4J経由のLogbackでロギングしてますが、Logbackは以下のような感じで設定しています。

logback.xml
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>/CpqPrototypeLog/logFile.log</File>
        
    <!-- logファイルを区切る設定 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        ...
    </rollingPolicy>
        
    <!-- 出力内容設定 -->
    <encoder>
        <charset>UTF-8</charset>
        <!-- 日付,出力レベル,処理区分,ログイン者ID,画面ID,クライアントIP,内容 -->
        <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS},%-5level,%marker,%X{LOGIN_USER_ID},%X{SCREEN_ID},%X{CLIENT_IP_ADDR},%msg %n</Pattern>
    </encoder>
</appender>

で、EclipseLinkのドキュメントを色々みていくと、CustomLoggerを使って出すのが良い…のかなと(^^;

EclipseLinkのCustomLoggerドキュメント

上記ドキュメントを参考に色々試していたのですが、頭がこんがらがったのでクラス図書きました。

f:id:kikutaro777:20130129194925p:plain

Custom Logger

ほぼサンプルイコールですが、以下のような感じで。Logクラスは内部でSLF4Jでロギングする処理を持つクラスです。
本当はここで、ログイン者情報とか画面IDとかをBackingBeanから取る、あるいはその逆などをしたいのですが、なんともかんとも。

package jp.co.hoge.hogehoge.log;

import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.logging.SessionLogEntry;

public class CustomSessionLog extends AbstractSessionLog implements SessionLog{
    
    //private static Logger logger = LoggerFactory.getLogger("jp.co.hoge.hogehoge.log.CustomSessionLog");
    
    @Override
    public void log(SessionLogEntry sle) {
        Log.info("ログメッセージ:" + sle.getMessage());
    }
}
SessionCustomizerとかSessionEventAdapterとか

SessionCustomizerが入ると以下のような形になるっぽいです。
f:id:kikutaro777:20130129195030p:plain
Adapterとかも触ってみて、なんかできないかなと模索したり。。。

package jp.co.hoge.hogehoge.log;

import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.sessions.Session;

public class CustomSessionCustomizer implements SessionCustomizer{

    @Override
    public void customize(Session sn) throws Exception {
        SessionLog customLogger = new CustomSessionLog();
        sn.setSessionLog(customLogger);

        sn.getEventManager().addListener(new LogSessionEventAdapter());
    }
}
package jp.co.hoge.hogehoge.log;

import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.SessionEvent;
import org.eclipse.persistence.sessions.SessionEventAdapter;

public class LogSessionEventAdapter extends SessionEventAdapter{
    
    @Override
    public void preExecuteQuery(SessionEvent event) {
        System.out.println("クエリ実行の前に走るイベントです");
    }
}

なんというか、何をしてもBackingBeanと縁が遠すぎてどうしたものかと。
たとえばASP.NETだと、上図のような境界が明確にはないため、なんでもかんでもできるのですが、Java EEって境界がしっかり分かれていて、CDIで注入は簡単ですが、上記のような既存ログの仕組みにどうやってログイン者情報のインスタンスとか伝播(あるいは逆から取得)させていくんだろ…とか悩んでしまったり。

ちなみに

persistence.xmlにあるproperty属性でeclipselink.logging.parametersがあるよという情報を頂き、調べたのですが、これはJPQLのバインド変数を出力するスイッチで使う、と理解しました。あってる…かな(^^;

たぶん

なんか自分の理解がおかしかったり、EclipseLink内のSession辺りがちゃんと理解できていないのが間違いなく今回の問題・原因なんですが、これ以上時間をかけるのは厳しいため諦めました(-_-;
今までで一番悩んだかもしれません。

EclipseLinkのログ周辺情報はすごく少ない感じを受けて、ちょっと不安にもなったり…。
がんばろー。

追記
記事の投稿後、interceptorでloggerを実装して、リフレクションでbeanの情報を取得して…って流れでどうでしょう、とコメント頂きました。またトライして結果が出たら追加、あるいは別記事にしたいと思います(^^)コメントありがとございました!

EclipseLinkのログをSLF4J&Logbackにて出力したい

現在O/RマッパーとしてEclipseLinkを利用しています。初めてO/Rマッパーを使うので不慣れなところが色々あるのですが、ログ出力もその1つです。現在、気になっている所としては
・O/RマッパーのログはGlassFish側のログファイルではなく、SLF4J&Logbackを使って出力するログファイルへ出力したい
・誰が実行したSQLか、を出せるか
辺りです。で、とりあえず前者をトライしました。

とりあえずEclipseLinkの本家ドキュメントを中心に探ります。

◆EclipseLinkにおける基本的なLoggingに関して
http://wiki.eclipse.org/EclipseLink/Examples/JPA/Logging

LOG4JApache Commons Loggingを利用している場合
http://wiki.eclipse.org/EclipseLink/Foundation/Logging

◆CustomLogger
http://wiki.eclipse.org/EclipseLink/Examples/JPA/CustomLogger

SLF4J&Logbackの場合は3つ目なのかな…?と、まずはサンプルコードプラスアルファで試してみました。

まずは「Custom Logger Implementation」と記述のある部分

package customlog;

import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.logging.SessionLogEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * …省略…
 */
public class CustomSessionLog extends AbstractSessionLog implements SessionLog {
    
    private static Logger logger = LoggerFactory.getLogger("customlog.CustomSessionLog");

    @Override
    public void log(SessionLogEntry sle) {
        logger.info("ログレベル:" + sle.getLevel() + "ログメッセージ:" + sle.getMessage());
    }
    
}

次に「SessionCustomizer Implementation」と記述のある部分

package customlog;

import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.sessions.Session;

/**
 * …省略…
 */
public class JPAEclipseLinkSessionCustomizer implements SessionCustomizer{

    @Override
    public void customize(Session sn) throws Exception {
        SessionLog aCustomLogger = new CustomSessionLog();
        aCustomLogger.setLevel(SessionLog.FINENEST); //ここはpersistence.xmlのログレベルにしたいが…
        sn.setSessionLog(aCustomLogger);
    }
    
}

あとは、persistence.xml

<properties>
    <property name="eclipselink.session.customizer" value="customlog.JPAEclipseLinkSessionCustomizer"/>
</properties>

を追加して、とりあえず実行すると…。(logback.xmlなどは設定されてる前提です)

2012-12-04 19:18:27.407,INFO ,,,,,ログレベル:5ログメッセージ:login_successful 
2012-12-04 19:18:27.408,INFO ,,,,,ログレベル:6ログメッセージ:jmx_mbean_runtime_services_registration_encountered_multiple_mbeanserver_instances 
2012-12-04 19:18:27.408,INFO ,,,,,ログレベル:2ログメッセージ:jmx_mbean_runtime_services_registration_mbeanserver_print 
2012-12-04 19:18:27.408,INFO ,,,,,ログレベル:6ログメッセージ:jmx_mbean_runtime_services_switching_to_alternate_mbeanserver 
2012-12-04 19:18:27.408,INFO ,,,,,ログレベル:2ログメッセージ:jmx_mbean_runtime_services_registration_mbeanserver_print 
2012-12-04 19:18:27.409,INFO ,,,,,ログレベル:6ログメッセージ:jmx_mbean_runtime_services_switching_to_alternate_mbeanserver 
2012-12-04 19:18:27.409,INFO ,,,,,ログレベル:1ログメッセージ:registered_mbean 
2012-12-04 19:18:27.409,INFO ,,,,,ログレベル:1ログメッセージ:registered_mbean 
2012-12-04 19:18:27.410,INFO ,,,,,ログレベル:1ログメッセージ:jmx_mbean_classloader_in_use 
2012-12-04 19:18:27.410,INFO ,,,,,ログレベル:1ログメッセージ:mbean_get_application_name 
2012-12-04 19:18:27.411,INFO ,,,,,ログレベル:1ログメッセージ:mbean_get_module_name 
2012-12-04 19:18:27.416,INFO ,,,,,ログレベル:2ログメッセージ:metamodel_canonical_model_class_found 
2012-12-04 19:18:27.417,INFO ,,,,,ログレベル:1ログメッセージ:deploy_end 
2012-12-04 19:18:31.939,INFO ,,,,,ログレベル:2ログメッセージ:client_acquired 
2012-12-04 19:18:31.940,INFO ,,,,,ログレベル:2ログメッセージ:TX_bind 
2012-12-04 19:18:31.940,INFO ,,,,,ログレベル:2ログメッセージ:acquire_unit_of_work_with_argument 
2012-12-04 19:18:31.941,INFO ,,,,,ログレベル:1ログメッセージ:register_new_for_persist 
2012-12-04 19:18:31.952,INFO ,,,,,ログレベル:2ログメッセージ:TX_beforeCompletion 
2012-12-04 19:18:31.952,INFO ,,,,,ログレベル:2ログメッセージ:begin_unit_of_work_commit 
2012-12-04 19:18:31.955,INFO ,,,,,ログレベル:2ログメッセージ:TX_begin 
2012-12-04 19:18:31.956,INFO ,,,,,ログレベル:1ログメッセージ:execute_query 
2012-12-04 19:18:31.957,INFO ,,,,,ログレベル:1ログメッセージ:acquire_connection 
2012-12-04 19:18:31.957,INFO ,,,,,ログレベル:1ログメッセージ:reconnecting_to_external_connection_pool 
2012-12-04 19:18:31.965,INFO ,,,,,ログレベル:3ログメッセージ:INSERT INTO MANUFACTURER (MANUFACTURER_ID, ADDRESSLINE1, ADDRESSLINE2, CITY, EMAIL, FAX, NAME, PHONE, REP, STATE, ZIP) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	bind => [274803, null, null, null, null, null, Manufacturer274803, null, null, null, null] 

FINESTなので超詳細まで出てますが、とりあえず何か出ました。でも、このままだとプログラム側でログレベルを設定するようになっているので嫌です。やはりpersistence.xmlで設定された

<property name="eclipselink.logging.level" value="FINE"/>

辺りか、logback.xmlのログレベルを(EclipseLinkのログレベルにマッピングして)設定したい所です。

あとは、詳細すぎる余計なログは不要なので、トランザクション周りとSQL文だけを出力するようにしたいなぁ。
この辺りはまた継続調査です。

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