Challenge Engineer Life !

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

JPQLのLIKE式で%や_を含んだ検索をする

開発が進んで、いよいよJPQLの世界にどっぷり入っていく必要がありそうです。
今の開発では、動的クエリをメインに利用する方針にしています。

間違い防止やスキーマ変更に強くするためにもCriateria APIを利用したい所ですが、業務系のシステムではクエリが複雑になるケースも多々あるため、可読性や効率性を選んで今回Criteriaは止めました(^^;

あれがゴリゴリ書ける人は本当すごいと思います。。。

さて、今日出会った小さな壁は、検索処理の際、パーセント(%)やアンダースコア(_)を含んだ検索をしたい、というものです。

例えば普通に

SELECT o FROM Opportunity o WHERE o.opportunityName LIKE '商談%'

とJPQLを書いて検索すると

商談ほにゃらら
商談hogehoge

みたいな商談名が拾えます。普通のLIKE式です。

で、今回は以下のような商談名があった場合

この商談は駄目確率0%
100%上手くいく商談

検索で「0%」を含むやつ、とか、「100%」で始まる、とか検索したい、みたいな。

金魚本みたのですが情報はなく、今回は珍しく色々ググる前にJSRをみてみました(^^;
http://jcp.org/en/home/index

JPAのspecの中にLike Expressionsのページがあり、かなりわかりやすい例でExamplesがあって、すぐ解決しました。

SELECT o FROM Opportunity o WHERE o.opportunityName LIKE '%0\%' ESCAPE '\'
SELECT o FROM Opportunity o WHERE o.opportunityName LIKE '100\%%' ESCAPE '\'

f:id:kikutaro777:20130228230745j:plain
f:id:kikutaro777:20130228230752j:plain
上記ウィンドウはNetBeans 7.3から入ったJava Persistence JPQL testing toolの結果ウィンドウです。これがあるとないでは開発効率に差が出る気がします(^^;
過去エントリで少し触れています→「NetBeans7.3で嬉しかったこと!

エスケープでいける、ってことで通常のSQLと大差ない感じのようです。
上記例は、%のみですが、アンダースコア(_)も同じです。

NetBeans7.3で嬉しかったこと!

2/21にNetBeans7.3がリリースされました。
TwitterのTLで多くの人が早速触っているので、自分もお試しで入れて触ってみました。

まだ十分触れていないのですが、ちょっと触っただけで

これは嬉しい!

というポイントが2つあったので以下にまとめます。

嬉しいポイント1 フィールドやメソッドの一覧

VisualStudioでは普通に使えたフィールドやメソッドの一覧が以前のNetBeansにはなくて(多分…)、不便を感じていましたが、以下のようなパンくず(Breadcrumbs)が付いて、クリックするとフィールドやメソッドが!

さらにフィールドやメソッドを選択すればその項目にちゃんとジャンプします(^^)

f:id:kikutaro777:20130222212654j:plain

他人が作ったクラスの全体像をソースコード上で把握するには必要な機能なので、とても便利です。

嬉しいポイント2 JPQLの実行確認ツール

JPAのJPQLは非常に便利ですが、「今書いたJPQL実行したらどうなるかなぁ」と確認するとき、わざわざデバッグ実行したりして確認してました(^^;

が、「Java Persistence JPQL testing tool」なるものが7.3で入りました!!

まさにこれが欲しかった。

persistence.xmlを右クリックすると「JPQL問合せの実行」なるメニューがみえます。

f:id:kikutaro777:20130222213200j:plain

サービスタブでDBに接続した状態で、JPQLを記述して実行すると…

f:id:kikutaro777:20130222213310j:plain

おおー。実行結果が確認できる。これは嬉しいです。

時間の都合上、他の機能などを確認できていないのですが、この2つだけでもかなり嬉しいです(^^)

JPAでEntityからテーブルスキーマを生成する方法を教えて頂きました!

今日は午後からOracleで開催された「WebLogic Server 12c Forum 2013~ Java EEの現在と未来、WebLogicが拓く新たな可能性 ~」に参加してきました。今週はJenkins勉強会、Java x HTML5 Nightと参加したので、週3でOracleに…(^^;

セミナー内容は明日にでも簡単に触れようかなと思います。

で、上記セミナーで寺田さんの発表をみていたら、デモ動画の中で、NetBeansを使ってEntityからテーブルのカラムを追加して反映させる、というのが出てきて「えっ、そんなのできるの!?」と初めて知りました(^^;

私はDDL書いて、テーブルを先に作って「データベースからのエンティティクラス」メニューを使ってEntityクラスを生成する、というのはよく使っています。
この一方向しかできないかと勝手に思っていたので、実は地味に衝撃でした。

手元にノートPCを持っていたので試したのですが、同じことができず、セミナー終了後、寺田さんにやり方を教えて頂きました(^^;ありがとうございました。

persistence.xmlをダブルクリックすると「ドロップして作成」という選択があるので、これを入れるだけでできるとのことで、帰宅後、実際に試してみました。

お試し環境

IDE:NetBeans 7.2.1
DB:Java DB(Apache Derby)

以下のお試しでは「データベースからエンティティクラス」を試した後、「エンティティクラスからテーブル」を試します。
具体的には

  1. テーブル作成&カラム定義
  2. テーブルからエンティティクラスを自動生成
  3. persistence.xmlを修正
  4. テーブルを削除
  5. エンティティクラスからテーブルを自動生成
  6. エンティティクラスからテーブルのカラムを追加

をやってみました。青字の部分がメインの設定ですね。

テーブル作成&カラム定義

NetBeansの左ウィンドウ「サービス」タブからJava DBの対象DBを開いて「コマンドの実行」を選択します。
f:id:kikutaro777:20130201205514j:plain

以下create文でテーブルを作成します。

create table Book (
    BookId varchar(64) primary key,
    BookName varchar(512)
)

テーブルができました。
f:id:kikutaro777:20130201205550j:plain

テーブルからエンティティクラスを自動生成

「プロジェクト」タブに戻って、Entityを追加するパッケージを右クリックして、「新規」メニューから「その他」を選択し、あとは図のように選んでいきます。

f:id:kikutaro777:20130201205632j:plain

対象のデータソースを選んで、エンティティを作成するテーブルを選択します。
f:id:kikutaro777:20130201210033j:plain

あとはとりあえずそのままでいきます。
f:id:kikutaro777:20130201210043j:plain
f:id:kikutaro777:20130201210049j: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 = "BOOK")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Book.findAll", query = "SELECT b FROM Book b"),
    @NamedQuery(name = "Book.findByBookid", query = "SELECT b FROM Book b WHERE b.bookid = :bookid"),
    @NamedQuery(name = "Book.findByBookname", query = "SELECT b FROM Book b WHERE b.bookname = :bookname")})
public class Book implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 64)
    @Column(name = "BOOKID")
    private String bookid;
    @Size(max = 512)
    @Column(name = "BOOKNAME")
    private String bookname;

    public Book() {
    }

    public Book(String bookid) {
        this.bookid = bookid;
    }

    public String getBookid() {
        return bookid;
    }

    public void setBookid(String bookid) {
        this.bookid = bookid;
    }

    public String getBookname() {
        return bookname;
    }

    public void setBookname(String bookname) {
        this.bookname = bookname;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (bookid != null ? bookid.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Book)) {
            return false;
        }
        Book other = (Book) object;
        if ((this.bookid == null && other.bookid != null) || (this.bookid != null && !this.bookid.equals(other.bookid))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "jp.co.kke.entity.Book[ bookid=" + bookid + " ]";
    }
    
}

persistence.xmlを修正

persistence.xmlをダブルクリックして、「表作成の方針」を「ドロップして作成」にします。なるほどー、こんな設定があったのですね。気づかなかった…。
f:id:kikutaro777:20130201210109j:plain

テーブルを削除

では、今作ったテーブルをわざと手で消してみましょう。えいやっ。
f:id:kikutaro777:20130201210118j:plain

エンティティクラスからテーブルを自動生成

実行すると…おー、消したテーブルがエンティティクラスから生成されてる!すごい。
f:id:kikutaro777:20130201210146j:plain

エンティティクラスからテーブルのカラムを追加

続いてエンティティクラスのフィールドを追加(カラムとして)して実行すると…
f:id:kikutaro777:20130201210153j:plain
テーブルのカラムもできてるー!

これは便利です(^^)/寺田さん教えて頂いて、ありがとうございました!

なお、これで色々いじるとDDLなくて困る…?って思わずつぶやいたら@megascusさんより「DDLは作成されたテーブルから作成すれば良いですね。」とコメント頂きました。確かにその通りですね(^^;

注意点

persistence.xmlの設定にも書いてあったように「ドロップして」なので、既存データは消えてしまうので、実際に試す方はそこだけご注意下さい(^^;

とはいえ、何かと便利なのではないかなーと思って、教えて頂いた内容を(勝手に;)ネタにさせて頂きました。

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文だけを出力するようにしたいなぁ。
この辺りはまた継続調査です。

ディープコピー in JPA

2013/5/21追記
後々、このブログで書いたやり方はやめました(^^;
最終的には
過去に書いた「ディープコピー in JPA」の訂正
で書いた方式を採用しています。

プロト開発ではJPAを利用してデータベースを操作しています。
ORマッパーは初めて利用するので、まだ慣れない感じですが、勉強していて面白いです。

さて、開発の中であるトランザクションをまるっとコピーする処理が必要となりました。
具体的には商談-見積-製品…と親子関係のあるデータにおいて、商談をコピーして再利用する、といったようなものです。

ディープコピーの必要があるのですが、実装方法としては
・Clonableインタフェースを実装して自前でコピー処理を書く
Apache commons langライブラリを使ってSerializationUtils.clone()を使う
辺りがスタンダードのようです。
まずは試しにSerializationUtils.clone()を使った所、単純なオブジェクトは問題なくディープコピーできたのですが、Entityに対して行うと

Exception [TOPLINK-7242] (Oracle TopLink Essentials - 2006.7 (Build 060720)): oracle.toplink.essentials.exceptions.ValidationException
Exception Description: An attempt was made to traverse a relationship using indirection that had a null Session. This often occurs when an entity with an uninstantiated LAZY relationship is serialized and that lazy relationship is traversed after serialization. To avoid this issue, instantiate the LAZY relationship prior to serialization.

なんてエラーが出てしまいました。Entityの@OneToManyの部分でfetch=FetchType.EAGERはつけていない(LAZY)にしているのが悪いのでしょうか。

そんなこんなで色々調べていた所、Stackoverflowにこんな話もありました。
JPAでfindしたインスタンスのIDをnullにする
http://stackoverflow.com/questions/1106632/deep-copy-in-jpa
これが本当なら結構楽そうです。

試してみた所、うまく動きました。

OpportunityEntity copyOpportunity = oppEntityFacade.find(opportunity.getOpportunityId());
oppEntityFacade.detach(copyOpportunity);
copyOpportunity.setOpportunityId(null);
//以降、配下にあるコレクションの中でOpportunityIdをみるものもnull

DBのスキーマによっては微妙な場合もありそうですが、とりあえずプロトはこんな感じで実装してみました。

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