複数のEntityManagerを切り替える(on CDI)

データベースが複数あるなどで、接続先を選びたい場合。
ついでにあやふやにしたままだったアノテーションも整理。
参考 JPA Module

環境

  • Java8SE
  • Tomcat8
    ※なので@PersistenceContextが使えません。使えるともう少し楽?
  • Weld 2.3.1

単一EntityManager(変更前)

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();
        }
    }
}

※@Producesはインジェクションを行うメソッド or フィールドに指定する
※@Disposesはオブジェクトの破棄を行うメソッドのパラメータに指定する

限定アノテーション

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface TestDS {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface CommonDS {}

※今回は2つを切り替えるのでアノテーションを2つ作成
※@Qualifierはインジェクションするimplを指定する時に使う
※@Retentionはアノテーションの有効範囲指定、指定しなければRetentionPolicy.CLASSになる
※ここでは実行時に切り替えが必要なのでRetentionPolicy.RUNTIMEを指定
※@Targetはアノテーションを指定できる対象指定

persistence.xml

<persistence-unit name="testProvider" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <non-jta-data-source>java:comp/env/jdbc/testSource</non-jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>

<persistence-unit name="commonProvider" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <non-jta-data-source>java:comp/env/jdbc/commonSource</non-jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>

Tomcatのデータソースにはjdbc/testSource、jdbc/commonSourceがある状態で、persistenceにそれぞれのデータソースを参照する設定を書く

EntityManger(変更後)

public class EntityManagerProducer {
    @Produces
    @TestDS
    @RequestScoped
    protected EntityManager createEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("testProvider");
        return factory.createEntityManager();
    }

    @Produces
    @CommonDS
    @RequestScoped
    protected EntityManager createCommonEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("commonProvider");
        return factory.createEntityManager();
    }

    protected void closeEntityManager(@Disposes @TestDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }

    protected void closeCommonEntityManager(@Disposes @CommonDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }
}

※限定アノテーション毎に@Producesのメソッドを作成
※同じく@Disposesのメソッドもそれぞれ用意し、パラメータに限定アノテーションを指定する

Inject

public class UserService {
    @Inject
    @TestDS
    private EntityManager entityManager;
}
public class GroupService {
    @Inject
    @CommonDS
    private EntityManager entityManager;
}

※Injectと共に限定アノテーションも指定すると、対象のEntityManagerをInjectできる

余談

@Disposesを横着して

   protected void closeCommonEntityManager(@Disposes @CommonDS @TestDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }

とか限定アノテーション両方とも指定したらエラーで怒られたのは内緒の話。

WELD-001424: The following disposal methods were declared but did not resolve to a producer method

closeするだけだから纏めさせて欲しい。。。