2013年6月27日木曜日

AWS Elastic Beanstalk + MySQL + JPA で WEB アプリを開発する(3)

このシリーズの3回目では、Eclipselink を使ってAWSのデータベースサービス(RDS)にアクセスする簡単なアプリケーションを作成します。1回目のポストで作成したプロジェクトをそのまま使用します。

SpringMVC、Eclipselink に必要なライブラリを設定する
Spring を初めて使う人にとって、最初に面倒に感じるのは Spring を使える環境を整えることだと思います。ここではその手間をのぞくために、AWS Java Web Project の作成ウィザードで選択することができるサンプルプロジェクト(Travel Log)を利用します。このプロジェクトを作成すると Spring を使うために必要なライブラリが WebContent/WEB-INF/lib に含まれています(少しバージョンは古いです)。今回のアプリでは使用しないものや余分なものも含まれていますが、AWS の様々なサービスを使うために必要なものもそろっているので、これをそのまま(SimpleDB 関連は除く)コピーして使用します。これに Eclipselink と MySQL 関連の JAR を追加します。

最終的な、awstest01 プロジェクトの WebContent/WEB-INF/lib の内容は以下のようになります。


時間をかけずに、アプリを完成したかったので、以前のポスト「eclipselink を使う :(1)エンティティの作成」や「eclipselink を使う :(2)CRUD操作用クラスの作成」で紹介したエンティティやデータアクセスクラスを使用します。ここで紹介しなかったソースコードはそちらを参照してください

persistence.xml の作成
プロジェクトの src/META-INF にpersistence.xml を作成します:
<?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">
       <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
       <!-- All persistence classes must be listed -->
       <class>awstest01.db.entity.Contact</class>
       <class>awstest01.db.entity.Address</class>
       <class>awstest01.db.entity.Phone</class>
       <properties>
             <property name="javax.persistence.target-database" value="MySQL"/>
           <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
           <property name="javax.persistence.jdbc.url"
               value="jdbc:mysql://XXXXXXXXXX-northeast-1.rds.amazonaws.com:3306/demo" />
           <property name="javax.persistence.jdbc.user" value="username" />
           <property name="javax.persistence.jdbc.password" value="password" />

           <property name="eclipselink.ddl-generation" value="update-tables" />
           <property name="eclipselink.ddl-generation.output-mode" value="database" />
            <property name="eclipselink.jdbc.read-connections.min" value="1" />
            <property name="eclipselink.jdbc.write-connections.min" value="1" />
            <property name="eclipselink.jdbc.batch-writing" value="JDBC" />
            <!-- Logging -->
            <!-- property name="eclipselink.logging.level" value="FINE" / -->
            <property name="eclipselink.logging.timestamp" value="false" />
            <property name="eclipselink.logging.session" value="false" />
            <property name="eclipselink.logging.thread" value="false" />

         </properties>
    </persistence-unit>

</persistence>

RCP アプリケーション用に作成したものをそのままコピーしたので、WEB環境に合わせて変更すべきですが、データベースの url とユーザー名、パスワード以外はとりあえずローカル環境の場合と同じにしておきます。 URL、ユーザー名、パスワードは1回目のポストで作成したデータベース環境の、DBエンドポイント、マスターユーザー名、パスワードを使用します。

エンティティクラスの作成
今回使用する Contact クラスを awstest01.db.entity パッケージに作成します:
@Entity
@Table(schema="demo", name="contact")
@NamedQueries({
    @NamedQuery(
            name="findContactByUsername",
            query="SELECT e FROM Contact e WHERE e.username=:username"
        )
    ,
    ...
})
public class Contact extends AbstractPersistentEntity {

    private static final long serialVersionUID = 1L;

    private String simei;
    private String yomi;
    private ContactType contactType;
    private String company;
    private String seibetu;
    private Date seinengappi;
    
    private String title;
    private String jobTitle;
    private String note;
    private String jpegString;
    
    private String username;
    private String googleid;
    
