Challenge Engineer Life !

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

過去に書いた「ディープコピー in JPA」の訂正

去年、Java EE6でWebプロトを作成していた頃に、JPAのEntityをディープコピーする方法に触れました。→Challenge Java EE 2012/10/30記事 ディープコピー in JPA

この当時やった方法は、StackOverflowの元ネタを参考に、コピーしたいEntityをfindで一旦取ってきてdetachし、IDを塗り替えて保存するという、今思うと結構ムチャクチャなやり方…(^^;

今の開発でこの方式を踏襲しようとしたのですが、試験しなおしてみると色々と問題があったので、トリッキーなやり方は止めて、普通にディープコピーすることにしました。
(もし参考にして実装された方がいたら申し訳ないです…うまくいっていることを祈ります;)

ディープコピーは、自前で実装もあると思うのですが、Entityの階層もそこそこに深いので、手間だな…とライブラリを探しました。

なんかJavaでディープコピー、シャローコピーするライブラリはたくさんありすぎて、どれがどういいのか判断に迷う…(-_-;ここがJavaの苦しい所でもあり、時間があれば楽しい所でもある感じです。

まずはStackOverflowにあった以下記事を参考にしました。

Java: recommended solution for deep cloning/copying an instance

pro/consって何?と思ったら、賛否、って感じの意味なんですね(^^;

Java Deep-Cloning Libraryはリンク切れしてますが、以下にありました。
https://code.google.com/p/cloning/

あと、この記事にないライブラリでもJava Generic Deep Copyとかもありました。

で、あれこれやりながら、最終的にはDozerなるライブラリを選びました。
以下、メモです。

Dozerのmaven設定

pom.xml

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.4.0</version>
</dependency>

と追加して終わりです。

DozerでEntityのディープコピー

コードもシンプルでサンプルレベルなら以下のような記述だけで動きます。

Mapper mapper = new DozerBeanMapper();
HogeEntity copyHoge = mapper.map(originalEntity, HogeEntity.class);

ただ、Mapperのインスタンスはシングルトンが推奨されていたので、そこら辺だけ工夫すればよいかなと。

結構シンプルに実装できました。

ログ注意

実行して、やたらコピー遅いなぁ、と思っていたら、DEBUGレベルで大量のログが出てました(^^;今の開発ではSLF4j&Logbackなので、logback.xml

<configuration>
    <!-- appenderとか省略 -->

    <logger name="org.dozer" level="off" />

    <!-- rootも省略 -->
</configuration>

という感じで、とりあえず回避しました。
このloggerが全然効かなくてハマってたのですが、pomにlogback-coreがなかったことが駄目だったようで、入れたら効きました。

ディープコピー 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へ
にほんブログ村