WinstoneでTopLink
JPAの手習い兼,Winstone/DerbyプラグインによるIDEAで完結した開発環境の検証なんてのをやってみたんだけど,意外なところでハマってしまった。一応,解決できたんで,経緯をメモっておく。
えーと,この構成をMacBookで試したときは,なんの問題もなく実施できたんだけど,Windowsでやったらダメだった。理由は,Winstoneのクラスローダに問題があるらしく,TopLinkがpersistence.xmlの読み込みに失敗してしまう(エラーコードはTOPLINK-30005)。
原因はハッキリしていて,persistence.xmlのURLのパス情報に"/"じゃなくて"\"が使われているからだった。で,どこでその設定をしているのか見つけるのが,少々面倒だったのだが,突き止めた先はこちら。
== WebAppConfiguration.java == private ClassLoader buildWebAppClassLoader(Map startupArgs, ClassLoader parentClassLoader, String webRoot, List classPathFileList) { List urlList = new ArrayList(); try { : // Classes folder File classesFolder = new File(webInfFolder, CLASSES); if (classesFolder.exists()) { Logger.log(Logger.DEBUG, Launcher.RESOURCES, "WebAppConfig.WebAppClasses"); urlList.add(new URL("file", "", classesFolder.getCanonicalPath() + "/")); ここ→ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :
classesFolder.getCanonicalPath()
なんてやってるから,おかしな事になるので,ここを以下のように書き換える。
urlList.add(new URL("file", "", classesFolder.getCanonicalPath() + "/")); ↓ urlList.add(classesFolder.toURL());
これで解決と思いきや。今度は,TopLinkがDerbyのJDBCドライバの読み込みに失敗する(TOPLINK-4002)。:-(
TopLinkもDerbyも同じくcommonLibFolderに入れてるんだけどなぁ。同じ親のクラスローダを共有しているはずだから,お互いを認識できると思うんだけど,なぜかダメみたい(なんでOSXで平気だったんだ?)。こっちは,しゃあないんで,DerbyをTopLinkより上のクラスローダに配置することで,急場をしのぐこととした。
ちょっと釈然としないところもあるが,まあいいや。
#ちなみに,Tomcatでは何もしなくてもすんなり動くよ。
TopLink Essentials JPA Extensions Reference
persistence.xmlのproperties要素に書けるオプションとか,いろいろ載ってて便利。これで,キレイに印刷できるように工夫してあれば,なお良いのに。
とりあえず,TopLink+Derby(非データソース利用)んときの,persistence.xmlの記述例をメモっておく(IDEAのpersistence.xmlの入力支援は,こうゆう実装依存ネタはてんで弱いから*1)。
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"> <persistence-unit name="TestPersistenceUnit" transaction-type="RESOURCE_LOCAL"> <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider> : <properties> <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/> <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/testdb;create=true"/> <property name="toplink.ddl-generation" value="create-tables"/> <property name="toplink.jdbc.user" value="app"/> <property name="toplink.jdbc.password" value="app"/> <property name="toplink.logging.level" value="ALL"/> </properties> </persistence-unit> </persistence>
注目すべき点はtoplink.ddl-generationプロパティ。
って,別にTopLinkに限った機能じゃないし,今となっては珍しくもないんだけど,ちょいちょい組んでみるときなんかは便利よね。ただ,無茶な話だとわかってはいるが,エンティティの追加(CREATE TABLE)はインクリメンタルにできるけど,エンティティの属性の変更(ALTER TABLE)は直接DBをいじってあげないとダメなのね。
#Railsのマイグレーション的な事を一瞬期待してしまった。:-P
*1:そのためにLive Templateがあるんだろう
JPAは悪くないと思う
しばらくO/Rマッピングな世界から遠ざかっていたけど,今となってはJPAにすがっていいんじゃないかと思う(世の中の流れ的にもズレてないし)。「JavaEE5はちょっと」っと思ってるなら,JPAだけ抜き出して使う事はできるから,そんなに気にする話でもないかと。
ちょっと手習い程度とは言え,「JDK5/TopLink(JPA)/Winstone(Servlet2.4)/Guice」の組み合わせでサックリ使えたし。
問題は「トランザクション管理をどいつにやらせるか」くらいだろうから,それこそSpringでもSeasarでも好きなのを使ってクレイって話だ。DAOを作るだ,作らないだなんて話は,それこそどうでもよろしかろう。
warp-persist試す
と言っても,ここに書いてあるとおりにやっただけなんだけどさ。Dynamic Findersで,気になったところがあったんで,そこだけメモっとく。
Dynamic Findersってのは,@Finderアノテーションを付けたインターフェイスだけを作っておけば,実装いらずで検索用DAOが作れるっていうwarp-persistの機能のことだ。
こんな感じでFinderのコードを書いて,
import com.wideplay.warp.persist.dao.Finder; import sample.entity.Blog; import java.util.List; public interface BlogFinder { @Finder(query = "select o from Blog o") List<Blog> getAllBlogs(); }
こんな感じで,Guice(warp-persist)に登録すると,Finderの実装がなくても使えるようになる。
public class BlogAppFactory { private static Injector injector; public static void init() { injector = Guice.createInjector( new BlogModule(), PersistenceService .usingJpa() .across(UnitOfWork.REQUEST) .addAccessor(BlogFinder.class) // ← ここ .transactedWith(TransactionStrategy.LOCAL) .buildModule()); } public static <T> T getInstacne(Class<T> clazz) { return injector.getInstance(clazz); } }
一見便利そうに思えたんだけど,実装いらずは検索処理だけで,更新処理は実装がいるのだ。
更新用DAOってのは,こんなコードを書く(JavaEEによるリソース管理しないから,@Resourceアノテーションはないよ)。
import com.google.inject.Inject; import com.google.inject.Provider; import com.wideplay.warp.persist.Transactional; import sample.entity.Blog; import javax.persistence.EntityManager; public class BlogDAO { @Inject private Provider<EntityManager> em; @Transactional public Blog create(String title, String link) { Blog blog = new Blog(title, link); em.get().persist(blog); return blog; } }
つまり,検索用と更新用のDAOを用意するハメになるんで,めんどくせぇなぁ〜なんて思ったわけだ。
BlogDAO blogDAO = BlogAppFactory.getInstance(BlogDAO.class); BlogFinder blogFinder = BlogAppFactory.getInstance(BlogFinder.class);
で,試したことは「BlogFinderをBlogDAOに実装したらどうなるか?」っての。やれば,できるんだけど,アノテーションとかどうなるのか,興味あったんで試してみた。結果はこう。
public interface BlogFinder { // @Finder(query = "select o from Blog o") → アノテーションは要らない List<Blog> getAllBlogs(); } public class BlogDAO implements BlogFinder { @Inject private Provider<EntityManager> em; @Transactional public Blog create(String title, String link) { Blog blog = new Blog(title, link); em.get().persist(blog); return blog; } @Finder(query = "select o from Blog o") // ← 実装側にアノテーションが必要 public List<Blog> getAllBlogs() { // 空実装でよい return null; } }
んー,こんなだったら,あえてDynamic Findersを使わず,ふつーにJPAクエリを使った方がよさげだの。あまり異端なことしても,ついてくる人いなさそうだし。
それと,なるべくJavaEE風な記述のほうが,いろいろ便利な予感がする。:-D