JSF2.0+JPA2.0+eclipse 構築編:JPAを動かす

前回までのプロジェクト構成にミスがありました。
src/java/test下のパッケージが「sandbo.***」になっていました。
正しくは「sandbox.***」です。

さてJPAに入りましょう。

persistence.xmlを書かないといけません。なんかトラブる予感がするなぁ。

DBの選定

DBに何を使うかを迷いますが、組み込みDerbyを使うことにします。
理由は

  • glassfish-embeddedにバンドルされているから
  • DBプロセスを起動する手間が省けるから
  • ローカルPC上で完結するから

です。

実際の業務ではDBは製品を使うことが多いですが、問題になるのは複数人で開発するときの干渉です。
出来れば、開発者にローカルな環境がほしいわけで、組み込みDerbyならここが解決します。

これの問題点は「テーブル構造の変更をどうやって共有するか」という点ですが、今回は

  • エンティティクラスの構造をそのままテーブル構造に反映するようにする
  • エンティティクラスのソースはチームで共有する
  • JPA起動時にテーブルを毎回再生成する

という構成を念頭に置いてプロジェクトを構成します。

persistence.xmlを作る

JPAを使う上で必要な設定ファイルはpersistence.xmlです。

presistence.xmlを格納するフォルダはどこに置くか、というのに迷います。
組み込みDerbyを本番でもリリースするなら本番用のソースフォルダに置くのが自然ですが、あまりないことだと思います。なのでテスト用のソースフォルダである「src/test/java」の下に配置しましょう。

また「Javaのソースファイルとリソースファイルを分ける」構成はmavenで採用されているためかよく見る構成ですが、少なくともテスト用ソースフォルダの下は分ける必要はないでしょう。
というわけで今回は「src/test/java/META-INF」の下に作ります。

中身はこうなりました。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/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">
	<persistence-unit name="pu" transaction-type="RESOURCE_LOCAL">
		<exclude-unlisted-classes>false</exclude-unlisted-classes>
		<properties>
			<property name="javax.persistence.jdbc.url" value="jdbc:derby:db/test;create=true"/>
			<property name="javax.persistence.jdbc.user" value="user"/>
			<property name="javax.persistence.jdbc.password" value="pwd"/>
			<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
			<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
			<property name="eclipselink.ddl-generation.output-mode" value="database"/>
			<property name="eclipselink.logging.level.sql" value="FINE"/>
		</properties>
	</persistence-unit>
</persistence>

設定内容のポイント。

  • 今回はDataSourceは使いません。ローカルで開発するならこれで充分でしょう。
  • DerbyのDBファイルはプロジェクト直下の「db」というフォルダに置きます。
  • JPA起動時にテーブルを再作成します。
  • SQL文をログに出力します。

JPA2.0からはJDBCの接続情報を設定するためのプロパティ名が標準化されています。

エンティティを作る

JPAの動作確認用に簡単なエンティティを作りましょう。
パッケージは「sandbox.entity」にします。
定番「Employee」クラスを作ります。
こんな↓感じ。

package sandbox.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @author jabaraster
 */
@Entity
public class Employee implements Serializable {
    private static final long serialVersionUID = -8288070485538345134L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long              id;

    @Column(length = 120, nullable = false)
    private String            name;

    /**
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * @return the name
     */
    public String getName() {
        return this.name;
    }

    /**
     * @param pName the name to set
     */
    public void setName(final String pName) {
        this.name = pName;
    }

}

とりあえず、テスト用なんで簡単に作っておきます。

EmployeeServiceを作る

Employeeエンティティを操作するためのDAOクラスを作りましょう。
JavaEEらしく、EntityManagerを@PersistenceContextアノテーションで修飾してGlassfishにインジェクトしてもらいましょう。
これがやりたいからJavaEE6を使うわけですから。

とりあえず、INSERTと全件SELECTを作ります。
こんな感じ。

package sandbox.bean;

import java.util.List;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import sandbox.entity.Employee;

/**
 * @author jabaraster
 */
@ManagedBean
@RequestScoped
public class EmployeeService {

    @PersistenceContext()
    private EntityManager em;

    private String        name;

    /**
     * @return
     */
    public List<Employee> getAll() {
        final CriteriaBuilder builder = this.em.getCriteriaBuilder();
        final CriteriaQuery<Employee> query = builder.createQuery(Employee.class);
        query.from(Employee.class);
        return this.em.createQuery(query).getResultList();

        // JPQLならこう↓
        // return this.em.createQuery("select e from Employee e", Employee.class).getResultList();
    }

    /**
     * @return the name
     */
    public String getName() {
        return this.name;
    }

    /**
     * 
     */
    public void insert() {
        final Employee e = new Employee();
        e.setName(this.name);
        this.em.persist(e);
    }

    /**
     * @param pName the name to set
     */
    public void setName(final String pName) {
        this.name = pName;
    }
}

getAllはあえてCriteriaAPIを使ってみましたが全件取得なら素直にJPQLを使った方が圧倒的に楽ですw

XHTMLを作る

ふう。あと一息。
名前を入力しつつ、その下に全件表示するemployee-list.xhtmlを書きましょう。
置き場所はWebContent直下に。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html" 
    xmlns:ui="http://java.sun.com/jsf/facelets"
    >
<head>
    <meta charset="UTF-8" />
    <title>Employees</title>
</head>
<body>
    <h:form>
        <h:inputText value="#{employeeService.name}" />
        <h:commandButton value="INSERT" action="#{employeeService.insert}" />
    </h:form>
    <hr />
    <fieldset>
        <legend>Employees</legend>
        <table>
            <tr jsfc="ui:repeat" value="#{employeeService.all}" var="e">
                <td>#{e.id}</td><td>#{e.name}</td>
            </tr>
        </table>
    </fieldset>
</body>
</html>

h:がいるときといらないときの差が分からない・・・XHTMLはもっと勉強がいりそうです。

動作させる・・・が!

さて準備が整いました。
glassfish-embeddedを起動して以下のURLにアクセスしましょう。
http://localhost:18080/employee-list.xhtml

華麗に画面が表示される・・・はずがあえなく500が返ってきてます。

eclipseのコンソールには例外のスタックトレースが。
全部載せるとあまりにも長いので本質的(と思われる)部分のみ掲載。

java.lang.IllegalStateException: Unable to retrieve EntityManagerFactory for unitName null
	at com.sun.enterprise.container.common.impl.EntityManagerWrapper.init(EntityManagerWrapper.java:121)
	at com.sun.enterprise.container.common.impl.EntityManagerWrapper._getDelegate(EntityManagerWrapper.java:162)
	at com.sun.enterprise.container.common.impl.EntityManagerWrapper.getCriteriaBuilder(EntityManagerWrapper.java:883)
	at sandbox.bean.EmployeeService.getAll(EmployeeService.java:30)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at javax.el.BeanELResolver.getValue(BeanELResolver.java:302)
	at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:175)
...

次は

もちろん例外「Unable to retrieve EntityManagerFactory for unitName null」を解消してJPAを動かすことです。
長い戦いになりそうだ・・・