JPAとJTAを使ってみる(JBoss)
JBossでJPAとJTAを使ってみます。DBにはApache Derbyを使用して、一覧表示、データ登録を行います。リソース・インジェクションは利用せず、EntityManagerとUserTransactionを自分で制御します。リソース・インジェクションは別の機会に使ってみます。
ホントはApache Tomcat + JOTM + EclipseLinkで試したかったのですが、一覧表示はできるけど、何故かデータ登録がされず、原因が分からなかったので挫折しました。
使用するソフトウェア
JBossをダウンロード、セットアップ、データソースを設定
JBoss Application Server Downloadsからファイルをダウンロードして、適当なフォルダに展開します。
%JBOSS%/docs/examples/jca/derby-ds.xmlファイルを%JBOSS%/server/default/deployフォルダにコピーして、内容を編集します。
<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>ds/foo-ds</jndi-name> <connection-url>jdbc:derby://localhost:1527/foo</connection-url> <driver-class>org.apache.derby.jdbc.ClientDriver</driver-class> <user-name>APP</user-name> <password>APP</password> <min-pool-size>5</min-pool-size> <max-pool-size>20</max-pool-size> <idle-timeout-minutes>5</idle-timeout-minutes> <track-statements /> </local-tx-datasource> </datasources>
Apache Derbyをダウンロード、セットアップ、データベースとテーブルの作成
Apache Derbyからファイルをダウンロードして、適当なフォルダに展開します。
%DERBY%/bin/startNetworkServer.batを実行して、Apache Derbyを起動します。
%DERBY%/bin/ij.batを実行します。connectコマンドを使用して、データベースを作成 & 接続します。
connect 'jdbc:derby://localhost:1527/foo;create=true';
create table文を実行して、テーブルを作成します。
create table foo ( id integer primary key, name varchar(100) not null );
ついでにテストデータも登録します。
insert into foo values (1, 'one'); insert into foo values (2, 'two'); insert into foo values (3, 'three');
%DERBY%/lib/derbyclient.jarを%JBOSS%/server/default/libフォルダにコピーします。
Note: %JBOSS%/server/default/libフォルダにjarファイルを配置すると、Eclipseのビルド・パスに追加されます。
Eclipseプロジェクトを作成
fooプロジェクトをDynamic Web Projectとして作成します。Target runtimeはJBoss v5.0とします。
今回はファイルがちょっと多めになります。最終的には、次のようになります。
DatabaseUtilクラスを作成
DB操作のための便利クラスとしてfoo.DatabaseUtilクラスを作成します。次の機能を持ちます。
- EntityManagerインスタンスを取得します。
- UserTransactionインスタンスを取得します。
- 検索の結果リストを取得します(warning箇所をまとめるため)。
- 検索の単一インスタンスを取得します(warning箇所をまとめるため)。
package foo; import java.util.List; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; import javax.transaction.UserTransaction; public final class DatabaseUtil { private DatabaseUtil() { } public static EntityManager createEntityManager() { EntityManagerFactory emf = Persistence.createEntityManagerFactory("foo-pu"); EntityManager em = emf.createEntityManager(); return em; } public static UserTransaction getUserTransaction() throws NamingException { Context ctx = new InitialContext(); UserTransaction utx = (UserTransaction) ctx.lookup("UserTransaction"); return utx; } public static <E> List<E> getResultList(Query query) { return query.getResultList(); } public static <E> E getSingleResult(Query query) { return (E) query.getSingleResult(); } }
エンティティ・クラスを作成
fooテーブルを表すfoo.FooEntityクラスを作成します。
package foo; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "foo") public class FooEntity { @Id private int id; private String name; public int getId() { return this.id; } public void setId(int id) { this.id = id; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } }
DAOクラスを作成
DBの検索や登録を行うDAOクラスを作成します。SessionBeanに倣って、インターフェイスと実装クラスに分けます。
まずは、インターフェイスであるfoo.FooDaoインターフェイスを作成します。
package foo; import java.util.List; public interface FooDao { List<FooEntity> findAll(); void create(int id, String name); }
次に、実装クラスであるfoo.FooDaoImplクラスを作成します。
package foo; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; public class FooDaoImpl implements FooDao { public List<FooEntity> findAll() { EntityManager em = DatabaseUtil.createEntityManager(); try { Query query = em.createQuery("select e from " + FooEntity.class.getSimpleName() + " e"); List<FooEntity> l = DatabaseUtil.getResultList(query); return l; } finally { em.close(); } } public void create(int id, String name) { EntityManager em = DatabaseUtil.createEntityManager(); try { FooEntity foo = new FooEntity(); foo.setId(id); foo.setName(name); em.persist(foo); } finally { em.close(); } } }
一覧表示Servletクラスを作成
FooEntityリストを取得して一覧表示を行うfoo.ListServletクラスを作成します。
package foo; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @SuppressWarnings("serial") public class ListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { FooDao fooDao = new FooDaoImpl(); List<FooEntity> l = fooDao.findAll(); String html = "<html><body><table border='1'><tr><th>ID</th><th>Name</th></tr>"; for (FooEntity foo : l) { html += "<tr><td>" + foo.getId() + "</td><td>" + foo.getName() + "</td></tr>"; } html += "</table></body></html>"; resp.getWriter().write(html); } }
データ登録Servletクラス
FooEntityインスタンスを永続化して、ListServletクラスにフォワードする、foo.CreateServletクラスを作成します。
package foo; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.transaction.UserTransaction; @SuppressWarnings("serial") public class CreateServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { int id = Integer.parseInt(req.getParameter("id")); String name = req.getParameter("name"); UserTransaction utx = DatabaseUtil.getUserTransaction(); try { utx.begin(); FooDao fooDao = new FooDaoImpl(); fooDao.create(id, name); utx.commit(); } catch (Exception e) { utx.rollback(); throw e; } this.getServletContext().getRequestDispatcher("/list").forward(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("error"); } } }
persistence.xmlファイルを作成
JPAの設定情報であるpersistence.xmlファイルを作成します。ソース・フォルダにMETA-INFフォルダを作成して、そこにpersistence.xmlファイルを作成します(例: src/META-INF/persistence.xml)。
<?xml version="1.0" encoding="UTF-8"?> <persistence 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 persistence_1_0.xsd" version="1.0"> <persistence-unit name="foo-pu" transaction-type="JTA"> <jta-data-source>java:/ds/foo-ds</jta-data-source> <class>foo.FooEntity</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" /> <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup" /> </properties> </persistence-unit> </persistence>
web.xmlファイルを編集
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <servlet> <servlet-name>ListServlet</servlet-name> <servlet-class>foo.ListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ListServlet</servlet-name> <url-pattern>/list</url-pattern> </servlet-mapping> <servlet> <servlet-name>CreateServlet</servlet-name> <servlet-class>foo.CreateServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CreateServlet</servlet-name> <url-pattern>/create</url-pattern> </servlet-mapping> </web-app>
動作確認
fooプロジェクトをRun on Serverで実行します。http://localhost:8080/foo/listにアクセスして、次のように表示されれば、正常動作しています。
次に、http://localhost:8080/foo/create?id=4&name=fourにアクセスして、次のように表示されれば、正常動作しています。
データを検索して、データが登録されていることを確認してみます。
ij> select * from foo; ID |NAME -------------------------- 1 |one 2 |two 3 |three 4 |four
動作確認(ロールバック)
次にUserTransaction#rollback()によって、ちゃんとロールバックされることを確認します。
CreateServletクラスのutx.commit()の部分をutx.rollback()に変更します。これで、正常処理でもロールバックするようになります。
CreateServletクラスを変更したらJBossを再起動します(コンテキストのリロードでも良いです)。http://localhost:8080/foo/create?id=5&name=fiveにアクセスして、次のようにデータが登録されなければ、正常動作しています。
データを検索して、データが登録されていないことを確認します。
ij> select * from foo; ID |NAME -------------------------- 1 |one 2 |two 3 |three 4 |four