似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

JPA + EJB + JSF による Web アプリケーション(その 3)

そろそろ現実に帰って Java EE の記事を書きます(汗)。

Model の Entity を JPA で、Business logic を EJB で実装します。JPA は前にも説明した通り永続化と O/R マッピングですが、EJB をことさらに使う理由としては、WildFly (やその他 Java EE アプリケーションサーバ)が EJB コンテナを持っていることにより、アノテーション一発でロジックを呼び出せるという利点があるからです。

以下、ソースコードを書いていきます。ファイルの配置については下図を参照のこと。


まずは Entity を作ります。

package jp.mydns.akanekodou.entity;

import java.io.Serializable;

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

@SuppressWarnings("serial")
@Entity
public class District implements Serializable {
    @Id
    @GeneratedValue
    private int id;
    private String name;

    public District() { }

    public District(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package jp.mydns.akanekodou.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.ManyToOne;

@SuppressWarnings("serial")
@Entity
public class Prefecture implements Serializable {
    @Id
    @GeneratedValue
    private int id;
    private String name;
    @ManyToOne
    private District district;

    public Prefecture() { }

    public Prefecture(
        String name,
        District district
    ) {
        this.name = name;
        this.district = district;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public District getDistrict() {
        return district;
    }

    public void setDistrict(District district) {
        this.district = district;
    }
}
package jp.mydns.akanekodou.entity;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.ManyToOne;

@SuppressWarnings("serial")
@Entity
public class City implements Serializable {
    @Id
    @GeneratedValue
    private int id;
    private String name;
    @ManyToOne
    private Prefecture prefecture;
    private Date designated;
    private double area;
    private int population;

    public City() { }

    public City(
        String name,
        Prefecture prefecture,
        Date designated,
        double area,
        int population
    ) {
        this.name = name;
        this.prefecture = prefecture;
        this.designated = designated;
        this.area = area;
        this.population = population;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Prefecture getPrefecture() {
        return prefecture;
    }

    public void setPrefecture(Prefecture prefecture) {
        this.prefecture = prefecture;
    }

    public Date getDesignated() {
        return designated;
    }

    public void setDesignated(Date designated) {
        this.designated = designated;
    }

    public double getArea() {
        return area;
    }

    public void setArea(double area) {
        this.area = area;
    }

    public int getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }
}

JPA がこれらを認識できるように persistence.xml に記述します。

<?xml version="1.0"?>
<persistence
  xmlns="http://xmlns.jcp.org/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
    http://xmlns.jcp.org/xml/ns/persistence
    http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
  version="2.1">
  <persistence-unit name="cityManager">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <jta-data-source>java:jboss/datasources/PostgreSQL/mydb</jta-data-source>
    <class>jp.mydns.akanekodou.entity.City</class>
    <class>jp.mydns.akanekodou.entity.Prefecture</class>
    <class>jp.mydns.akanekodou.entity.District</class>
    <properties>
      <property name="hibernate.dialect"
        value="org.hibernate.dialect.PostgreSQLDialect" />
    </properties>
  </persistence-unit>
</persistence>

続いて EJB で Business logic を書きます。EJB では原則としてまずインターフェースを用意します。

package jp.mydns.akanekodou.dao;

import javax.ejb.Local;

import java.util.List;

import jp.mydns.akanekodou.entity.City;

@Local
public interface CityDAO {
    List<City> all();
    City find(int id);
}

今回は EJB とそれを利用するプログラムを両方とも同じサーバに配置するので Local Bean です。別々のサーバに配置する場合は Remote Bean になります。

インターフェースですので実装を書く必要がありますね。

package jp.mydns.akanekodou.dao;

import javax.ejb.Stateless;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import java.util.List;

import jp.mydns.akanekodou.entity.City;

@Stateless
public class CityDAOImpl implements CityDAO {
    @PersistenceContext
    private EntityManager manager;

    @Override
    public List<City> all() {
        Query query = manager.createQuery("from City");
        @SuppressWarnings("unchecked")
        List<City> result = query.getResultList();
        return result;
    }

    @Override
    public City find(int id) {
        return manager.find(City.class, id);
    }
}

EntityManagerアノテーション一発で生成出来るのも Java EE アプリケーションサーバの強みです。非常に記述が簡潔になります。Session Bean には Staleless なものと Staleful なものがありますが、セッションに渡って連続的な処理をする場合は Staleful、一メソッド内で完結する処理なら Staleless と覚えると良いでしょう。

なお今回はインターフェースと実装を分けましたが、EJB 3.1 からは特定のインターフェース(java.io.Serializable, java.io.Externalizable 及び javax.ejb パッケージに含まれるインターフェース)以外を implements していないことと LocalBean アノテーションを付けることを条件にインターフェースを省略できるようです。なので、実は今回について言えばインターフェースを省略してこういう書き方もできる。

package jp.mydns.akanekodou.dao;

import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import java.util.List;

import jp.mydns.akanekodou.entity.City;

@Stateless
@LocalBean
public class CityDAO {
    @PersistenceContext
    private EntityManager manager;

    public List<City> all() {
        Query query = manager.createQuery("from City");
        @SuppressWarnings("unchecked")
        List<City> result = query.getResultList();
        return result;
    }

    public City find(int id) {
        return manager.find(City.class, id);
    }
}

試したらちゃんと動きました。でも迷ったらインターフェースと実装に分けて書く方が確実です。

次回は JSF 側のプロジェクトを作ります。