新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:Spring

[学習記録] Spring

内容: 11章 JPAとSpringの連携

JPAJava Persistence API)とは何か?
  • EJB2.xの反省をもとに作成された、Java EEに含まれるAPI
    • Java EE 5ではEJBに含まれるAPIだったが、EE 6から分離)
    • EJB2.xの永続化機能であるEntityBeanには、EJBコンテナ上でしか動作しない、デプロイの手間が掛かるという問題が存在
    • これらの反省を踏まえ、JPAPOJOベースとなり、EJBコンテナなしの環境で利用できるように
    • EJBコンテナ有りでも利用可能(トランザクション管理などの処理をEJBがやってくれるか、ソースコードで明示的に行うかの違い)
    • Springと一緒に使う場合は当然EJBコンテナなし。本書ではEJBコンテナなしの場合を解説
  • JPAは規約であって実装ではないので、実装が必要
    • Hibernate EntityManager, EclipseLink, OpenJPAなど
POJOベースのドメインクラス
  • ドメインクラスの設定
    • グループテーブルT_GROUP(group_id, name)とメンバーテーブルT_MEMBER(member_id, name, detail, group_id)
    • GroupクラスとMemberクラスを考える
    • @Entityで永続化を行うドメインクラスを指定
    • @Tableで対象テーブルを指定(クラス名とテーブル名が等しければ省略可能)
    • @Columnでフィールドと列名を関連づけ(フィールド名と列名が等しければ省略可能)
    • @IdでPrimary Keyを指定
    • @ManyToOneで多対一の関係を指定、@JoinColumnで外部キーを指定
    • @OneToManyで一対多の関係を指定、mappedbyで@JoinColumnで設定したフィールドを指定
@Entity
@Table(name="T_MEMBER")
public class Member {

    @Id
    @Column(name = "member_id")
    private int id;

    // @Columnを省略可能
    private String name;
    private String detail;

    @ManyToOne
    @JoinColumn(name = "group_id")
    private Group group;

    // setter/getter 省略
}

@Entity
@Table(name = "T_GROUP")
public class Group {
    
    @Id
    @Column(name = "group_id")
    private int id;

    private Stirng name;

    @OneToMany(cascade = CascadeType.ALL, mappedby = "group")
    private List<Member> members;

    // setter/getter 省略
}
persistence.xmlファイルの編集
  • JPA仕様で定められた設定ファイル
    • EJBコンテナを使用しない場合には、/META-INF/persistence.xmlに配置する
    • JPAのプロバイダクラス名、JPAプロバイダごとの固有のプロパティを設定する
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  ...
  >

  <persistence-unit name="manager1">
    <!-- JPAのプロバイダクラス名の設定 -->
    <provider>org.hibernate.ejb.HibernamePersistence</provider>
    <!-- JPAプロバイダごとの固有プロパティの設定 -->
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
      <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbc.JDBCDriver" />
      <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:hsql://localhost/sample" />
      ...
   </properties>
 </persistence-unit>
</persistence>
JPAの実行(EJBコンテナなしの場合)
  • GroupDaoImplクラス
    • EJBコンテナなしの状態でJPAを利用したクラス
    • EntityManagerを介して明示的にトランザクション管理を行う必要
    • EntityManagerFactoryを生成し、EntityManagerFactoryからEntityManagerを生成
    • メソッドでは生成したEntityManagerにトランザクションの管理を要求
    • EntityManagerに対してテーブルのCRUD処理を要求
    • 今回はGroupの参照(Read)を行うのでEntityManagerのcreateQueryメソッドを利用してQueryを生成
    • Queryのsetparameterメソッドのパラメータにnameをセットし、getResultListメソッドでMemberと関連するGroupを取得
    • createQueryの引数で指定しているSQLのような文字列は、JPQL(Java Persistence Query Language)
    • 処理の終了後、EntityManagerに対してトランザクションのコミットを要求
    • 最後にEntityManagerを解放
public class GroupDaoImpl implements GroupDao {
    // 1)EntityManagerFactoryを生成し、EntityManagerFactoryからEntityManagerを生成
    private EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
    private EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

