JAX-RS(Jersey) + JPA (on CDI) / on Tomcat
Tomcat上でJAX-RX(Jersey)を使う環境で、CDIからJPAのEntityManagerを取得するようにする。開発環境はeclipseでMarvenプロジェクトを使用。
分かりやすいサンプルがあったので楽勝・・・ではなかった。。。
参考(というかほぼそのまま)
JAX-RS(Jersey)とJPAのサンプルにCDIを使ってTomcatで動かす - Qiita
環境
- JavaSE 8
- Tomcat8
- 他ライブラリversionは記事中
JPA設定
src/main/resourcesに「META-INF」フォルダ追加
META-INFにpersistence.xml作成
<?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="testProvider" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:(略)" /> <property name="javax.persistence.jdbc.user" value="(略)" /> <property name="javax.persistence.jdbc.password" value="(略)" /> <property name="eclipselink.logging.level" value="ALL" /> </properties> </persistence-unit> </persistence>
※EclipseLink使用、DBはPostgreSQL、unit名はtestProvider
CDI設定
pom.xmlの依存設定
<dependency> <groupId>org.jboss.weld.servlet</groupId> <artifactId>weld-servlet</artifactId> <version>2.3.1.Final</version> </dependency> <dependency> <groupId>org.glassfish.jersey.ext.cdi</groupId> <artifactId>jersey-cdi1x</artifactId> </dependency>
※CDIコンテナはWeldを使用
追記:たぶん「jersey-cdi1x-servlet」の方がいい。
理由はここ。
/src/main/resources/META-INF/にbeans.xml作成
<?xml version="1.0" encoding="UTF-8"?> <beans/>
EntityManagerのProducerクラス
public class EntityManagerProducer { @Produces @RequestScoped protected EntityManager createEntityManager() { EntityManagerFactory factory = Persistence.createEntityManagerFactory("testProvider"); return factory.createEntityManager(); } protected void closeEntityManager(@Disposes EntityManager entityManager) { if (entityManager.isOpen()) { entityManager.close(); } } }
※@PersistenceContextは使えない(後述)
EntityManager使用(@Inject)クラス
@ApplicationScoped public class UserService { @Inject private EntityManager entityManager; public User getUser(Integer id) { User user = this.entityManager.find(User.class, id); return user; } }
※@InjectでEntityManagerを注入
※Userは単純な@Entityなクラスなので省略
※UserServiceクラス自体もCDIで管理してInjectしたいので@ApplicationScoped指定
JAX-RSのリソースクラス
@RequestScoped @Path("myresource") public class MyResource { @Inject private UserService userService; @GET @Produces(MediaType.APPLICATION_JSON) public User getIt() { User user = userService.getUser(1); return user; } }
※CDI管理しているUserServiceを@InjectしてUserを取得
※本当はUser自体ではなくDTOを返す方がいいと思うけど
これでブラウザから該当パスにアクセスすればUserのjsonが表示される。
半日ハマる
最初はEntityManagerの注入どころか、JPAを使わないUserServiceクラスの注入さえ
javax.ws.rs.WebApplicationException: Trying to register multiple service locators into single service locator application.
というエラーが出てできなかった。調べても解決法が見つからないので、weldのドキュメントでJNDIの設定をしてみたり、beans.xmlを書き換えてみたり、etc...と色々試したが一向に解決せず。
結論としては、pom.xmlに追加したjersey-cdi1xの設定がおかしかっただけ。。。
eclipseのプロジェクトは半年前に作っていたもので、その時のjerseyのバージョンは2.17。
にもかかわらず、jersey-cdi1xのversionタグに最新(この時は2.22.1)を手動設定してしまっていたので、整合性が取れていなかった様子。
pomの依存関係あたりを適当にいじっていたら急に動いたので、逆に何故動いたのか一瞬分からなかった。
要はversionタグを消してmanagedにしてやればよかった。
念のためプロジェクトを作りなおしてJerseyの最新版(2.22.1)で揃えてちゃんと動くことを確認。
Maven使うのも初めてとは言え、何のためのMavenなんだか。。。