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

これまでの総仕上げとして、一からテーブルを作って JPA + EJB + JSF で Web アプリケーションを作成してみましょう。

テーブルの作成

今回は RDBMS として PostgreSQL を使います。

CREATE TABLE district (
    id   INT         PRIMARY KEY,
    name VARCHAR(30)
);

INSERT INTO district ( id, name ) VALUES (1,'北海道');
INSERT INTO district ( id, name ) VALUES (2,'東北');
INSERT INTO district ( id, name ) VALUES (3,'関東');
INSERT INTO district ( id, name ) VALUES (4,'中部');
INSERT INTO district ( id, name ) VALUES (5,'近畿');
INSERT INTO district ( id, name ) VALUES (6,'中国');
INSERT INTO district ( id, name ) VALUES (7,'四国');
INSERT INTO district ( id, name ) VALUES (8,'九州');
INSERT INTO district ( id, name ) VALUES (9,'沖縄');

CREATE TABLE city (
    id          INT          PRIMARY KEY,
    name        VARCHAR(30),
    pref        VARCHAR(30),
    designated  DATE,
    area        DECIMAL(7,2),
    population  INT,
    district_id INT          REFERENCES district(id)
);

INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (1,'札幌市','北海道','1972-04-01',1121.12,1921245,1);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (2,'仙台市','宮城県','1989-04-01',785.85,1049493,2);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (3,'さいたま市','埼玉県','2003-04-01',217.49,1229479,3);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (4,'千葉市','千葉県','1992-04-01',272.08,963120,3);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (5,'横浜市','神奈川県','1956-09-01',437.38,3691693,3);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (6,'川崎市','神奈川県','1972-04-01',142.7,1430773,3);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (7,'相模原市','神奈川県','2010-04-01',328.83,719412,3);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (8,'新潟市','新潟県','2007-04-01',726.1,812458,4);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (9,'静岡市','静岡県','2005-04-01',1411.85,714513,4);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (10,'浜松市','静岡県','2007-04-01',1558.04,798924,4);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (11,'名古屋市','愛知県','1956-09-01',326.43,2266517,4);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (12,'京都市','京都府','1956-09-01',827.9,1473416,5);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (13,'大阪市','大阪府','1956-09-01',223.0,2670579,5);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (14,'堺市','大阪府','2006-04-01',149.99,842685,5);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (15,'神戸市','兵庫県','1956-09-01',552.26,1544496,5);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (16,'岡山市','岡山県','2009-04-01',789.92,710913,6);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (17,'広島市','広島県','1980-04-01',905.41,1177711,6);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (18,'北九州市','福岡県','1963-04-01',488.78,974287,8);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (19,'福岡市','福岡県','1972-04-01',341.7,1479433,8);
INSERT INTO city ( id, name, pref, designated, area, population, district_id ) VALUES (20,'熊本市','熊本県','2012-04-01',389.54,736010,8);

エンティティの作成

package jp.mydns.akanekodou.entity;

import java.io.Serializable;

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

@Entity
public class District implements Serializable {
    private static final long serialVersionUID = 1L;

    @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 java.util.Date;

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

@Entity
public class City implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private int id;
    private String name;
    private String pref;
    private Date designated;
    private double area;
    private int population;
    @ManyToOne
    private District district;

    public City() { }

    public City(
        String name,
        String pref,
        Date designated,
        double area,
        int population,
        District district
    ) {
        this.name = name;
        this.pref = pref;
        this.designated = designated;
        this.area = area;
        this.population = population;
        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 String getPref() {
        return pref;
    }
    public void setPref(String pref) {
        this.pref = pref;
    }
    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;
    }
    public District getDistrict() {
        return district;
    }
    public void setDistrict(District district) {
        this.district = district;
    }
}

今回は city テーブルの district_id カラムが district テーブルの id カラムを参照している(命名規則に従っている)ので、JoinColumn アノテーションは省略できます。

<?xml version="1.0"?>
<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
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
  version="2.0">
  <persistence-unit name="cityManager">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/pgsql/mydb</jta-data-source>
    <class>jp.mydns.akanekodou.entity.City</class>
    <class>jp.mydns.akanekodou.entity.District</class>
    <properties>
      <property name="hibernate.dialect"
        value="org.hibernate.dialect.PostgreSQLDialect" />
    </properties>
  </persistence-unit>
</persistence>

必要な data-source はあらかじめ定義済みであるものとします。

DAO 層の実装(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);
}
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);
    }
}

管理 Bean の作成

package jp.mydns.akanekodou;

import javax.ejb.EJB;
import javax.annotation.PostConstruct;

import javax.faces.bean.ManagedBean;
import javax.faces.component.html.HtmlInputHidden;