   public List<Group> findGroupByName(String name) {
        EntityManager em = null;
        EntityTransaction tran = null;
        List<Group> groups = null;
        
        try {
            em = getEntityManger();
            tran = em.getTransaction();
            // 2)各メソッドでは生成したEntityManagerにトランザクションの管理を要求
            tran.begin();

            // 3)今回はGroupの参照(Read)を行うのでEntityManagerのcreateQueryメソッドを利用してQueryを生成
            Query query = em.createQuery("select x FROM Group x where x.name like ?1");
            query.setParameter(1, "%" + name "%");
            groups = query.getResultList();
       
            // 4)処理の終了後、EntityManagerに対してトランザクションのコミットを要求
            tran.commit();

            /* 遅延ロード回避のコードが必要 */
        } catch (Exception e) {
            /* 省略 */
        } finally {
            /* クローズ処理 */
        }
        return groups;
    }
}
SpringのJPAインテグレーション機能
  • 全章のとおり、EJBコンテナを使わないとトランザクション管理やEntityManagerの取得・解放など煩雑
    • しかし、これだけのためにEJBコンテナを導入したくはない
    • Springの提供するJPAサポート機能を使えばEJBコンテナを利用しない場合でも煩雑な処理を隠蔽してくれる
    • GroupDaoImpl, MemberDaoImplを通してSpringのJPAインテグレーション機能を解説
  • JPAサポート機能を利用するとEJBコンテナ有りの場合と同じ実装になる
    • EntityManagerをインジェクションするときには@PersistenceContextを使用
    • あとはDAOクラスのロジックに必要なデータのクエリ、永続化、削除を行うメソッドを追加するだけ
public class GroupDaoImpl implements GroupDao {
    @PersistenceContext
    private EntityManager em;

    public List<Group> findGroupByName(String name) {
        Query query = em.createQuery("select x FROM Group x where x.name like ?1");
        query.setParameter(1, "%" + name + "%");
        List<Group> groups = query.getResultList();
        /* 遅延ロード回避のコードが必要 */
        return groups;
    }

    public void insertGroup(Group group) {
        em.persist(group);
    }

    public void removeGroup(Group group) {
        em.remove(group);
    }
}
Bean定義ファイルの設定
  • JPAのサポート機能を利用するためには、Bean定義ファイルの設定が必要
    • Springが提供するクラスの登録を行う
    • EntityManagerFactoryの実装クラスをJpaTransactionManagerのプロパティを設定
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManage">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceUnitName" value="manager1" />
</bean>
  • persistenceUnitNameに設定しているmaneger1はpersistence.xmlのpersistence-unitタグのname属性の値
    • Spring3.1からはpersistence.xmlを用意しなくてもよい方法が提供
<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence" />
    <property name="packagesToScan" value="sample.biz.domain" />
</bean>
遅延ロードの問題
  • 関連するオブジェクトが必要になるまでテーブルにアクセスしない機能
    • GroupとMemberクラスを例にして解説
    • findGroupByNameでGroupクラスを取得
    • Groupクラスに含まれるMemberクラスを参照したとき、初めてテーブルにアクセスして読み込んでくる
  • JPAでは関連をOneToManyやManyToManyにした場合、デフォルトで遅延ロードを利用
    • ドメインの管理スコープが外れた場合の分離エンティティを扱ったときに問題を引き起こす
    • 例えば、普通にWebアプリケーションを設計すればプレゼン層とビジネスロジック層の間にトランザクション境界を引く
    • ドメインの管理スコープにはビジネス層とデータアクセス層だけが含まれ、プレゼン層は管理スコープ外になる
    • JSPでgroup.members[0].nameのような記述を行うと、遅延ロードしようとして例外が発生してしまう
  • 回避策
    • 遅延ロードを切る
    • Transaction Viewパターン
    • SpringのOpenEntityManagerInViewFilter
      • SpringではJPAHibernate用に、ViewでEntityMangerやSessionを扱うためのクラスが用意
      • web.xmlに設定が必要
    • Triggering Lazy Loading
    • Tarnsfar Objectを使う
/* 遅延ロードを回避するために無理矢理ロードする */
List<Group> groups = groupDao.findAll();
for (Group group : groups) {
    for (Member member : group.getMembers()) {
        member.getId();
    }
}
まとめ
  • SpringではJPAのほか、Spring JDBCHibernateなどDBアクセス手法をいろいろ提供
    • 他の手法と比較して、JPAドメインクラスのアノテーションなどプログラマに若干高いスキルが必要になる
    • ドメインの設計によってはパフォーマンスのチューニングが難しいデメリットも存在
    • JPAに精通したメンバが確保できれば開発効率が大きく向上
    • Java EEの標準であるため、他の手法と比較してAPIの安定度が高い