    private Map<String, Address> addresses;
    private Map<String, Phone> phones;

    public Contact() {
        contactType = ContactType.None;
        seibetu = "";
        seinengappi = Calendar.getInstance().getTime();
        simei = "";
        yomi = "";
        company = "";
        title = "";
        jobTitle = "";
        note = "";
        username="";
        googleid="";
        jpegString="";
        addresses = new HashMap<String, Address>();
        phones = new HashMap<String, Phone>();
    }
    //..
    
    public Contact(Contact c) {
        contactType = c.getContactType();
        seibetu = c.getSeibetu();
        seinengappi = c.getSeinengappi();

        simei = c.getSimei()==null ? "" : c.getSimei();
        yomi = c.getYomi()==null ? "" : c.getYomi();
        company = c.getCompany();
        title = c.getTitle();
        jobTitle = c.getJobTitle();
        note = c.getNote();
        username =c.getUsername();
        googleid = c.getGoogleid();
        jpegString = c.getJpegString();
        addresses = new HashMap<String, Address>();
        //addresses = c.getAddresses();
        phones = new HashMap<String, Phone>();
        //phones = c.getPhones();
    }

    @OneToMany(cascade=CascadeType.ALL)
    @MapKeyClass(String.class)
    @MapKeyColumn(table="CONTACT_ADDRESS")
    @JoinTable(name="CONTACT_ADDRESS")
    public Map<String, Address> getAddresses() {
        return addresses;
    }

    public void setAddresses(Map<String, Address> addresses) {
        this.addresses = addresses; 
    }

    @OneToMany(cascade=CascadeType.ALL)
    @MapKeyClass(String.class)
    @MapKeyColumn(table="CONTACT_PHONE")
    @JoinTable(name="CONTACT_PHONE")
    public Map<String, Phone> getPhones() {
        return phones;
    }

    public void setPhones(Map<String, Phone> phones) {
        this.phones = phones;
    }
    //... getter, setter 
    
    @Column(length=20)
    public String getSimei() {
        return simei;
    }

    public void setSimei(String simei) {
        this.simei = simei;
    }

    @Column(length=30)
    public String getYomi() {
        return yomi;
    }

    public void setYomi(String yomi) {
        this.yomi = yomi;
    }

    @ObjectTypeConverter(name = "contactType",
            objectType = ContactType.class,
            dataType = String.class,
            conversionValues = {
                @ConversionValue(objectValue = "SYS_FAMILY", dataValue = "家族・親戚"),
                @ConversionValue(objectValue = "SYS_FRIENDS", dataValue = "友人・知人"),
                @ConversionValue(objectValue = "SIGOTO", dataValue = "仕事"),
                @ConversionValue(objectValue = "DOURYOU", dataValue = "同僚"),
                @ConversionValue(objectValue = "KOKYAKU", dataValue = "顧客"),
                @ConversionValue(objectValue = "SEIKATU", dataValue = "生活"),
                @ConversionValue(objectValue = "None", dataValue = "")
            }
    )
    @Convert("contactType")
    @Column(length=10)
    public ContactType getContactType() {
        return contactType;
    }

    public void setContactType(ContactType type) {
        this.contactType = type;
    }

    ...

}


データアクセスクラスの作成
Contact クラスを操作するいわゆる DAO クラスの ContactHomeを awstest01.db.home パッケージに作成します:
@Repository
public class ContactHome extends AbstractEntityHome<Contact> implements IContactHome {

    private AddressHome addressHome;
    private PhoneHome phoneHome;

    public ContactHome() {
        super(Contact.class);
    }
    
    public ContactHome(Class<Contact> type) {
        super(type);
    }

    public ContactHome(Class<Contact> type, AddressHome addressHome,
            PhoneHome phoneHome) {
        this(type);
        this.addressHome = addressHome;
        this.phoneHome = phoneHome;
    }