import java.util.List;


import jp.mydns.akanekodou.dao.CityDAO;
import jp.mydns.akanekodou.entity.City;

@ManagedBean
public class MajorCity {
    @EJB
    private CityDAO dao;
    private HtmlInputHidden id;
    private List<City> items;
    private City item;

    @PostConstruct
    private void init() {
        items = dao.all();
    }

    public HtmlInputHidden getId() {
        return id;
    }
    public void setId(HtmlInputHidden id) {
        this.id = id;
    }

    public List<City> getItems() {
        return items;
    }
    public City getItem() {
        return item;
    }

    public String detail() {
        int id = (Integer)this.id.getValue();
        item = dao.find(id);
        return "detail";
    }
}

Facelets と CSS の作成

list.xhtml

<!DOCTYPE html PUBLIC
  "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
<h:head>
  <title>日本の政令指定都市一覧</title>
  <h:outputStylesheet library="css" name="list.css" />
</h:head>
<h:body>
<h:dataTable var="item" value="#{majorCity.items}" columnClasses="no,pref,name,button" rowClasses="tr0,tr1">
  <f:facet name="caption">日本の政令指定都市一覧</f:facet>
  <h:column>
    <f:facet name="header">No.</f:facet>
    <h:outputText value="#{item.id}" />
  </h:column>
  <h:column>
    <f:facet name="header">都道府県</f:facet>
    <h:outputText value="#{item.pref}" />
  </h:column>
  <h:column>
    <f:facet name="header">都市名</f:facet>
    <h:outputText value="#{item.name}" />
  </h:column>
  <h:column>
    <f:facet name="header"></f:facet>
    <h:form>
      <div>
        <h:commandButton action="#{majorCity.detail}" value="詳細" />
        <h:inputHidden value="#{item.id}" binding="#{majorCity.id}" />
      </div>
    </h:form>
  </h:column>
</h:dataTable>
</h:body>
</html>

h:dataTablecolumnClasses 属性は、表の列ごとの class 属性値を指定します。また rowClasses は行ごと(tr 要素)の class 属性値を指定します。複数書いておくことで行ごとに tr0, tr1 が繰り返し指定されます。

detail.xhtml

<!DOCTYPE html PUBLIC
  "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
<h:head>
  <title>#{majorCity.item.name}の詳細</title>
  <h:outputStylesheet library="css" name="detail.css" />
</h:head>
<h:body>
  <h:panelGrid columns="2" columnClasses="header,">
    <f:facet name="caption">#{majorCity.item.name}のデータ</f:facet>

    <h:outputText value="指定日" />
    <h:outputText value="#{majorCity.item.designated}">
      <f:convertDateTime pattern="yyyy 年 M 月 d 日" />
    </h:outputText>

    <h:outputText value="地方" />
    <h:outputText value="#{majorCity.item.district.name}" />

    <h:outputText value="都道府県" />
    <h:outputText value="#{majorCity.item.pref}" />

    <h:outputText value="面積" />
    <h:panelGroup>
      <h:outputText value="#{majorCity.item.area}">
        <f:convertNumber />
      </h:outputText>km&sup2;
    </h:panelGroup>

    <h:outputText value="人口" />
    <h:panelGroup>
      <h:outputText value="#{majorCity.item.population}">
        <f:convertNumber />
      </h:outputText></h:panelGroup>
  </h:panelGrid>
  <p><h:link value="一覧に戻る" outcome="list" /></p>
</h:body>
</html>

f:convertDateTime で日付のフォーマット、f:convertNumber で数値のフォーマットを行っています。

resources/css/list.css

table {
    margin : auto;
    border : 1px outset black
}

caption {
    font-size : 20pt;
    color : #008b8b
}

thead {
    background-color : #008b8b;
    color : #e6e6fa;
    font-size : 14pt
}

th { border : 1px inset black }

tr.tr0 { background-color : #b0e0e6 }
tr.tr1 { background-color : #e6e6fa }

td {
    text-align : left;
    border : 1px inset black
}

.no { width : 30px }
.pref { width : 100px }
.name { width : 120px }
.button {
    width : 80px;
    text-align : center;
}

resources/css/detail.css

table { margin : auto }

caption {
    font-size : 20pt;
    color : #6a5acd
}

td {
    width : 180px;
    background-color : #e6e6fa;
    color : #6a5acd;
    font-size : 14pt
}

td.header {
    width : auto;
    background-color : #6a5acd;
    color : #e6e6fa;
    font-weight : bold
}

p { text-align : center }

この他、list.jsf にリダイレクトするためのダミーの index.jsp と web.xml の修正(filter や welcome-file の設定など)をして完成です。