JPAとJTAを使ってみる(JBoss)

JBossJPAJTAを使ってみます。DBにはApache Derbyを使用して、一覧表示、データ登録を行います。リソース・インジェクションは利用せず、EntityManagerとUserTransactionを自分で制御します。リソース・インジェクションは別の機会に使ってみます。

ホントはApache Tomcat + JOTM + EclipseLinkで試したかったのですが、一覧表示はできるけど、何故かデータ登録がされず、原因が分からなかったので挫折しました。

使用するソフトウェア

Note: Java SE 6、Eclipseのセットアップは説明しません。

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ファイルを編集

web.xmlServletクラスの設定を記述します。

<?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