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を動かすことです。
長い戦いになりそうだ・・・