    @Override
    public void fireInputChange() {
        getListeners().firePropertyChange(INPUT_CHANGE, null, null);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public List<Contact> findByUser(String username) {
        entityList = LocalJPA.getEntityManager()
        .createNamedQuery("findContactByUsername")
        .setParameter("username", username)
        .getResultList();
        return entityList;
    }
    
    ...
    

}

このアプリで使用するメソッドは findByUser( ) だけです。


コントローラクラスの作成
ビューに対してモデルを受け渡す、コントローラクラス ContactListController を awstest01.web パッケージに作成します:
public class ContactListController implements Controller {

    protected final Log logger = LogFactory.getLog(getClass());
    
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        ContactHome home = new ContactHome();
        List<Contact> contacts = home.findByUser("username");
 
        return new ModelAndView("contactList", "contacts", contacts);
    }
}

上のコードでは、spring の DI を使わずに直接 ContactHome インスタンスを作成しています。ContactHome#findByUser( ) メソッドにより、指定したユーザーの連絡先リストを取得します。戻り値として ModelAndView のインスタンスを返します。これにより、このメソッドの実行後、"contactList" 論理名で指定されるビュー(実際は contactList.jsp)を表示することを指定します。さらに第2、第3の引数により、取得した連絡先リスト contacts を contactList.jsp で "contacts" という名前で参照できるようにします。

ビューの作成
WebContent/WEB-INF/jsp フォルダに連絡先一覧を表示する contactList.jsp を作成します:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
    <h3>AWS + Spring + JPA サンプル:</h3>
    <table border=1 width="80%" align=center>
    <caption>連絡先一覧</caption>
    <tr bgcolor="#eeeeee">
        <th>氏名</th>
        <th>よみ</th>
        <th>種別</th>
    </tr>
    <c:forEach items="${contacts}" var="contact">
        <tr>
            <td><c:out value="${contact.simei}"></c:out></td>
            <td><c:out value="${contact.yomi}"></c:out></td>
            <td><c:out value="${contact.contactType}"></c:out></td>
        </tr>
    </c:forEach>
    </table>
</body>
</html>

cotactList.jsp では items = "${contacts}" でコントローラから連絡先一覧を受け取ります。 そして forEach ループ内でそれぞれの連絡先の氏名、よみ、種別を出力します。

モデル、ビュー、コントローラを連携するための設定を行う
WebContent/WEB-INF/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"
 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">
  <display-name>awstest01</display-name>
  <servlet>
    <servlet-name>springapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet> 
  <servlet-mapping>
    <servlet-name>springapp</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

上のサーブレットマッピングで、このアプリケーションの /*.htm のURLパターンは全て DispatcherServlet によって制御されます。

WebContent/WEB-INF/springapp-servlet.xml を作成します:
<?xml version="1.0" encoding="UTF-8"?>
 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
  <!-- the application context definition for the springapp DispatcherServlet -->
 
  <bean name="/contactList.htm" class="awstest01.web.ContactListController"/>
 
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>        
    </bean>
</beans>

このファイルには DispatcherServlet で使用される bean 定義を記述します。 springapp-servlet.xml という名前は web.xml で、ディスパッチャのサーブレット名を springapp としたことに対応します(SpringMVCの標準ネーミング規約)。上の bean 定義により、/contactList.htm へのリクエストはディスパッチャで処理されて対応する ConatactListController が実行されます。このコントローラーは ContactHome を使って連絡先リストを取得し、それを contactList.jsp へ渡します。ビューリゾルバーはコントローラが指定した論理名 "contactList" から /WEB-INF/jsp/contactList.jsp を決定します。

更新されたアプリを再度デプロイする
プロジェクトを右クリックして、Amazon Web Services > Deploy to AWS Elastic Beanstalk.. を選択します。 今回は初回に作成したアプリケーション環境 app01 を選択します。しばらくして前回と同じ画面 (index.jsp) が表示されます。 リクエストURLとして、・・・/contactList.htm を指定すると次の画面が表示されます。



0 件のコメント: