Quantcast
Channel: 行きあたりばったりエンジニアの日記
Viewing all 88 articles
Browse latest View live

IntelliJ IDEAでJAX-RSが捗る!

$
0
0

小ネタです。
IntelliJ IDEA 14.1.1 Ultimate Editionを前提としています。

最近は、意識してIntelliJ IDEA+GradleでJava EEやってます。
いつもはNetBeansMavenを使うことが多くて、これが一番手軽なんですが、IDEAは補完がとにかく強力で、「ここも補完が効くのか!」とビックリさせられます。

その1つが今回の話なんですが、JAX-RSでリソースメソッド作るとき、例えばこんなコードがあります。

@GET@Produces("application/json")
@Path("{name}")
public Response hello(@PathParam("name") String name) {
    // 処理
}

ちょいと面倒だと感じるのが、「"application/json"」の部分と、「name」と3回も指定しなきゃいけない部分です。

前者は「MediaType.APPLICATION_JSON」という定数で書くこともできますが、ここはIDEAの補完が効きます。
f:id:MasatoshiTada:20150508223926g:plain
()の中でCtrl+Spaceすれば候補が出てくるので、あとは選択してEnterするだけ。
「MediaType.APPLICATION_JSON」とするよりもタイプ数が圧倒的に少ないので、こっちの方が楽です。

後者は、@Path("{name}")さえ自力で書けば、あとの@PathParamとメソッド引数名はCtrl+Spaceで補完できます。
f:id:MasatoshiTada:20150508223945g:plain
うーん、便利。しかもJAX-RSで出来るということは、MVC 1.0でも出来る訳です。

NetBeansでは、上記のような補完は出来ませんでした。
IDEAは本当に色んなところで補完が効くので、思いついたら即座にCtrl+Spaceしてみてください。驚くような補完が出来ることがあります。

簡単ですが、今日はここまで。


※追記
Spring MVCでは、同様の補完は効きませんでした。
おそらく、そのうち同様の補完が出来るように改良されると思います。


jBatch(Java EE 7)をNetBeans+GlassFish+Mavenではじめました

$
0
0

jBatchとは?

Java EE 7から新たに加わった、バッチ処理フレームワークの仕様です。
EE 6までは、Java EE標準仕様にバッチは入っていなかったのですが、EE 7から加わりました。
正式名称は「Batch Application for the Java Platform」で、公式?の略称が「jBatch」です。
Spring Frameworkには、以前から「Spring Batch」というものがあったのですが、これを基にjBatchの仕様が策定されたようです。
また、Spring Batch自身も、jBatch準拠になっているようです。

jBatchの実装

jBatchに限らず、Java EEは仕様のみ(ほとんどインターフェイスアノテーション・例外・enum)なので、jBatchを使うためには、それに対応する「実装」が必要です。
僕が調べた限りでは、今のところ実装は下記の3つです。

  1. RI (GlassFishに内包。特別な名前はついていないようです)
  2. JBeret (WildFlyに内包)
  3. Spring Batch

jBatchに関する情報

まず最初に見るべき情報

Java Day Tokyo 2015の上妻さんの発表「jBatch実践入門」を見ることをお勧めします。
PDFおよびYouTubeで見ることができます。とても分かりやすくまとまっている、素晴らしい資料・発表です。
http://www.oracle.com/technetwork/jp/ondemand/online2015-javaday-2511676-ja.html

公式情報(英語)

僕もまだしっかりとは目を通せていないのですが、JSRとチュートリアルは読んだ方がよいでしょう。
JSR 352 JSR-000352 Batch Applications for the Java Platform 1.0, Revision A Maintenance Release for Evaluation
Java EE 7 Tutorial 55 Batch Processing (Release 7)
あと、洋書だとArun Guptaさんの「Java EE 7 Essentials」にjBatchの章があります。僕はKindle版を持ってます。
Amazon.co.jp: Java EE 7 Essentials 電子書籍: Arun Gupta: Kindleストア

jBatchをはじめよう!

さて、説明はこれくらいにして、具体的なサンプルコードの説明に入ります。
上記のように、既に何人かの方がブログに書いていらっしゃるのですが、意外にも一番スタンダード(だと僕は思ってるのですが・・・(^^; )なNetBeans+Maven+GlassFishという組み合わせが無かったため、今回はコレで行きたいと思います。ちなみに、IntelliJ IDEA 14.1.3+Gradle 2.4+Payara 4.1.152.1でも、同様のコードで動作確認済みです。

サンプルコードはGitHubにアップしています。
MasatoshiTada/jBatch-sample · GitHub

今回の環境

OS XだとGlassFish 4.1が動かないという情報もあったのですが、今のところ問題なく動作しています。

やりたいこと

下記のような「test1.csv」を読み込んで、JPAエンティティに変換し、DBにINSERTする。

1,suzuki
2,tanaka
3,takahashi
4,aoyama
5,yamashiro
6,ishida
7,naoki
8,ochiai
9,kubota
10,kaneda
11,hino
12,kobayakawa
13,uehara
14,fujita
15,takeshita

プロジェクトの作成

NetBeansで[Maven]-[Webアプリケーション]で作成します。
pom.xmlには、下記のような依存性を追加します。プロジェクト作成直後は「javaee-web-api」になっていますが、今回はjBatchを使うので下記のように「javaee-api」に修正してください。
これは、jBatchがJava EE Web Profileではなく、Full Profileに含まれているためです。

<dependencies><dependency><groupId>javax</groupId><artifactId>javaee-api</artifactId><version>7.0</version><scope>provided</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.35</version></dependency></dependencies>

DB・JPAの準備

テーブルの作成

データベースに「test」というテーブルを作成してください。

CREATETABLE TEST (
    ID INTEGERNOTNULL, 
    NAME VARCHAR(255), 
    PRIMARY KEY (ID))

もし、僕のサンプルコードを「git clone」してお使いになる場合は、後述のpersistence.xmlにエンティティクラスからDBにテーブルを自動生成する設定を記述していますので、この手順は不要です。

GlassFishのコネクションプーリング設定

詳しい手順はこちらへ→GlassFish 4.1でのJNDIコネクションプーリングの設定方法 - Java EE 事始め!
今回は「jdbc/mysqlTest」という名前にします。

JPAエンティティ

NetBeansを使えば、テーブルからエンティティクラスおよびpersistence.xmlを自動生成することが可能です。
詳しい手順はこちらへ→NetBeansとMavenでJPAプロジェクトの作成~エンティティ自動生成まで - Java EE 事始め!

エンティティ

こんなエンティティが自動生成されます。

@Entitypublicclass Test implements Serializable {
    @Id@Basic(optional = false)
    @NotNullprivate Integer id;
    @Size(max = 20)
    private String name;

    public Test() {
    }

    public Test(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
    // setter/getter/equals/hashCode省略
}
persistence.xml

大半の記述は自動生成されますが、ログ出力などの設定を追記してください。

<?xml version="1.0" encoding="UTF-8"?><persistence version="2.1"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"><persistence-unit name="testPU"transaction-type="JTA"><provider>org.eclipse.persistence.jpa.PersistenceProvider</provider><!-- GlassFishに設定したJDBCリソース名 --><jta-data-source>jdbc/mysqlTest</jta-data-source><exclude-unlisted-classes>false</exclude-unlisted-classes><shared-cache-mode>NONE</shared-cache-mode><validation-mode>NONE</validation-mode><properties><!-- エンティティからのテーブル自動生成 --><property name="javax.persistence.schema-generation.database.action"value="create"/><!-- ログ出力 --><property name="eclipselink.logging.level"value="ALL"/><!-- DB製品の指定(必須ではない) --><property name="eclipselink.target-database"value="MySQL"/><!-- 一括追加の設定 --><property name="eclipselink.jdbc.batch-writing"value="JDBC"/><!-- 一括追加サイズの設定 --><property name="eclipselink.jdbc.batch-writing.size"value="5"/></properties></persistence-unit></persistence>

Producerクラスの作成

JPAのEntityManagerを他のクラスにインジェクトできるように、Producerクラスを作成します。

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Dependentpublicclass JpaProducer {
    @PersistenceContext(unitName = "testPU")
    private EntityManager em;
    
    @Producespublic EntityManager getEntityManager() {
        return em;
    }
}

バッチクラスの作成

お待たせしました。ここからがjBatchのコードです。
今回は「chunk方式」という、「読み込み」「処理」「書き込み」の3つで処理を行う方式でやります。

読み込みクラス

前述の「test1.csv」を読み込みます。

import java.io.BufferedReader;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.batch.api.chunk.ItemReader;
import javax.batch.runtime.context.JobContext;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;

@Named@Dependentpublicclass MyItemReader implements ItemReader {

    @Injectprivate JobContext jobContext;
    
    private BufferedReader br;
    
    @Overridepublicvoid open(Serializable checkpoint) throws Exception {
        System.out.println("ItemReader open.");
        String dirName = jobContext.getProperties().getProperty("dir");
        String fileName = jobContext.getProperties().getProperty("input_file");
        br = Files.newBufferedReader(Paths.get(dirName, fileName), StandardCharsets.UTF_8);
    }

    @Overridepublicvoid close() throws Exception {
        System.out.println("ItemReader close.");
        br.close();
    }

    @Overridepublic Object readItem() throws Exception {
        String data = br.readLine();
        System.out.println("Reader readItem : " + data);
        return data;
    }

    @Overridepublic Serializable checkpointInfo() throws Exception {
        System.out.println("ItemReader checkpoint.");
        returnnull;
    }
    
}

後述しますが、@Namedと@Dependentを忘れないようにしてください。

処理クラス

読み込みクラスでCSVから読んだ1行1行を、Testエンティティに変換します。

import javax.annotation.PostConstruct;
import javax.batch.api.chunk.ItemProcessor;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
import sample.entity.Test;

@Named@Dependentpublicclass MyItemProcessor implements ItemProcessor {

    @PostConstructpublicvoid init() {
        System.out.println("ItemProcessor init.");
    }
    
    @Overridepublic Object processItem(Object item) throws Exception {
        String line = (String) item;
        System.out.println("Processor processItem : " + line);
        String[] values = line.split(",");
        int id = Integer.parseInt(values[0]);
        String name = values[1];
        Test test = new Test(id, name);
        return test;
    }
    
}

こちらも、@Namedと@Dependentを忘れないようにしてください。

書き込みクラス

変換したTestエンティティを、DBにINSERTします。

import java.io.Serializable;
import java.util.List;
import javax.batch.api.chunk.ItemWriter;
import javax.batch.runtime.context.JobContext;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;

@Named@Dependentpublicclass MyItemWriter implements ItemWriter {

    @Injectprivate JobContext jobContext;
    
    @Injectprivate EntityManager em;
    
    @Overridepublicvoid open(Serializable checkpoint) throws Exception {
        System.out.println("ItemWriter open.");
    }

    @Overridepublicvoid close() throws Exception {
        System.out.println("ItemWriter close.");
    }

    @Overridepublicvoid writeItems(List<Object> items) throws Exception {
        System.out.println("ItemWriter writeItem : " + items);
        items.forEach(item -> em.persist(item));
    }

    @Overridepublic Serializable checkpointInfo() throws Exception {
        System.out.println("ItemWriter checkpoint.");
        returnnull;
    }
    
}

くどいようですが、@Namedと@Dependentを忘れないようにしてください。

ジョブXMLの作成

バッチジョブを定義するXMLを書きます。
XMLを置くディレクトリは、「src/main/resources/META-INF/batch-jobs」です。
ここに、「my-batch-job.xml」という名前で下記のようなXMLを作成します。

<?xml version="1.0" encoding="UTF-8"?><job id="my-batch-job"xmlns="http://xmlns.jcp.org/xml/ns/javaee"version="1.0"><properties><property name="dir"value="CSVを置いているディレクトリのパス"/><property name="input_file"value="test1.csv"/></properties><step id="first-step"><chunk item-count="5"><reader    ref="myItemReader"/><processor ref="myItemProcessor"/><writer    ref="myItemWriter"/></chunk></step></job>

「job」がルート要素で、その中に1つ以上の「step」があります。1つのstepは、chunk方式では「reader」「processor」「writer」から成り立ちます。
ref属性には、「各クラスの完全修飾名」(パッケージ付きクラス名:FQCN)または「@Namedで指定した名前」を記述しますが、後者にしないとDIできずにNullPointerExceptionが発生します。
僕はここで詰まっていたのですが、前述のかずひらさんのブログで解決しました。
はじめてのjBatch - CLOVER


ちなみに@Namedアノテーションに何も属性を指定しなかった場合は、「クラスの単純名の頭文字を小文字にしたもの」が名前になります。
すなわち、下記のコードは全て同じ意味になります。

@Namedpublicclass HogeClass {}
@Named("hogeClass")
publicclass HogeClass {}
@Named(value = "hogeClass")
publicclass HogeClass {}

もちろん、明示的に任意の名前を指定することも可能です。

@Named("hoge")
publicclass HogeClass {}


今回はitem-count属性に「5」を指定しています。これにより、「CSVを1行読み込み→それをTestエンティティに変換」を5回繰り返した後、その5つのエンティティを一気にDBへ書き込み、という流れになります。これの流れを、読み込みレコードが無くなるまで(=読み込みクラスのreadItem()がnullを返すまで)繰り返します。

ジョブを起動する

ジョブを起動する方法は色々あり、EJBタイマー・JAX-RSリソース・サーブレット・SE環境ではmain()メソッドで可能です。
今回は手軽さ重視でサーブレットから起動します。

import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;

@WebServlet(urlPatterns = "/batch-start")
publicclass BatchStartServlet extends HttpServlet {

    @Overrideprotectedvoid doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // ジョブの起動
        JobOperator jobOperator = BatchRuntime.getJobOperator();
        long executionId = jobOperator.start("my-batch-job", null);

        PrintWriter out = response.getWriter();
        out.println("start!!!");
    }
    
}

極端に言えば、このコードを書ける場所であれば、どこからでもジョブを起動できるということです。

JobOperator jobOperator = BatchRuntime.getJobOperator();
long executionId = jobOperator.start("my-batch-job", null);

「executionId」は、ジョブを一時停止する時やリスタートする時に使うのですが、今回は特に使っていません。

いざ、実行!

web.xmlに、ウェルカムページの設定を書いておきます。

<web-app version="3.1"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee                 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"><welcome-file-list><welcome-file>batch-start</welcome-file></welcome-file-list></web-app>

NetBeansからプロジェクトを右クリック→[実行]で、GlassFish起動→WARデプロイ→ブラウザが起動してウェルカムページの表示、までやってくれます。

実行ログ

情報:   ItemProcessor init.
情報:   JTS5014: Recoverable JTS instance, serverId = [100]
情報:   ItemReader open.
情報:   ItemWriter open.
情報:   Reader readItem : 1,suzuki
情報:   Processor processItem : 1,suzuki
情報:   Reader readItem : 2,tanaka
情報:   Processor processItem : 2,tanaka
情報:   Reader readItem : 3,takahashi
情報:   Processor processItem : 3,takahashi
情報:   Reader readItem : 4,aoyama
情報:   Processor processItem : 4,aoyama
情報:   Reader readItem : 5,yamashiro
情報:   Processor processItem : 5,yamashiro
情報:   ItemWriter writeItem : [Test[ id = 1, name = suzuki ], Test[ id = 2, name = tanaka ], Test[ id = 3, name = takahashi ], Test[ id = 4, name = aoyama ], Test[ id = 5, name = yamashiro ]]
詳細:   client acquired: 846621209
詳細:   TX binding to tx mgr, status=STATUS_ACTIVE
詳細:   acquire unit of work: 631199463
最も詳細:   persist() operation called on: Test[ id = 1, name = suzuki ].
最も詳細:   persist() operation called on: Test[ id = 2, name = tanaka ].
最も詳細:   persist() operation called on: Test[ id = 3, name = takahashi ].
最も詳細:   persist() operation called on: Test[ id = 4, name = aoyama ].
最も詳細:   persist() operation called on: Test[ id = 5, name = yamashiro ].
情報:   ItemReader checkpoint.
情報:   ItemWriter checkpoint.
詳細:   TX beforeCompletion callback, status=STATUS_ACTIVE
詳細:   begin unit of work commit
詳細:   TX beginTransaction, status=STATUS_ACTIVE
最も詳細:   Execute query InsertObjectQuery(Test[ id = 5, name = yamashiro ])
最も詳細:   Connection acquired from connection pool [default].
最も詳細:   Execute query InsertObjectQuery(Test[ id = 4, name = aoyama ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 1, name = suzuki ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 3, name = takahashi ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 2, name = tanaka ])
最も詳細:   reconnecting to external connection pool
詳細:   Begin batch statements
普通:   INSERT INTO TEST (ID, NAME) VALUES (?, ?)
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
詳細:   End Batch Statements
最も詳細:   Connection released to connection pool [default].
詳細:   TX afterCompletion callback, status=COMMITTED
詳細:   end unit of work commit
詳細:   release unit of work
詳細:   client released
情報:   Reader readItem : 6,ishida
情報:   Processor processItem : 6,ishida
情報:   Reader readItem : 7,naoki
情報:   Processor processItem : 7,naoki
詳細:   client acquired: 1918580639
情報:   Reader readItem : 8,ochiai
詳細:   TX binding to tx mgr, status=STATUS_ACTIVE
情報:   Processor processItem : 8,ochiai
情報:   Reader readItem : 9,kubota
詳細:   acquire unit of work: 653950772
情報:   Processor processItem : 9,kubota
最も詳細:   persist() operation called on: Test[ id = 6, name = ishida ].
情報:   Reader readItem : 10,kaneda
最も詳細:   persist() operation called on: Test[ id = 7, name = naoki ].
情報:   Processor processItem : 10,kaneda
最も詳細:   persist() operation called on: Test[ id = 8, name = ochiai ].
情報:   ItemWriter writeItem : [Test[ id = 6, name = ishida ], Test[ id = 7, name = naoki ], Test[ id = 8, name = ochiai ], Test[ id = 9, name = kubota ], Test[ id = 10, name = kaneda ]]
最も詳細:   persist() operation called on: Test[ id = 9, name = kubota ].
最も詳細:   persist() operation called on: Test[ id = 10, name = kaneda ].
情報:   ItemReader checkpoint.
情報:   ItemWriter checkpoint.
詳細:   TX beforeCompletion callback, status=STATUS_ACTIVE
詳細:   begin unit of work commit
詳細:   TX beginTransaction, status=STATUS_ACTIVE
最も詳細:   Execute query InsertObjectQuery(Test[ id = 6, name = ishida ])
最も詳細:   Connection acquired from connection pool [default].
最も詳細:   Execute query InsertObjectQuery(Test[ id = 7, name = naoki ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 10, name = kaneda ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 9, name = kubota ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 8, name = ochiai ])
最も詳細:   reconnecting to external connection pool
詳細:   Begin batch statements
普通:   INSERT INTO TEST (ID, NAME) VALUES (?, ?)
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
詳細:   End Batch Statements
最も詳細:   Connection released to connection pool [default].
詳細:   TX afterCompletion callback, status=COMMITTED
詳細:   end unit of work commit
詳細:   release unit of work
詳細:   client released
情報:   Reader readItem : 11,hino
情報:   Processor processItem : 11,hino
情報:   Reader readItem : 12,kobayakawa
情報:   Processor processItem : 12,kobayakawa
情報:   Reader readItem : 13,uehara
詳細:   client acquired: 1975411799
情報:   Processor processItem : 13,uehara
情報:   Reader readItem : 14,fujita
情報:   Processor processItem : 14,fujita
情報:   Reader readItem : 15,takeshita
情報:   Processor processItem : 15,takeshita
情報:   ItemWriter writeItem : [Test[ id = 11, name = hino ], Test[ id = 12, name = kobayakawa ], Test[ id = 13, name = uehara ], Test[ id = 14, name = fujita ], Test[ id = 15, name = takeshita ]]
詳細:   TX binding to tx mgr, status=STATUS_ACTIVE
詳細:   acquire unit of work: 2116586805
最も詳細:   persist() operation called on: Test[ id = 11, name = hino ].
最も詳細:   persist() operation called on: Test[ id = 12, name = kobayakawa ].
最も詳細:   persist() operation called on: Test[ id = 13, name = uehara ].
最も詳細:   persist() operation called on: Test[ id = 14, name = fujita ].
最も詳細:   persist() operation called on: Test[ id = 15, name = takeshita ].
情報:   ItemReader checkpoint.
情報:   ItemWriter checkpoint.
詳細:   TX beforeCompletion callback, status=STATUS_ACTIVE
詳細:   begin unit of work commit
詳細:   TX beginTransaction, status=STATUS_ACTIVE
最も詳細:   Execute query InsertObjectQuery(Test[ id = 14, name = fujita ])
最も詳細:   Connection acquired from connection pool [default].
最も詳細:   Execute query InsertObjectQuery(Test[ id = 13, name = uehara ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 15, name = takeshita ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 12, name = kobayakawa ])
最も詳細:   Execute query InsertObjectQuery(Test[ id = 11, name = hino ])
最も詳細:   reconnecting to external connection pool
詳細:   Begin batch statements
普通:   INSERT INTO TEST (ID, NAME) VALUES (?, ?)
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
普通:   	bind => [2 parameters bound]
詳細:   End Batch Statements
最も詳細:   Connection released to connection pool [default].
詳細:   TX afterCompletion callback, status=COMMITTED
詳細:   end unit of work commit
詳細:   release unit of work
詳細:   client released
情報:   Reader readItem : null
情報:   ItemReader checkpoint.
情報:   ItemWriter checkpoint.
情報:   ItemReader close.
情報:   ItemWriter close.

5件ずつ読み込んで、DBにINSERTしているのが分かります。

DB

mysql> select * from test;
 ID   NAME       
   1   suzuki     
   2   tanaka     
   3   takahashi  
   4   aoyama     
   5   yamashiro  
   6   ishida     
   7   naoki      
   8   ochiai     
   9   kubota     
  10   kaneda     
  11   hino       
  12   kobayakawa 
  13   uehara     
  14   fujita     
  15   takeshita  

確かに、DBへの追加が出来ています。

他にもjBatchで出来ること

今回は1つのジョブしか作っていませんが、複数のジョブを作成することも可能です。
その場合は、ジョブXMLを同じフォルダ内に複数ファイル作ることになります。
また、今回は1つのジョブに1つのステップしか作っていませんが、通常は1つのジョブは複数のステップから成り立ちます。
それら複数のステップは、条件分岐(Decision)したり、ステップを並列で実行(Split)することもできます。
さらに、例外発生時には指定したチェックポイントから読み込みを再開したり、例外が発生したレコードはスキップしたりすることもできます。

まとめ

jBatchは今まで触ったことが無かったのですが、思いのほか簡単でした。CDIでちょっと詰まりましたが(^^;
今後、もう少し複雑なサンプルを作って、分岐・並列・例外処理なども試してみようと思います!
今日はここまで。

JJUGのReactive Streams勉強会に行ってきました!

$
0
0

最近ちょこちょこ話題になっている「Reactive Streams」についての勉強会に行ってきました!
僕の事前知識は「『React.js』とか『RxJava』っつーのがあるらしい」という程度です。
実際に触ったことはない・・・(^^;
事前にちょっとネットで調べはしたんですが、Java標準のStream APIと何がどう違うんだろう?というのが分からず、そこらへんを重点的に聴きたいなーと思って臨みました!

Reactive Streams入門

前半は岡本さん(@okapies)の発表でした。speakerdeck.com
「Reactiveとは何か?」という概念的なお話でした。

Reactive Manifesto

http://www.reactivemanifesto.org/ja
岡本さんは「Twelve factor appみたいな位置付けのもの」とおっしゃっていました。

Reactive Streamsを使ってみよう

後半は吉田さん(@grimrose)の発表でした。
http://www.grimrose.org/jjug-2015-reactive-streams/#!index.md
実際にRxJavaを使ったReactive Programmingの例を解説されていました。
JavaScriptみたいなイベント駆動のようなイメージを感じました。
RxJava 2.0だとJava 8対応するということで、ラムダ式使ってもっと簡潔に書けるようになるのでしょうね。

Java 8のStream APIとの違いって?

質問タイムの際に質問させていただきました。
ご回答いただいた上での僕の理解では、
Stream API→単体マシン内での非同期も可能なコレクション操作
Reactive Streams→ネットワーク上に分散している可能性もあるし、単体マシン内の可能性もある。その辺りは意識しなくてもいいような抽象化がされている
という認識です。

後は、送り手側が一方的にデータを送りつけすぎて受け手側がオーバーフローしないように、互いに連携しながらデータのやり取りを行ったりするところが違いでしょうか。

うーむ、勉強せにゃならんことがいっぱいや…(^^;;
でもまあ、勉強すべきことがいっぱいあることを認識すること自体がまず大事かなと思うので、今日はそのための良い機会を頂けたのだと思います!

発表者の岡本さん・吉田さん、そしてJJUGの皆様、ありがとうございました!

はじめてのMacBook Air!〜環境設定でハマったこと〜

$
0
0

先日、はじめてMacBook Air(11インチ、Core i7、8GB、128GB)を買いました。
今までずっとWindowsノートPCを使っていたのですが、

  • 軽くて持ち運びがしやすい
  • JavaIDEやAPサーバーをサクサク動かせるスペック
  • 将来的にはSwiftiPhoneアプリ開発もやりたい
  • 上記の3点+予算とのバランス

という点を考慮して、MacBook Airにしました。
僕はMacを使うのは初めてですし、UNIX/Linux系のOSも少ししか触ったことがありません。
知ってるコマンドは「cd」「ls -all」「mkdir」「cat」「vi」くらいですね・・・(^^;
そんな僕が、Macの環境設定でハマったことを、備忘録的にメモしておきます。
これからMacを使ってみたい!という方の参考になれば幸いです。

セキュリティソフト

Macならセキュリティソフトは不要という説もネット上ではあったのですが、万全を期すためにカスペルスキーの1台3年版を入れました。
今のところ、重くなく快適に使えています。

キーボードの謎の記号

ネットでいろいろとMacの情報を調べていると、出くわすのがキーボードのショートカットキーを表す謎の記号です。
ぐるぐるマークがCommandなのはキーボードに書いてあるから分かるんですが、他のが分からない(^^;
結局、会社の先輩に「Macキーボード 記号」でググるといいよーと教えていただきました。

一本指でのクリック

多くのWindowsノートでは、トラックパッド上で一本指でタップするとマウスでいう左クリックになりますが、Macはデフォルトではそうなっていません。
これを有効化するには、デスクトップ左上のリンゴマークをクリック→システム環境設定→トラックパッドで、[タップでクリック 1本指でタップ]にチェックを入れます。

ターミナルの補完設定

「ターミナル」とは、Windowsで言うコマンドプロンプトです。
ターミナルを開くには、デスクトップ右上の虫眼鏡アイコンをクリックしてSpotlight検索を出し、そこに「ターミナル」と入力すれば出てきます。
デフォルトでは、WindowsみたいにTabキーでパスなどを補完できないことがあります。
ユーザーホームディレクトリ(/Users/ユーザー名。「~」で表す)に.inputrcというファイルを作ります。

cd ~
sudo vi .inputrc

そこに、下記の記述を追記します。

set completion-ignore-case on
TAB: menu-complete

補完を有効化するには、ターミナルの再起動が必要です。
viエディタの使い方は、下記のサイトなどを参考にしてください。

環境変数の設定

JDKMaven・Gradleなどをインストールした後は、環境変数の設定が必要です。
ユーザーホームディレクトリに.bash_profileというファイルを作ります。

cd ~
sudo vi .bash_profile

下記の記述を追記

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home
export GRADLE_HOME=~/Java/gradle-2.4
export M2_HOME=~/Java/apache-maven-3.3.3
export PATH=$PATH:$JAVA_HOME/bin:$GRADLE_HOME/bin:$M2_HOME/bin

NetBeansの爆速設定

NetBeansの補完が速くなるように設定します。

cd ~/Library/Application¥ Support/NetBeans/8.0.2/config/Editors/text/x-java/Preferences
vi org-netbeans-modules-editor-settings-CustomPreferences.xml

下記の記述を追記

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE editor-preferences PUBLIC"-//NetBeans//DTD Editor Preferences 1.0//EN""http://www.netbeans.org/dtds/EditorPreferences-1_0.dtd"><editor-preferences><entry javaType="java.lang.String"name="javaAutoCompletionTriggers"xml:space="preserve"><value><![CDATA[.qwertyuiopasdfghjklzxcvbnm]]></value></entry><entry javaType="java.lang.Boolean"name="javaCompletionSubwords"xml:space="preserve"><value><![CDATA[false]]></value></entry><entry javaType="java.lang.Integer"name="completion-auto-popup-delay"xml:space="preserve"><value><![CDATA[20]]></value></entry><entry name="code-folding-collapse-innerclass"remove="true"/><entry name="code-folding-collapse-javadoc"remove="true"/><entry name="code-folding-collapse-method"remove="true"/><entry name="pair-characters-completion"remove="true"/><entry name="show-deprecated-members"remove="true"/></editor-preferences>

MySQLの設定

MySQLをインストールしたはいいが動かなかったので、下記の設定で対処しました。

sudo chown -R ユーザー名 /usr/local/
sudo chgrp -R staff /usr/local/

MySQLの起動・停止はターミナルからコマンドで実行します。

mysql.server start
mysql.server stop

MySQLコマンドラインクライアントは、下記で起動します。

mysql

また、文字コードがなぜかUTF8じゃなかったので、下記のサイトを参考に変更しました。

IntelliJ IDEA 14.1.3 + Gradle 2.4 + Payara 4.1.152.1でWebアプリのデプロイエラー

ビルドは通ったものの、デプロイ時に「java.lang.NoClassDefFoundError: org/apache/tools/ant/util/ReaderInputStream 」が出ました。
IDEAで[File] > [Invalidate Caches / Restart...]すると、デプロイに成功してブラウザから実行できました。このエラーは、今でもたまに出てきますので、その度にコレを実行しています。

今後の課題

IDEの補完で使うショートカット(Command + Spaceなど)が、OSレベルで別の役割になってしまっているので、これを何とかしたいです。
気がついたことがあれば、今後も追記します。

Mac版Eclipse 4.5 Marsをpleiadesで日本語化する方法

$
0
0

ちょっとハマったのでメモしときます。

Eclipse 4.5 Marsのインストール

まずは、Eclipse公式サイトからMac版のtar.gzファイルをダウンロードしてください。
そして、tarコマンドで解凍します。

tar -xf eclipse-jee-mars-R-macosx-cocoa-x86_64.tar.gz
mv Eclipse.app /Applications

pleiadesのダウンロード・インストール

pleiadesは1.6.0以上をダウンロードしてください。
Eclipse 日本語化 | MergeDoc Project

pleiades.zipをunzipで展開して、pluginsフォルダとfeaturesフォルダの中身を、それぞれEclipseのpluginsフォルダとfeaturesフォルダにコピーします。
「/Applications/Eclipse.app/Contents/Eclipse」フォルダ内に、Eclipseのpluginsフォルダとfeaturesフォルダがあります。
Windowsのように、pluginsフォルダを「すべて上書き」みたいなことをすると、Eclipseのpluginsフォルダの元々の中身がすべて消えてしまいますので注意してください。

eclipse.iniの編集

eclipse.iniは、「/Applications/Eclipse.app/Contents/Eclipse」フォルダ内にあります。eclipse.iniを「テキストエディット」などのエディタ開いて、下記の記述を最終行に追加します。

-Xverify:none
-javaagent:../Eclipse/plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar

1行目の「-Xverify:none」が無いと起動時エラーになります。
2行目の「-javaagent:」では、eclipse.iniがあるフォルダからの相対パスをします。(絶対パスでもOKです)
4.5 Marsから、MacEclipseのフォルダ構成が変わっているようなので、注意が必要です。

【修正しました】NetBeansでtarget/generated-sources配下のソースがエディタ上で認識できない場合の対処法

$
0
0

NetBeans+MavenJPA(EclipseLink or Hibernate)やってると、エンティティのメタモデルクラスが自動生成されます。
これらのソースはプロジェクトルート/target/generated-sources/annotationsフォルダに生成されます。
にも関わらず、NetBeansのエディタ上ではこれらのクラスが認識されないことがあります。

この現象が置きた場合、Windowsの場合はC:\Users\User\AppData\Local\NetBeans\Cache内の全ファイル・フォルダを削除して、NetBeansを再起動してください。
そうすると、エディタでメタモデルクラスが認識できるようになりました。

追記

Lombokを使っていることがどうやら原因だったようです。
Lombokを使わないで書き直したら、上記の問題が解決しました。

再追記

JPAエンティティにLombokを使っていたことが原因でした。
JPAエンティティ以外の部分であれば、今のところはLombokを使っても大丈夫そうです。

「はまる!JPA」のN+1問題をEntity Graphで解決する

$
0
0

JPAにおけるN+1問題とは?

元ネタは、こちらの@makingさんの資料(2013年のGlassFish勉強会にて)です。

www.slideshare.net
N+1問題とは、エンティティに1対多のリレーションがあるときに、多数のSQLが発行されてパフォーマンスが落ちるという問題です。
上記の槙さんの資料では、JPQLのJOIN FETCH文+setHint()メソッドを利用してこの問題を解決し、1回のSQLで全てのデータを取得できるようにしています。
今回の記事では、JPA 2.1(Java EE 7)の新機能であるEntity Graphを使って、N+1問題の解決を試みます。

リレーションやフェッチなど、JPAの入門的な内容についてはJJUG CCC 2015 Springでの僕の発表資料をご覧ください。
(タイトルは槙さん上記発表のパクリオマージュです(^^;)

www.slideshare.net

Entity Graphとは?

JPAでは、JPA 1.0(Java EE 5)の頃からフェッチの設定(EAGER or LAZY)をすることが出来ました。
しかし、アノテーションで静的に設定するため、動的に変更するためにはJOIN FETCHを使う必要がありました。
また、槙さんの資料にも説明されている通り、ネストしたJOIN FETCHはサポートされていないため、3つ以上の表を結合する際に問題が発生することがあります。
そこで、動的にフェッチを変更できるように誕生したのが、Entity Graphです。
Entity Graphを使えば、どれくらいの深さまでEAGERフェッチするかを指定することが可能です。
Java EE 7 TutorialでもJSRでもあんまりページ数が多くなくて、英語情報も少ないです。
なので、今回のサンプルプログラムもかなり試行錯誤しながら書きましたので、不正確な記述があるかもしれません。

今回の環境

後述しますが、EclipseLinkおよびGlassFishでは上手くいきませんでした。。。

今回のコードはGitHubにアップしています。
MasatoshiTada/hamaru-jpa · GitHub
Entity Graphを使っている部分以外は、ほとんど槙さんの資料を参考に作成したものです。

エンティティ

まずはエンティティです。DB側にも、これらに対応するテーブルが存在します。

注文(Order)

@Entity@Table(name = "T_ORDER")
publicclass Order implements Serializable {
    privatestaticfinallong serialVersionUID = 1L;
    
    @Idprivate Long id;

    @OneToMany(mappedBy = "order", 
            cascade = CascadeType.ALL, 
            orphanRemoval = true,
            fetch = FetchType.LAZY)
    private List<OrderItem> orderItems;
    
    // setter/getter/equals/hashCodeなど省略

商品(Item)

@Entity@Table(name = "T_ITEM")
publicclass Item implements Serializable {
    privatestaticfinallong serialVersionUID = 1L;
    
    @Idprivate Long id;
    
    private String name;

    @OneToMany(mappedBy = "item", 
            cascade = CascadeType.ALL, 
            orphanRemoval = true,
            fetch = FetchType.LAZY)
    private List<OrderItem> orderItems;
    
    // setter/getter/equals/hashCodeなど省略

注文と商品の中間エンティティ(OrderItem)

@Entity@Table(name = "T_ORDER_ITEM")
publicclass OrderItem implements Serializable {
    privatestaticfinallong serialVersionUID = 1L;
    
    @Idprivate Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    // setter/getter/equals/hashCodeなど省略

データアクセス

ここからが、Entity Graphを使っている部分です。

@Dependent@Transactional(Transactional.TxType.REQUIRED)
publicclass OrderService implements Serializable {
    @Injectprivate EntityManager em;
    
    public List<Order> listOrders() {
        EntityGraph<Order> rootOrderGraph = em.createEntityGraph(Order.class);
        Subgraph<OrderItem> orderItemSubgraph = rootOrderGraph.addSubgraph("orderItems");
        Subgraph<Item> itemSubgraph = orderItemSubgraph.addSubgraph("item");
        
        TypedQuery<Order> query = em.createQuery(
                "SELECT DISTINCT o FROM Order o ORDER BY o.id", 
                Order.class);
        query.setHint("javax.persistence.loadgraph", rootOrderGraph);
        List<Order> orders = query.getResultList();
        
        PersistenceUtil util = Persistence.getPersistenceUtil();
        System.out.println("=====================================================================");
        System.out.println("orderItems loaded: " + util.isLoaded(orders.get(0), "orderItems"));
        System.out.println("orderItems.item loaded: " + util.isLoaded(orders.get(0), "orderItems.item"));
        System.out.println("=====================================================================");
        
        return orders;
    }
}

まず、EntityManager#createEntityGraph()でEntityGraphを取得します。

EntityGraph<Order> rootOrderGraph = em.createEntityGraph(Order.class);

ここからaddSubGraph()することで、どんどんフェッチを深くしていく感じです。

// OrderエンティティのorderItemsプロパティをフェッチ
Subgraph<OrderItem> orderItemSubgraph = rootOrderGraph.addSubgraph("orderItems");
// OrderItemエンティティのitemプロパティをフェッチ
Subgraph<Item> itemSubgraph = orderItemSubgraph.addSubgraph("item");

そして、検索時にこのEntityGraphを、TypedQuery#setHint()で指定します。

query.setHint("javax.persistence.loadgraph", rootOrderGraph);

ヒントの指定方法は2種類あります。
(1) javax.persistence.loadgraph
EntityGraphやSubGraphで明示的に指定したプロパティはEAGERフェッチとし、その他のプロパティはエンティティのデフォルト設定に従います。
(2) javax.persistence.fetchgraph
EntityGraphやSubGraphで明示的に指定したプロパティはEAGERフェッチとし、その他のプロパティはエンティティのデフォルト設定は無視してフェッチしません。
(どちらの場合でも。主キーやバージョン値は、明示的に指定しなくても必ずフェッチされます)

PersistenceUtil util = Persistence.getPersistenceUtil();
System.out.println("orderItems loaded: " + util.isLoaded(orders.get(0), "orderItems"));
System.out.println("orderItems.item loaded: " + util.isLoaded(orders.get(0), "orderItems.item"));

上記の部分は、指定したプロパティがフェッチされたかどうかを確かめるメソッドです。フェッチされていればtrueが返ります。

ビュー

JSFを使っています。これも槙さんの資料を参考に作りました。

バッキングビーン(初期画面)

ベンチマークとして処理時間を測っているのですが、Facelets上でフェッチされる可能性があるので、本来は正しい時間ではありません。あくまで参考程度に書いたものです。

@Named@ViewScopedpublicclass IndexBean implements Serializable {
    privatestaticfinallong serialVersionUID = 1L;
    
    @Injectprivate OrderService orderService;
    
    public String send() {
        long start = System.currentTimeMillis();
        List<Order> orders = orderService.listOrders();
        long time = System.currentTimeMillis() - start;
        Flash flash = FacesContext.getCurrentInstance()
                .getExternalContext()
                .getFlash();
        flash.put("orders", orders);
        flash.put("time", time);
        return"result.xhtml?faces-redirect=true";
    }
}

Facelets(初期画面)

<html xmlns="http://www.w3.org/1999/xhtml"      xmlns:h="http://xmlns.jcp.org/jsf/html"><h:head><title>N+1問題</title></h:head><h:body><h:form><h:commandButton action="#{indexBean.send()}"value="全件取得" /></h:form></h:body></html>

バッキングビーン(結果画面)

@Named@ViewScopedpublicclass ResultBean implements Serializable {
    privatestaticfinallong serialVersionUID = 1L;
    
    @Getter@Setterprivate List<Order> orders;
    
    @Getter@Setter
    Long time;
    
    @PostConstructpublicvoid init() {
        Flash flash = FacesContext.getCurrentInstance()
                .getExternalContext()
                .getFlash();
        orders = (List<Order>) flash.get("orders");
        time = (Long) flash.get("time");
    }
    
}

Facelets(結果画面)

<html xmlns="http://www.w3.org/1999/xhtml"      xmlns:h="http://xmlns.jcp.org/jsf/html"      xmlns:f="http://xmlns.jcp.org/jsf/core"      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"><h:head><title>N+1問題</title></h:head><h:body>処理時間:<h:outputText value="#{resultBean.time}"/>[ms]
        <h:dataTable value="#{resultBean.orders}" var="order"border="1"><h:column><f:facet name="header"><h:outputText value="Order ID"/></f:facet><h:outputText value="#{order.id}"/></h:column><h:column><f:facet name="header"><h:outputText value="ITEM"/></f:facet><ul><ui:repeat value="#{order.orderItems}" var="orderItem"><li><h:outputText value="#{orderItem.item.name}"/></li></ui:repeat></ul></h:column></h:dataTable></h:body></html>

いざ、実行!

ビルドしてWildFlyにデプロイして、下記のURLにアクセスします。
http://localhost:8080/hamaru-jpa/
そして、全件検索ボタンをクリックします。
すると、下記のように全注文が一覧で表示されます。
f:id:MasatoshiTada:20150711183303p:plain
IDEWildFlyのログを見てみましょう。

 Hibernate: 
     select
         distinct order0_.id as id1_1_0_,
         orderitems1_.id as id1_2_1_,
         item2_.id as id1_0_2_,
         orderitems1_.item_id as item_id2_2_1_,
         orderitems1_.order_id as order_id3_2_1_,
         orderitems1_.order_id as order_id3_1_0__,
         orderitems1_.id as id1_2_0__,
         item2_.name as name2_0_2_ 
     from
         T_ORDER order0_ 
     left outer join
         T_ORDER_ITEM orderitems1_ 
             on order0_.id=orderitems1_.order_id 
     left outer join
         T_ITEM item2_ 
             on orderitems1_.item_id=item2_.id 
     order by
         order0_.id
 =====================================================================
 orderItems loaded: true
 orderItems.item loaded: true
 =====================================================================

3つのテーブルがすべてジョインされて、1回のSQLでデータが取れています。loadedの値もtrueになっていますね。
もし、今回のコードでEntityGraphを使わなかった場合、Hibernateでは例外になってしまいます。
これは、OrderエンティティがDETACHED状態で、orderItemsプロパティにアクセスしてしまうためです。

EclipseLinkだと・・・?

では、全く同じコードをEclipseLink+GlassFishで実行します。
GitHub上では別ブランチにしてあります。
さて実行すると・・・


・・・


めちゃくちゃ遅い(--;
f:id:MasatoshiTada:20150711184440p:plain
GlassFishのログを見ましょう。

普通:   SELECT DISTINCT ID FROM T_ORDER ORDER BY ID
普通:   SELECT ID, item_id, order_id FROM T_ORDER_ITEM WHERE (order_id = ?)
	bind => [1000]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [434]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [72]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [277]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [389]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [34]
普通:   SELECT ID, item_id, order_id FROM T_ORDER_ITEM WHERE (order_id = ?)
	bind => [1001]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [305]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [86]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [387]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [44]
普通:   SELECT ID, NAME FROM T_ITEM WHERE (ID = ?)
	bind => [77]
・・・(中略)・・・
情報:   =====================================================================
情報:   orderItems loaded: true
情報:   orderItems.item loaded: true
情報:   =====================================================================

loadedの値がtrueになっているものの、大量のSQLが発行されています。N+1問題そのものですね。
EntityGraphを使わない場合、まずは「SELECT DISTINCT ID FROM T_ORDER ORDER BY ID」だけが実行され、Facelets上でorderItemsプロパティやitemプロパティにアクセスする際に、その他のSQLが都度実行される形になります。
つまり、SQLが発行されるタイミングが変わるだけで、SQLの数そのものは全く変わりません。
Hibernateのように例外にはなりません。これも実装によって異なります)

何故こうなったのかというと、実はJPAでは、EAGERフェッチは必ずしもJOINを意味しません。それは実装によって異なります。
EclipseLinkの場合は、EAGERフェッチはJOINせずに全SQLをまとめて実行するだけなので、このようになったということです。
となると、EclipseLinkでは槙さんが書かれていた方法でないと、解決できないかもしれないですね・・・。

まとめ

  • EntityGraphを使うと、動的にフェッチ設定を変更できる
  • ただし、挙動はJPA実装によってことなるため、必ずしもN+1問題を解決できるとは限らない
  • SQLログは必ず出して、挙動を確かめることが重要

最後に

9月に蓮沼さん(@khasunuma)がGlassFish勉強会を久々に開催されるということで、僕もスピーカーとしてお手伝いさせていただくことになりました。帰ってきたGlassFish Users Group Japan勉強会glassfish.doorkeeper.jp
発表内容は検討中ですが、何と言っても、発表者が上妻さん・菊田さん・蓮沼さんと豪華すぎです!
そんな中に僕が居ていいものか・・・とも思ったのですが、Java EEおよびGlassFishの普及のため、頑張ります!

参考資料

  • Java EE 7 Tutorial "Chapter 43 Creating Fetch Plans with Entity Graph"
  • JSR-338 "3.7 Entity Graph"
  • Javadoc
  • マスタリングJava EE 5 第2版

はじめてのIntelliJ IDEA 14

$
0
0

きっかけ

最近(というか先週から)、IntelliJ IDEA 14.0.2を使っています。
きっかけは、先日のJJUG CCCで「開発環境はIntelliJ IDEA」という話が多かったことです。
長く使ってきたのはEclipse、最近のお気に入りはNetBeansですが、何でも食わず嫌いはよろしくないと思い、使ってみようと思いました。

エディション

有料版のUltimate Editionと、無料版のCommunity Editionがありますが、有料版も30日間は試用としてフリーで使えます。
Webアプリ開発は基本的にUltimate Editionなのですが、Spring Bootのように組み込みAPサーバーを使うならばCommunity Editionでも大丈夫です。Community EditionでもHTMLの作成・編集は可能です。

インストール

IntelliJ IDEA :: Download Latest Version of IntelliJ IDEA
上記のサイトからインストーラーをダウンロードして、実行するだけです。

初期設定

バージョンによって設定の箇所がけっこう変わっているらしく、注意が必要です。

プロジェクトのコンパイラ・レベルの設定

プロジェクトを右クリックしても「Property」とかないんですね(^^;
初期設定ではデフォルトのレベルが「6」になっているので、変更しましょう。
IDEAの起動画面で[Configure]-[Project Structure]→[Project]-[Project language level]で選択します。
もし、プロジェクトを開いていたら、[File]メニューで[Close Project]を選択すると、起動画面になります。
ちなみに、「9」も設定可能です。恐るべし。

行番号の表示

[File]メニューの[Settings]→[Editor]-[General]-[Appearance]で、[Show line numbers]にチェックを入れます。

プロジェクトファイルの文字コード設定、プロパティファイルのnative2asciiの有効化

[File]メニューの[Settings]→[Editor]-[File Encodings]です。
native2asciiを有効化するには、画面下方の[Transparent native-to-ascii]にチェックを入れます。

インクリメンタルビルドの設定

Eclipseとかだと、Ctrl+Spaceで保存すると同時にコンパイルが実行されますが、IDEAはデフォルトではこの機能がオンになっていません。
[File]→[Settings]→[Build,Execution,Deployment]→[Compiler]の[Make project automatically]にチェックを入れます。

補完機能の設定

デフォルトでは、大文字・小文字を区別しないと補完してくれません。
例えば、「@SpringBootApplication」と補完したい場合、頭文字を大文字で「@Spr」みたいにしないと補完できません。
これは面倒なので、頭文字が小文字でも補完できるようにしましょう。
[File]→[Settings]→[Editor]→[General]→[Code Completion]の[Case sensitive completion]を[None]にします。

プラグイン

プラグインの追加方法

IDEAの起動画面で[Configure]-[Plugins]を選択します。

Database Navigator

Community Editionには、データベース連携機能がありませんので、このプラグインを入れました。
ちなみに、Ultimate Editionのデータベース連携機能は、どうもプロジェクト単位に紐付いているらしいですので、こちらも入れたほうが良いかもです。

Lombok Plugin

こちらのサイトを参考にしました。
IntelliJでLombok - しおしお
・・・が、なんかうまくいかず。
Annotation Processorsも有効にしたんやけどなあ(^^;
引き続き、調査します。

プログラムの実行

main()メソッドがあるクラスをエディタで開いて、Shift+Ctrl+F10です。

まとめ

最新情報に関しては、日本語より英語でググった方が速いです。
IntelliJ IDEA 14 行番号」じゃなくて「IntelliJ IDEA 14 line number」とか。
やっぱり英語って大事やなあ・・・。
これからどんどん使い込んでいこうと思います!
気が付いた使い方とかあれば、随時追記していきます。


Spring BootのREST APIとAngularJSの組み合わせ

$
0
0

最近、AngularJSも少し勉強しているのですが、「Spring Boot AngularJS」で検索したらサンプルが出てきたので、やってみました。

やりたいこと

「はじめてのSpring Boot」の「3.2 「REST Webサービスの開発」」のサンプルに、WebサービスからのJSONをAngularJSで受け取って表示する画面を作成します。

作り方

src/main/resources直下に「public」というフォルダを作り、そこに下記のようなJS・HTMLを作成します。

hello.js

function Hello($scope, $http) {
    $http.get('http://localhost:8080/api/customers').
        success(function(data) {
            $scope.customers = data;
        });
}

home.html

<!doctype html><html ng-app><head><title>Hello AngularJS</title><scriptsrc="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script><scriptsrc="hello.js"></script></head><body><div ng-controller="Hello"><ul ng-repeat="customer in customers"><li>{{customer.id}} {{customer.firstName}} {{customer.lastName}}</li></ul></div></body></html>

こんな感じです。
f:id:MasatoshiTada:20141220163706p:plain

実行結果

作成できたら、メインメソッドを実行して起動し、ブラウザから下記のURLにアクセスします。
http://localhost:8080/home.html
こんな感じになったら成功です。
f:id:MasatoshiTada:20141220164049j:plain

注意点

このサンプル、AngularJSの1.2系では動きますが、1.3系では動きません。
1.3系では何も表示されず、画面が真っ白になります。
この点は、引き続き調査します。

★追記
AngularJS 1.3系でのサンプルはコレあたりかな?
https://docs.angularjs.org/tutorial/step_11

続・JPQLでハマった話

$
0
0

このブログについて

この記事は、Java EE Advent Calendar 2014 - Qiitaの21日目です。
昨日は@kokuzawaさんのRESTEasyとSpringの連携 - Katsumi Kokuzawa's Blogでした。
明日は@den2snさんです。

経緯および前回の記事

まずは、こちらをご覧ください。
JPQLでハマった話 - Java EE 事始め!
で、この前回の記事では「EclipseLinkではネイティブSQL使うしかない!」と結論づけてしまったのですが、改めてよく考えてみると気になる部分があったので、再調査しました。

気になった部分とは?

実行したときの例外ログです。

javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: column "emp.ename" must appear in the GROUP BY clause or be used in an aggregate function

よーく見ると、PSQLExceptionが出ています。ということは、JPAレベルの問題ではなく、JDBCおよびSQLレベルの問題なのではないか、と思いました。

ログ出力の設定

persistence.xmlに、以下の記述を追加します。

EclipseLinkの場合

<property name="eclipselink.logging.level.sql"value="FINE" /><property name="eclipselink.logging.parameters"value="true" />

Hibernateの場合

<property name="hibernate.show_sql"value="true" />

コード

EntityManagerFactory factory = Persistence.createEntityManagerFactory("empPU");
EntityManager manager = factory.createEntityManager();
String jpql = "SELECT SUBSTRING(e.ename, 1, 1), COUNT(e) FROM Emp e GROUP BY SUBSTRING(e.ename, 1, 1)";
List<Object[]> result = manager.createQuery(jpql, Object[].class)
        .getResultList();
result.forEach(array -> System.out.println(array[0] + " : " + array[1]));
manager.close();
factory.close();

実行されたSQL

EclipseLinkの場合

SELECT SUBSTR(ENAME, ?, ?), COUNT(EMPNO) FROM EMP GROUPBY SUBSTR(ENAME, ?, ?)
-> bind [1,1,1,1]

SQLはログに出ましたが、このSQLを実行した段階で例外になっているようです。

Hibernateの場合

select substring(emp0_.ename, 1, 1) as col_0_0_, count(emp0_.empno) as col_1_0_ 
from Emp emp0_ groupby substring(emp0_.ename, 1, 1)

SQLが出力され、プログラムも実行できました。

違いは?

大きな違いは、「1」の部分がEclipseLinkはパラメータ化されているということです。
Hibernateはパラメータ化されていません。
これが原因ではないかと。

JDBCでやってみた

コード

String sql = "SELECT SUBSTR(ENAME, ?, ?), COUNT(EMPNO) FROM EMP GROUP BY SUBSTR(ENAME, ?, ?)";
Connection con = DriverManager.getConnection(url, user, password);
PreparedStatement pst = con.prepareStatement(sql);
pst.setInt(1, 1);
pst.setInt(2, 1);
pst.setInt(3, 1);
pst.setInt(4, 1);
ResultSet rs = pst.executeQuery();
while (rs.next()) {
    System.out.println(rs.getString(1) + " : " + rs.getInt(2));
}

実行結果

Exception in thread "main" org.postgresql.util.PSQLException: ERROR: column "emp.ename" must appear in the GROUP BY clause or be used in an aggregate function

EclipseLinkでやったときと同じ例外ですね。

推測ですが

おそらくパラメータ化されていると、SELECT句で指定された「SUBSTR(ENAME, ?, ?)」と、GROUP BY句で指定されている「SUBSTR(ENAME, ?, ?)」が同じものかどうかDBが判断できないため、例外になっているものと思われます。

EclipseLinkで実行する方法

つまり、EclipseLinkの場合、JPQLにベタ書きした定数がパラメータ化されてしまうため、例外が発生していたということになります。
この設定、persistence.xmlのプロパティでどうにか変更できるんでは?という仮説を思いつき、調べてみたら、ありました!
jdbc.bind-parameters | EclipseLink 2.5.x Java Persistence API (JPA) Extensions Reference
(「eclipselink properties persistence」でググりました)

<property name="eclipselink.jdbc.bind-parameters"value="false"/>

こうして最初のJavaプログラムを実行すると・・・

SELECT SUBSTR(ENAME, 1, 1), COUNT(EMPNO) FROM EMP GROUPBY SUBSTR(ENAME, 1, 1)
Y : 1
I : 2
K : 4
N : 3
S : 2
O : 1
A : 1

おおお、「1」の部分がパラメータ化されず、実行することができました!

この設定をしたとしても、通常のJPQLパラメータは使えますので、ご安心ください。

// 実行可能
String jpql = "SELECT e FROM Emp e WHERE e.empno = :empno";
Emp emp = manager.createQuery(jpql, Emp.class)
        .setParameter("empno", 102)  // パラメータ指定
        .getSingleResult();
System.out.println(emp.getEmpno() + " : " + emp.getEname());
manager.close();
factory.close();

まとめ

うーむ、JPA実装によって、こーゆー所も違うんですね。
後はやはり、SQLログは出しましょう、と。

eclipselink.jdbc.bind-parametersは、基本的にfalseを指定した方がよいかもしれません。
もし「この設定だとこういう場合まずいんじゃない?」といったことがありましたら、ぜひコメントいただければと思います。

2014年ふりかえり&2015年の目標

$
0
0
さて皆さん、あけましておめでとうございます。
今年もブログはマイペース更新になると思いますが、見かけたらぜひ読んでやってくださいませ。

2014年ふりかえり

このブログを書き始めてから、約1年が経ちました。
2013年の暮れからJava EE 7の研修を作るべく、英語のチュートリアルと格闘していました。
その過程を書いた「Java EE 7を勉強する方法」は、今もGoogle検索などから一定数の方々に読んでいただいているようです。
ちなみに、僕が作った研修はこちら↓

しかし、この1年でだいぶ環境が変わりました。
Java EE 7に関する日本語書籍が出版されたり、GlassFish 4.1がリリースされたり。
また、僕自身も何回かJava EE 7研修を担当して、新たな知見も溜まってきたので、「Java EE 7を勉強する方法(2015年version)」という記事は、早目に書こうと思っています。

あと、JJUG CCCでは春・秋の2回、セッションをすることができました。
両方とも、研修の開発から生まれたネタです。
講師というプレゼンテーションが主である仕事をしていますが、何百人という方々を前にしたプレゼンは、ものすごく緊張しました。

年末にはアドベントカレンダーも2本書きました。
この二つも、多くの方々に読んでいただいたようで、とても嬉しかったです。

あとは@makingさんの「はじめてのSpring Boot」を読んで、Spring Bootにも興味を持ち、これも研修に仕立てられたらいいな〜と考え中です。

年間を通じて、このブログやTwitter、あとはJJUG CCCの懇親会などて、色んな方と交流する機会がありました。
とても楽しくて、今後も継続していきたいと思います!

2015年の目標

まだまだな部分はありますが、2014年は結構「やり切った」年で、とても充実感がありました。
今年、主にやりたいこととしては、
  1. Spring Bootの研修開発
  2. HTML5/CSS3/JavaScript/jQuery/AngularJSといったクライアントサイドの勉強
  3. LinuxやDBなどインフラの勉強
Spring Bootは仕事、あとはプライベートです。
2と3については資格を取りたいと思っていますので、受かったら合格体験記なども書きます。

まあ何はともあれ、まずはJava EE 7を勉強する方法(2015年version)」を書きます。
帰省から帰ってきたら…。

Java EE 7を勉強する方法(2015年ver.)

$
0
0

このブログについて

約1年前、下記の記事を書きました。
http://masatoshitada.hatenadiary.jp/entry/2014/04/11/081731
僕がJava EE 7研修を開発するにあたって、どのように情報収集したのかを書いたもので、今も一定数の方々に読んでいただいているようです。
しかし、あれから月日が経ち、Java EE 7を取り巻く環境も変わりつつあります。
日本語の書籍もいくつか出版されています。
そこで、「今からJava EE 7を勉強するには、どうすればよいか?」を改めて書きたいと思います。

0.開発環境のセッティング

JDK 8u25以上+GlassFish 4.1+NetBeans 8.0.2がおススメです。
GlassFishNetBeansのインストールは、ZIPを展開するだけと非常に簡単です。
下記の書籍でも紹介されていますが、僕もブログにいくつか記事を書きましたので、参考になさってください。
GlassFish 4.0のインストールとNetBeans 8.0の連携 - Java EE 事始め!
GlassFish 4.1でのJNDIコネクションプーリングの設定方法 - Java EE 事始め!

1.まずは概要を知る

まずは、Java EE 7の全体像を把握しつつ、簡単なプログラムを作って体験してみましょう。
それにうってつけの書籍が、「Javaエンジニア養成読本」です。Amazon.co.jp: Javaエンジニア養成読本[現場で役立つ最新知識、満載!] 電子書籍: きしだなおき, のざきひろふみ, 吉田真也, 菊田洋一, 渡辺修司, 伊賀敏樹: KindleストアJava EE 7の全体像がコンパクトにまとめられており、1日で十分読める分量です。
前半はJava EE 7の各技術の解説、後半が実際にプログラムを作ってみるチュートリアルになっています。
チュートリアルでは、データベース連携を伴うWebアプリを開発するので、Java EE 7での開発をザッと理解するには最適です。

もし、時間に余裕があるのであれば、JJUGが出しているチュートリアルをやってみるのもよいでしょう。

養成本よりもボリュームが大きいので、それなりに歯ごたえがありますが、これも2~3日あれば読める分量だと思います。
このスライドはWord形式(PDFだったかも?)でダウンロードできますので、ローカルで見たり、紙に印刷して読んだりできます。

2.ベースを固める

Java EE 7でWebアプリを作るための中心技術であるJSFJPACDIについて詳しく学びましょう。
僕自身を含め、おそらく多くの人が待望していた、Java EE 7初の日本語書籍です。Amazon.co.jp: わかりやすいJavaEEウェブシステム入門: 川場 隆: 本けっこう厚い本なのですが、最初の部分はHTMLやCSSの話なので、飛ばしてよいでしょう。
また、この本もプログラムを作りながら学習できるので、厚さの割には読みやすいです。
あとは、ログインなどのセキュリティ面に触れているのもいいですね。

3.知識を補完する

上記2冊の本はとても良いのですが、実務で利用する上では、まだ足りない部分があります。
特にJPAについては、かなり奥が深いので、もう少し知識を補完する必要があります。Amazon.co.jp: マスタリングJavaEE5 第2版 (DVD付) (Programmer’s SELECTION): 三菱UFJインフォメーションテクノロジー株式会社 斉藤 賢哉: 本古い本なのですが、JPAについてはかなり詳しく書いており、基本的な部分はほとんど変わっていないので、今でも参考になる書籍だと思います。
また、JSFについても参考になる部分は多いですが、だいぶ変わっている部分もありますので注意してください。
とても厚い本ですが、2.までに勉強したベースの部分も多いので、そのあたりはザッと読んで、知らなかった部分だけ重点的に読めばよいでしょう。

もう1冊、日本語書籍としては通称「金魚本」があります。こちらも手元に1冊あると安心です。Amazon.co.jp: Beginning Java EE 6 Programmer’s SELECTION 電子書籍: Antonio Goncalves, 株式会社プロシステムエルオーシー: Kindleストア

4.各種Web情報をあたる

書籍には書いていない情報も結構ありますので、ブログなどのWeb情報をあたってみましょう。
僕が特に参考になった情報をご紹介しておきます。

JSF

@kikutaro_さんのブログです。養成本のJava EEパートの著者さんです。
Challenge Java EE !
菊田さんは、実際にJava EEを使って開発をされていました。
JSFだけでなく、リッチコンポーネントのPrimeFacesや、JPAEJBについても書いていらっしゃいますので、参考になる記事は非常に多いです。

Bean Validation

下記の記事を読めば、一通りの知識が得られます。
けっこう長いので、僕は紙に印刷して読んでいました。
JSR 303 Bean Validationで遊んでみるよ! - Yamkazu's Blog

CDI

どの日本語書籍でも、あまり詳しく解説されていません。
上妻さんのブログの、下記の記事を読んでおくとよいでしょう。
CDIをはじめよう - 見習いプログラミング日記
Java EE環境におけるCDIのデフォルト化 - 見習いプログラミング日記

JPA

@megascusさんと@makingさんの資料は目を通しておいたほうがよいでしょう。
金魚本に載ってないJpqlの話 #glassfishjp
はまる!!JPA #glassfish_jp #javaee

JAX-RS

@backpaper0さんのブログが良いでしょう。
JAX-RSを始める #javaee — 裏紙

GlassFish

@khasunumaさんのブログが一番詳しいです。
GlassFish Japan

Java EE全般

kagamihogeの日記
英語の公式情報を、日本語に訳してブログに書いていらっしゃいます。
特にJTAJava Transaction API)の章は参考になりました。
The Java EE 7 TutorialのTransactionsの章をテキトーに訳した - kagamihogeの日記
ただし、全部の章がアップされている訳ではありませんので、ご注意ください。

★2015/01/05追記
こちらにまとまっていました。
The Java EE 7 Tutorialのテキトー翻訳まとめ - Qiita
kagamihogeさん、ご連絡ありがとうございます!

Java EE Advent Calendar

年末恒例のアドベントカレンダーです。2014年は、僕も記事を書きました。
多くの人が記事を書かれていて、気になったタイトルのものを読んでいくとよいでしょう。
Java EE Advent Calendar 2014 - Qiita

公式ドキュメントを読む

特にここ1年くらいで日本語情報が充実してきましたが、できれば英語の公式情報にも目を通しておきましょう。
日本語情報が無い場合には、英語の1次情報をあたるのが最も良い方法です。
OracleJava EE 7公式情報は、こちらにまとめられています。
Home: Java Platform, Enterprise Edition (Java EE) 7 Release 7

また、仕様書であるJSRも重要です。
JSRはこちらから検索できます。
The Java Community Process(SM) Program

まとめ

日本語情報がだいぶ多くなってはきましたが、最終的に頼りになるのは英語の公式情報と、自分での検証です。
上記でご紹介した情報を参考にしながら、足りない部分は英語情報をあたりつつ、ご自分でプログラムを作りながら、少しずつ学習を深めていきましょう。
Java EEは非常にボリュームが大きいのですが、まずはJSFJPACDI(場合によってはEJB)・Bean Validation・JTAあたりを勉強すればよいかと思います。

それでは、Enjoy Java EE 7!!

ラムダ式・Stream APIの理解のポイントは「型」

$
0
0

はじめに

Java SE 8がリリースされて、そろそろ1年が経とうとしています。早いものです。
それはすなわち、Java SE 7のサポート切れが間近に迫っているということでもあります。
そこで今回は、改めてJava SE 8の理解のポイントを解説しようと思います。

ラムダ式・Stream APIの2点が、Java SE 8の目玉となる新機能です。
こんなコードが出てきます。

List<Emp> source = Arrays.asList(
        new Emp(101, "Nishida", Dept.ADMIN, 500000),
        new Emp(102, "Nohira", Dept.SALES, 285000),
        // 以下省略
);
// ラムダ式&Stream API!// EmpのListから、Deptが「SALES」の要素のみ抽出し、名前のみのListに集約する
List<String> result = source.stream()
        .filter(emp -> emp.getDept() == Dept.SALES)
        .map(emp -> emp.getName())
        .collect(Collectors.toList());

はじめて読んだときは「何コレ??」と感じるかもしれません。

しかし、理解するためのポイントがあります。
かつ、Java SE 8の日本語書籍ではほとんど触れられていない事項です。

それは「型」です。
もう少し具体的に言うと、「型推論」と「型変換」です。
この2点について解説します。

型推論ラムダ式

ラムダ式の基本

型推論とは、変数やインスタンスの型を、コンパイラが推測してくれることです。

例えば、Java SE 7からダイヤモンド演算子が導入されました。これは、左辺の型から右辺の型を推論しています。これも型推論の1つです。

// 右辺の型引数は、左辺から推論されるので省略できる!
List<String> list = new ArrayList<>();

Javaのアップデートに関する公式ドキュメント「Java Programming Language Enhancements」では、Java SE 8での強化ポイントとして、ラムダ式に次ぐ2番目に「Improved Type Inference(改良された型推論)」が挙げられています。
Java Programming Language Enhancements

もっと言うと、ラムダ式自体、型推論から成り立っています。

ラムダ式は、端的に言えば「無名クラスを簡単に書く方法」です。*1
SE 7以前では、無名クラスをこのように書く必要がありました。
f:id:MasatoshiTada:20150206140413p:plain

しかし、上記のコードは冗長に感じるでしょう。
右辺のnewの後の「Calculator」の部分は、左辺から推測(=型推論)できるはずです。
また、「calc」というメソッド名や、メソッド引数の型なども、Calculatorインターフェイスメソッドを1つしか持たない(=関数型インターフェイス)ならば、推測可能なはずです。
よって、Java SE 8からは、これらの部分を省略することができます。
f:id:MasatoshiTada:20150206140850p:plain

そして、残った「(引数名リスト)」と「{処理}」を、アロー記号(->)でつなぎます。これがラムダ式です。
f:id:MasatoshiTada:20150206141011p:plain

さらに、処理の部分が1行のみの場合は、{ }とreturnも省略可能です。
f:id:MasatoshiTada:20150206141252p:plain
(引数が1個の場合は、( )も省略可能です)

Java SE 8で強化された型推論

Java SE 8の型推論のすごいところは、型に関する手がかりが何か1つでもあれば、推論可能だということです。
例を挙げます。

Object obj = (a, b) -> a + b;

このコードはコンパイルエラーになります。なぜなら、右辺のラムダ式の型を推論できる手がかりがないからです。
分かるのは、「右辺はラムダ式だ。そしてそれは、2つ引数(型は不明)があって、それらを+演算子で計算(or 文字列結合?)した結果をreturnするメソッドを1つ持っている、関数型インターフェイスラムダ式らしい」ということだけです。

ラムダ式は「インスタンス」です。なので、何がしかの「型」があるはずです。そして、その型は関数型インターフェイスのはずです。

そこで、推論できるようになるための手がかりを与えます。以下のコードはコンパイルが通ります。

// 左辺の型から右辺の型を推論
Calculator c = (a, b) -> a + b;
// キャストから型を推論
Object obj = (Calculator) (a, b) -> a + b;

また、メソッドの引数の型からも推論できます。ラムダ式は基本的に、メソッドの引数に指定することが多いです。

publicvoid hoge(Calculator c) {
    // do shomething
}

// hogeメソッドの引数の型はCalculatorなので、引数のラムダ式の型はCalculatorと推論される
hoge((a, b) -> a + b);

さらに、ジェネリクスの型引数まで含めて、推論可能です。

// 型引数を持つ関数型インターフェイスinterface Function<T, R> {
    R apply(T t);
}

// Functionを引数とするメソッド。T = String、R = Integerpublicvoid fuga(Function<String, Integer> func) {
    System.out.println(func.apply("123"));
}

// fugaメソッドにラムダ式を指定
fuga(s -> Integer.parseInt(s));

ちょっとややこしくなってきましたが、下記と同じです。

Function<String, Integer> func = s -> Integer.parseInt(s);
fuga(func);
// ラムダ式を省略しないで書くとこうなる
Function<String, Integer> func = (String s) -> { return Integer.parseInt(s); };
fuga(func);

つまり、fugaメソッドの引数の型から、ラムダ式のsがString、戻り値がIntegerだと推論してくれます。

このように、そもそもラムダ式型推論によって成り立っています。

型変換とStream API

次に、型変換です。Stream APIでは、型変換が頻繁に発生します。
最初のコードを再掲します。

List<Emp> source = ・・・
List<String> result = source.stream()
        .filter(emp -> emp.getDept() == Dept.SALES)
        .map(emp -> emp.getName())
        .collect(Collectors.toList());

Stream APIのコードは、「1回のStream生成」「0回以上の中間操作」「1回の終端操作」から成り立ちます。
上記のコードでは、「source.stream()」が生成、「filter()」「map()」が中間操作、「collect()」が終端操作です。
これらのそれぞれの処理で、型変換が発生しています。

メソッドチェーンなしで書く

上記のようにメソッドチェーンで書くことが一般的なのですが、これだと型変換が見えにくくなります。
型変換が分かりやすくなるように、メソッドチェーンなしで書き直します。

List<Emp> source = ・・・
// (1) List<Emp>→Stream<Emp>
Stream<Emp> empStream = source.stream();
// (2) Stream<Emp>→Stream<Emp>
Stream<Emp> filteredStream = empStream.filter(emp -> emp.getDept() == Dept.SALES);
// (3) Stream<Emp>→Stream<String>
Stream<String> nameStream = filteredStream.map(emp -> emp.getName());
// (4) Stream<String>→List<String>
List<String> result = nameStream.collect(Collectors.toList());

Streamの生成

まず、List<Emp>からStream<Emp>に変換します。

Stream<Emp> empStream = source.stream();

streamメソッドは、ListのスーパーインターフェイスであるCollectionで定義されており、戻り値のStreamの型引数は、Collectionの型引数と同じ(今回はEmp)と定められています。

filterでの抽出

次に(2)で、DeptがSALESの要素のみを抽出しています。

Stream<Emp> filteredStream = empStream.filter(emp -> emp.getDept() == Dept.SALES);

Stream<Emp>からStream<Emp>に変換されます。
filterメソッドの引数に指定したラムダ式は、java.util.function.Predicateです。

publicinterface Predicate<T> {
    boolean test(T t);
}

Streamインターフェイスのfilterメソッドの引数は、「Predicate predicate」です。*2
つまり、TはempStreamの要素の型(今回はEmp)です。
そして、このラムダ式がempStreamの全要素に適用され、条件に合致した(emp.getDept() == Dept.SALESがtrueとなる)要素のみが、filteredStreamに含まれます。

mapでの変換

このmapメソッドが、大きな理解ポイントになります。
mapメソッドも、filter()と同じくStreamを返すのですが、型引数が変換される可能性があります。
(3)では、Stream<Emp>からStream<String>に変換しています。

Stream<String> nameStream = filteredStream.map(emp -> emp.getName());

map()の引数は、Functionインターフェイスです。

interface Function<T, R> {
    R apply(T t);
}

Stream#map()では、TはStreamの要素の型と定められています(今回はEmp)。
Rはapply()の戻り値の型ですが、これはプログラマが自分で好きに決めることが出来ます。
ちょっと上記のままだと分かりづらいので、ラムダ式の部分を省略せずに書き直します。

Stream<String> nameStream = filteredStream.map( (Emp emp) -> { return emp.getName(); } );

今回の場合は、名前のみを抽出したいので、emp.getName()をreturnしています。
そうすると、このreturn文から「R=String」だと型推論されます。

collectでの集約

最終的には、終端操作であるcollect()で、List<String>に集約しています。

List<String> result = nameStream.collect(Collectors.toList());

collect()の引数は、Collectorというインターフェイス(関数型ではない)です。
つまり、Collectors.toList()はCollector実装クラスのインスタンスを返すメソッドなのですが、これを説明すると力尽きる長くなりますので、@backpaper0さんの下記のブログをご参照ください。
Streamのcollectメソッドを学ぶ — 裏紙

まとめ

少しでも参考になれば幸いです。
ツッコミ大歓迎ですので、何かありましたら是非コメントをいただきたいと思います。

Spring Boot キャンプ予習メモ

$
0
0

この記事について

3月25日(水)、JJUGナイトセミナーの「Spring Bootキャンプ」に参加してきます。
【東京】JJUG ナイト・セミナー「中上級者向け!Spring Bootハンズオン!」3/25(水)開催 | 日本Javaユーザーグループ
予習必須の勉強会なので、予習しててハマったことをメモしときます。
手順はこちら→making/spring-boot-camp-instruction · GitHub

かかった時間

恥ずかしながら、コードをコピペして実行するだけで、3日間で計5~6時間かかりました・・・(^^;
とにかく動かすというところまで出来れば、ハンズオン当日は内容の理解や、さらに試してみたいこと等に集中できるので、予習は出来る限りやっておいたほうがいいと思います。
この記事が参考になれば幸いです。

環境設定など

必要なものはこちら。
spring-boot-camp-instruction/index.rst at master · making/spring-boot-camp-instruction · GitHub
僕の場合は、Windows 7x64JDK 8u31、IntelliJ IDEA 14.1 EAPMaven 3.2.3、CURL 7.33.0です。

第1章 Spring BootでHello World

特にハマりどころは無いと思います。

第2章 OpenCV

ちょっとハマりました。
原因は、プロジェクトを置いているパスでした。パスには日本語・半角スペースは入れない方がいいです。
(追記:半角スペースはOKでした。日本語が入ってるとダメです)
最初は日本語・半角スペースがパスに入っていたので、出力が「Image = null」になったり、レナの画像にDukeが出てこなかったりしました。
あと、実行時にWARNINGが出てきて気になったたので、pom.xmlを少し修正しました。

<!-- 38行目以降 --><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration><!-- コレを追記 --><version>3.2</version></plugin></plugins></build>

第3章 顔変換サービス

CURLコマンド

下記のコマンドは、画像ファイルが置いてあるディレクトリをカレントとしてから実行してください。
(src/main/resourcesなど)

curl -v-Ffile=@lena.png http://localhost:8080/duker > after.png

コマンド実行後は、そのカレントディレクトリに画像が生成されます。

「file=@lena.png」の部分をシングルクオテーションで囲むと、レスポンスで「couldn't open file "lena.png'"」と出てきたので、シングルクオテーションは付けませんでした。
これは、僕がIntelliJ IDEAのTerminalや、Windowsコマンドプロンプトを使っているためです。
Git Bashの場合は、シングルクオテーションは有っても無くても大丈夫でした。

上記のコマンドでうまくいかない場合は、下記のどちらかを試してみてください。

curl -v-Ffile=@lena.png http://localhost:8080/duker.png > after.png

curl -v-Ffile=@lena.png;type=image/png http://localhost:8080/duker > after.png


(追記:2つ目のコマンドは、再確認したら実行できませんでした。お詫びして訂正します)

アップロードする画像と生成する画像は、形式を同じにしないと例外が発生することがあります。
発生しないで正しく画像が生成されることもあるし、ここはよく分かりません(^^;

curl -v-Ffile=@lena.png http://localhost:8080/duker > after.jpg

画像が生成されない

アプリをシャットダウンしないと、after.pngが生成されないことがありました。
(ファイルは出来るが0KB、またはファイルそのものが生成されない)
アプリをシャットダウンすると、画像が正しく生成されました。

恐らくBufferedImageがフラッシュされてないからだと考えて、Appクラスのduker()メソッドで、imageをreturnする直前に「image.flush()」を入れたらいけました。

と思いきや、1回 image.flush()を書いて実行したら、2回目以降はimage.flush()をコメントアウトしても大丈夫(=アプリをシャットダウンしなくても画像が生成される)になりました。
リビルドもしたし、CLASSファイル消去してからビルドもしたし、ここもよく分かりません・・・。

第4章 JMSの導入

HornetQの導入や起動自体には、ハマったところはありませんでした。
最後に、CURLコマンドを10回連続で叩くコマンドがあるんですが、

$ for i in`seq 110`;do curl localhost:8080/send?msg=test;done

これはbashコマンドなので、Windowsコマンドだとこうなります。

>for /L %i in(1,1,10)do(curl localhost:8080/send?msg=test)

第5章 JMSで非同期処理

特にハマりどころは無かったですが、僕の環境だと、JVMのハングアップは起きませんでした。
ただ、第3章と同様、コマンドはシングルクオテーション無しで。

curl -Ffile=@lena.png localhost:8080/queue

第6章 STOMPの導入

特にハマりどころはありませんでした。

第7章 STOMP over WebSocket

import文が記載されていませんが、Base64クラスは「java.util.Base64」のようです。

第8章 WebRTC

特にハマりどころはありませんでした。

第9章 WebRTC+顔変換サービス

特にハマりどころはありませんでした。

第10章以降

Dockerのインストールしてないので、やってません。

以上です。

Spring Bootハンズオンに参加してきました!

$
0
0

今日、こちらのイベントに参加して来ました。
【東京】JJUG ナイト・セミナー「中上級者向け!Spring Bootハンズオン!」3/25(水)開催 | 日本Javaユーザーグループ
@makingさんの資料はこちら。
Spring Bootキャンプ ハンズオン資料 — Spring Bootキャンプ ハンズオン資料 1.0.0-SNAPSHOT ドキュメント
基本的にこちらの資料を基に、ハンズオンは第3章~第7章まで、第8章以降は解説のみでした。

Spring Bootを使うと、JMS・WebSocket・STOMPが簡単に使えることが分かりました。
しかし、予習しといてよかった・・・(^^;

@makingさん、ありがとうございました!この教材はかなりの力作でした。
Spring Boot以外の分野の勉強にもなりますし、一通りやってみるといいと思います!

かなり簡単ですが、今日はここまで。


MVC 1.0(Java EE 8)をとりあえず動かしてみた

$
0
0

MVC 1.0とは?

Java EE 8は、来年(2016年)の第3四半期にリリースが予定されています。
その中に含まれる予定の、アクションベースMVCフレームワークの仕様が「MVC 1.0」です。
参照実装は「Ozark」です。
Ozark, the JavaEE MVC RI — Project Kenai

環境

プロジェクトは[Maven]→[Webアプリケーション]で作ってください。
プロジェクト名は「OzarkSample」とします。

Maven依存関係

プロジェクトを作成したら、pom.xmlに下記の依存関係を追加してください。

<dependencies><dependency><groupId>com.oracle.ozark</groupId><artifactId>ozark</artifactId><version>1.0.0-m01</version><scope>compile</scope></dependency><dependency><groupId>javax</groupId><artifactId>javaee-web-api</artifactId><version>7.0</version><scope>provided</scope></dependency></dependencies>

JAX-RSの有効化

package ozarksample;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("api")
publicclass MyApplication extends Application { 
}

MVC 1.0は、JAX-RSベースのフレームワークです。
javax.ws.rs.core.Applicationのサブクラスを作ると、JAX-RSが有効化されます。
クラスの中身は空でOKです。
@ApplicationPath("api")は、このアプリケーションのURLを指定しています。
つまり「http://localhost:8080/コンテキストパス/api/・・・」となります。

コントローラーの作成

package ozarksample.controller;

import com.oracle.ozark.engine.JspViewEngine;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.mvc.Viewable;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

@Path("hello")
@RequestScopedpublicclass HelloController {
    
    @Injectprivate Models models;
    
    @GET@Controller@Path("{name}")
    public String hello(@PathParam("name") String name) {
        models.put("name", name);
        return"hello.jsp";
    }
    
// コレでもOK//    @GET//    @Controller//    @Path("{name}")//    public Viewable hello(@PathParam("name") String name) {//        models.put("name", name);//        return new Viewable("hello.jsp", models, JspViewEngine.class);//    }
    
}

@Pathは、やはりURLを指定するものです。
はまったのは、このコントローラークラスに@RequestScopedなどのCDIスコープアノテーションをつけないと、実行時に「org.glassfish.hk2.api.UnsatisfiedDependencyException」が出てしまいました。
参考URL:GlassFish 4.0のCDIに潜む罠 - Programming Studio
本来は、コントローラークラスにはスコープアノテーションは付けないようなのですが、今回はとりあえず動かすためにこうしてます。

JSPページ

上記のコントローラーから、フォワードする先のJSPです。
EL式を使って、コントローラーでModelsにputしたnameを表示しています。

<%@pagecontentType="text/html" pageEncoding="UTF-8"%><!DOCTYPE html><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>JSP Page</title></head><body><%-- EL式でnameを表示 --%><h1>Hello, ${name}!!</h1></body></html>

最終的なプロジェクト構成

f:id:MasatoshiTada:20150331164356p:plain

実行してみる

ブラウザで、下記のURLにアクセスしてください。
http://localhost:8080/OzarkSample/api/hello/hogehoge
すると、URLの最後の「hogehoge」の部分が、下記のように表示されます。
f:id:MasatoshiTada:20150331164258p:plain

感想

非常にシンプルで、使いやすいフレームワークだと感じました。
まだ仕様策定中で未完成な部分があるということもありますが、逆に言えばまだクラス数が少ないので、コードリーディングがしやすいです。
今のうちにチェックしておいて、構造を把握しておくのも良いと思います。
僕も、今後も定期的にチェックします。

注意事項

MVC 1.0およびOzarkは、現在も仕様策定や開発の真っ最中であり、今回のサンプルは2015年3月31日現在のものに過ぎません。
後々になると仕様が変わっており、上記のサンプルは動かなくなっている可能性がありますので、ご注意ください。

Ozarkのサンプル

https://github.com/spericas/ozark/tree/jaxrs/ozark-sample
GitHubにあるのですが、このサンプルはどうも古いらしく、現在の実装とはちょっと違っています。

JSR 371(MVC 1.0)Early Draftリリース&簡単に解説。

$
0
0

リリース予定

  • Q3 2014 Expert Group formed
  • Q1 2015 Early Draft ★今はココ!
  • Q3 2015 Public Review
  • Q1 2016 Proposed Final Draft
  • Q3 2016 Final Release

MVC 1.0に限らず、Java EE 8の各技術はだいたいこんな感じのスケジュールのようです。
MVC 1.0は、Q1 2015ギリギリの3月31日でした。

Security APIは間に合わなかったのかな・・・。
The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 375

ゴールとするもの・しないもの

上記のJSRには冒頭に、ゴールとするもの・しないものが明記されています。

ゴールとするものは、下記の5つです。
(あんまり英語得意じゃないので、厳密には正しくないかもしれません^^;)

  1. Java EEの既存技術を活用する。
  2. CDIおよびBean Validationと統合する。
  3. 最初のバージョンでは、MVCアプリケーションのコアを提供し、すべての機能は必ずしもサポートしない。
  4. JAX-RS上で動かす。
  5. JSPとFaceletsのビュー技術をサポートする。

逆に、ゴールとしないものは、下記の4つです。

  1. 新しいビュー技術は定義しない。
  2. Java EE外で、スタンドアロンで動作するMVCはサポートしない。
  3. JAX-RSベースでないRESTはサポートしない。
  4. Java EEでないビュー技術はサポートしない。

要するに、既存のJava EE技術を最大限に活用して、新たに仕様を策定する部分は最小限に留める、と僕は解釈しています。
これは、個人的には非常に賛同します。Java EEは既に膨大な仕様の集まりになっているので。

Ozark独自の拡張機能

https://github.com/spericas/ozark/tree/master/ext
JSP・Facelets以外の、ThymeleafやVelocityなどのビュー技術は、Ozark独自の拡張機能として提供されています。
具体的には、javax.mvc.engine.ViewEngineというインターフェイスがあり、それを実装してXxxViewEngineというクラスを作ることで、各ビュー技術をサポートしています。
JspViewEngineやFaceletsViewEngineは、Ozarkに含まれています。

サンプルコード

MavenからWARでダウンロードして、そのままGlassFishにデプロイすれば動きます。
GroupIdが「com.oracle.ozark.test」のものが、MVC 1.0を使ったサンプルプログラムです。
The Central Repository Search Engine
GitHubにソースもあります。
https://github.com/spericas/ozark/tree/master/test

動くサンプル

上記のWARすべては試していないのですが、下記のサンプルは動きました。

  • book-cdi
  • book-models
  • facelets
  • thymeleaf

動かないサンプルとバリデーション方法

動かなかったのは、validationのサンプルです。
動くには動くのですが、バリデーションで引っかかるような入力をしてSubmitすると、例外画面になってしまいます。
f:id:MasatoshiTada:20150402220031j:plain

ozark/FormController.java at master · spericas/ozark · GitHub
ValidationResultというのが、検証結果を保持する役割を持っているのですが、あらためて、現時点のOzarkのソースコードを一通り確認すると、そのValidationResultに検証結果を詰める処理が、まだ未実装のようでした。
まだEarly Draftなので、これからに期待ですね。
また、現時点のJSRには、検証結果のエラーメッセージをどのようにJSPなどのビューに表示させるかは、まだ明記されていません。

ということで

まだまだ未実装の部分はありますが、これからに期待ですね!引き続きウォッチします。

MVC 1.0ではじめる簡単Java EE開発入門!

$
0
0

この記事の想定読者

  • Struts 1.xの代替技術を模索している方
  • Java EEという言葉は知っているが、中身はあまり知らないという方
  • Java EEってそもそも何?という方

今回は、「そもそもJava EEって何?」「なぜJava EEが注目されているの?」ということを解説するとともに、Java EE 8で導入される「MVC 1.0」を利用して、Java EEの技術で簡単な検索Webアプリの開発する方法を解説します。

これまでのJavaによる開発

これまで、Javaシステム開発を行う際には、複数オープンソースフレームワークを組み合わせて使うことが多かったと思います。
たとえば、Struts 1.x + Spring + Hibernate(or MyBatis)は、良く聞く組み合わせです。
しかし、Struts 1.xは2008年からアップデートされておらず、2013年にはサポート終了が公式にアナウンスされました。
Apache Struts 1 EOL Press Release
また、各フレームワークごとに異なる設定ファイル(XMLなど)があります。いわゆる「XML地獄」です。
さらに、複数の異なるフレームワークを組み合わせるためには特殊な設定(XMLJavaコード)が必要となり、プロジェクトの雛形を作るだけでも相当な工数がかかります。

そもそも、Java EEって?

Java Enterprise Edition(Java EE)は、Webを中心とした大規模システム向けの「標準技術」の「仕様」です。
昔は「J2EE」と呼ばれていて、EJBEnterprise JavaBeans)の重厚長大さで敬遠されていたようです。
そのアンチテーゼとして、Struts・Spring・Hibernateなどが生まれてきた経緯があります。
しかし、Java EEも負けじと進化を続け、かなり実用的なものになってきました。
2006年にはJava EE 5、2009年にはJava EE 6、2013年にはJava EE 7がリリースされ、実用性や開発の生産性が飛躍的に高まっています。
そして、来年(2016年)の第3四半期(おそらく9月末くらい?)には、Java EE 8が登場する予定です。

Java EEの良いところ

総じていえば、「安心して使えて、生産性が高く、開発が楽」ということです。

フルスタックフレームワーク

Webフレームワーク・DIコンテナ・ORマッパー・その他など、システム開発に必要となるものが一括で提供されています。
また、各技術はシームレスに連携できるようになっており、特殊な設定をする必要はありません。
プロジェクトの雛形作成は非常に楽です。

ほぼXMLレス

Java EEの各技術は、ほとんどXMLを書きません。これは、Java SE 5で導入されたアノテーションの活用や、CoC(設定よりも規約)によるものです。

標準技術

Java EEは標準技術なので、Oracle社などの明確なサポートを受けることができます。
また、世界中の人が標準技術を使うことで、ノウハウがどんどん共有されていきます。

Java EEによるWebシステムの基本アーキテクチャ

f:id:MasatoshiTada:20150403125639p:plain

データアクセス

JPAJava Persistence API)がORマッパーです。データベースにアクセスし、CRUD操作を行います。エンティティクラスでデータをやりとりします。

ビジネスロジック

CDI(Context and Dependency Injection)がDIコンテナです。
基本的にはPOJO(Plain Old Java Object)で作りますが、これをCDI管理対象にし、プレゼンテーション層にDIします。
EJBを使うことも可能ですが、EE 7以降、トランザクション管理などもCDIおよびJTAJava Transaction API)でできるようになっており、EJBレスで開発も可能です。
ただし、EJBにしかない機能(タイマーなど)もあり、場合によって使い分ければOKです。
EJBアノテーションベースで、かなり使うのが楽になっています。

プレゼンテーション

HTMLを返すのがMVC(EE 8新機能)・JSFJSONXMLを返すREST Webサービスを構築するのがJAX-RS、リアルタイム通信を行うのがWebSocketです。
MVCはアクションベース、JSFコンポーネントベースです。CSSJavaScriptをガリガリ活用したいならMVC、簡単にリッチな画面を作りたいならJSF、という使い分けになるでしょう。

MVC 1.0 + CDI + JPAで簡単な検索アプリを作る

環境

インストール方法などは、下記の記事をご覧ください。とても楽チンです。
GlassFish 4.0のインストールとNetBeans 8.0の連携 - Java EE 事始め!

作るアプリ

f:id:MasatoshiTada:20150403132156p:plain
製造者のIDを入力したら、検索結果が表示されるWebアプリです。

プロジェクトの作成

NetBeansで[ファイル]→[新規プロジェクト]→[Maven]→[Webアプリケーション]としてください。
プロジェクトができたら、pom.xmlに下記の依存性を追加します。

<dependencies><!-- MVC 1.0の実装。まだEarly Draft版 --><dependency><groupId>com.oracle.ozark</groupId><artifactId>ozark</artifactId><version>1.0.0-m01</version><scope>compile</scope></dependency><!-- JPAの実装 --><dependency><groupId>org.eclipse.persistence</groupId><artifactId>eclipselink</artifactId><version>2.5.2</version><scope>provided</scope></dependency><!-- エンティティ自動生成ツール。自動で追記されるので自分で書く必要はなし --><dependency><groupId>org.eclipse.persistence</groupId><artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId><version>2.5.2</version><scope>provided</scope></dependency><!-- OzarkでThymeleafを使うための拡張 --><dependency><groupId>com.oracle.ozark.ext</groupId><artifactId>ozark-thymeleaf</artifactId><version>1.0.0-m01</version></dependency><!-- Java DBのドライバ --><dependency><groupId>org.apache.derby</groupId><artifactId>derbyclient</artifactId><version>10.11.1.1</version></dependency><!-- Java EE 7 Web ProfileのAPI --><dependency><groupId>javax</groupId><artifactId>javaee-web-api</artifactId><version>7.0</version><scope>provided</scope></dependency></dependencies>

データアクセス

エンティティを作成します。NetBeansを使えば、自動生成が可能です。
ちなみに、「Manufacturer」は「製造者」という意味です。Java DBに最初から入っているテーブルです。

package ozarksample.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entitypublicclass Manufacturer implements Serializable {
    privatestaticfinallong serialVersionUID = 1L;
    @Id@Basic(optional = false)
    @NotNull@Column(name = "MANUFACTURER_ID")
    private Integer manufacturerId;
    @Size(max = 30)
    private String name;
    @Size(max = 30)
    private String addressline1;
    @Size(max = 30)
    private String addressline2;
    ・・・
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "manufacturer", fetch = FetchType.LAZY)
    private List<Product> productList;

    public Manufacturer() {
    }
    
    // 以下、setter/getterなど

また、エンティティを自動生成すると、同時にperisistence.xmlという、JPAの設定ファイルも自動生成されます。
場所はsrc/main/resources/META-INFです。
自動生成されたXMLに、ユニット名、キャッシュ、ログなどの設定を加えるだけです。

<?xml version="1.0" encoding="UTF-8"?><persistence version="2.1"・・・><!-- あとで呼び出す際のユニット名 --><persistence-unit name="ozarkPU"transaction-type="JTA"><provider>org.eclipse.persistence.jpa.PersistenceProvider</provider><jta-data-source>jdbc/sample</jta-data-source><exclude-unlisted-classes>false</exclude-unlisted-classes><!-- キャッシュとバリデーションをOFF --><shared-cache-mode>NONE</shared-cache-mode><validation-mode>NONE</validation-mode><properties><!-- ログ出力の設定など --><property name="eclipselink.logging.level"value="FINE"/><property name="eclipselink.logging.thread"value="true"/><property name="eclipselink.logging.session"value="false"/><property name="eclipselink.logging.timestamp"value="false"/><property name="eclipselink.target-database"value="JavaDB"/></properties></persistence-unit></persistence>

ビジネスロジック

package ozarksample.service;

import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import ozarksample.entity.Manufacturer;

@RequestScopedpublicclass ManufacturerService {
    @PersistenceContext(unitName = "ozarkPU")
    private EntityManager manager;
    
    @Transactional(Transactional.TxType.REQUIRED)
    public Manufacturer findById(Integer id) {
        Manufacturer manufacturer = manager.find(Manufacturer.class, id);
        return manufacturer;
    }
    
    // other methods for CRUD operations
}

EntityManagerがDBアクセスを仲介します。EntityManagerにはCRUD操作用のメソッドが定義されています。また、JPQLという問い合わせ言語、ネイティブSQLの利用も可能です。
@PersistenceContextアノテーションで、EntityManagerをDIします。
クラスについている@RequestScopedは、このクラスをCDI管理対象にするためのものです。次のプレゼンテーション層に、このクラスをDIします。
メソッドに@Transactionalを付けることで、トランザクション管理を自動で行ってくれます。具体的には、対象メソッドにインターセプターをかませることで、メソッド開始前にトランザクションの開始、メソッド終了後にコミット(例外発生時は自動ロールバック)してくれます。

また、ここまでのコードは、プレゼンテーション技術に依存しません。
MVCでもJSFでもJAX-RSでもWebSocketでも、ビジネスロジック以下は変える必要がありません。
そういう意味で、再利用性も高いです。

プレゼンテーション

コントローラーはMVC、ビューは今回はThymeleafで作ります。もちろん、JSPやFacelets(XHTML形式で書く、JSFのビュー)も使えます。
ThymeleafはJava EE標準ではないのですが、独自タグを使わずピュアなHTML5で書けるので、CSSJavaScriptも書きやすくなり、おすすめです。
ただ、Thymeleafは整形式のXHTMLで書く必要があるので、注意してください。
つまり、<br>はダメで、<br/>ならOKです。

JAX-RSの有効化

MVCは、JAX-RSベースのフレームワークですので、まずはJAX-RSを有効化する必要があります。
そのためには、javax.ws.rs.core.Applicationのサブクラスを作ります。クラスの中身は空でOKです。

package ozarksample;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("api")
publicclass MyApplication extends Application {
}
コントローラー

MVCのコントローラーは、ビューの技術に依存しません。これも嬉しいですね。

★追記(2015/04/22)
JSR 371によると、コントローラークラスは、CDI管理ビーンでなければなりません。
下記のコントローラークラスに@RequestScopedが付いているのは、そのためです。

package ozarksample.controller;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import ozarksample.entity.Manufacturer;
import ozarksample.service.ManufacturerService;

@Path("manufacturer")
@RequestScopedpublicclass ManufacturerController {
    @Injectprivate ManufacturerService manufacturerService;
    
    @Injectprivate Models models;

    @GET@Path("input")
    @Controllerpublic String input() {
        return"manufacturer/input.html";
    }
    
    @GET@Path("find")
    @Controllerpublic String findById(@QueryParam("id") Integer id) {
        Manufacturer manufacturer = manufacturerService.findById(id);
        models.put("manufacturer", manufacturer);
        return"manufacturer/result.html";
    }
}
入力画面(input.html)

HTMLやJSPは、デフォルトでは「WEB-INF/views」配下に作成します。サブフォルダを作ることも可能です。
今回は、WEB-INF/views/manufacturerフォルダ内にHTMLを作成します。

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd"><html xmlns="http://www.w3.org/1999/xhtml"      xmlns:th="http://www.thymeleaf.org"><head><title>input</title><metacharset="UTF-8"/></head><body><p>input manufacturer id</p><formaction="./find"method="get"><inputtype="text"name="id"/><inputtype="submit"value="search"/></form></body></html>
検索結果表示画面(result.html)
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd"><html xmlns="http://www.w3.org/1999/xhtml"      xmlns:th="http://www.thymeleaf.org"><head><title>result</title><metacharset="UTF-8"/></head><body><p th:text="${manufacturer.manufacturerId}">Manufacturer ID</p><p th:text="${manufacturer.name}">Manufacturer Name</p></body></html>

実行!

NetBeans上でプロジェクトを右クリックー>[実行]とすると、MavenがプロジェクトをWARにビルドし、GlassFishにデプロイされます。
ブラウザも自動で起動します。

入力画面

f:id:MasatoshiTada:20150403133519p:plain

結果表示画面

f:id:MasatoshiTada:20150403133534p:plain

ここまでのまとめ

僕がこのアプリを作るのにかかった時間は、だいたい10分です。
もちろん僕がJava EENetBeansに慣れているということもありますが、これくらいのアプリであれば非常に簡単です。
もちろん、CDIJPAなどの各技術は奥が深いですし、もっと複雑なことをやろうとすると手間はかかるのですが、それはどんなフレームワークでも一緒ではないでしょうか。

ここまでのコードは、GitHubにアップしておきましたので、ご覧ください。
MasatoshiTada/OzarkSample · GitHub

話はそれますが、NetBeansならGitHub連携もすごく簡単です。一切gitコマンドを打たずにコミット・プッシュできます。
NetBeans IDEを使用してGitHubリポジトリを設定するためのビデオ

Java EEの改善してほしい点

さて、ここまで「Java EE良いよね!」という話をしてきましたが、改善点の無いものなどありませんので、いくつか挙げます。

商用アプリケーションサーバーが出るのに時間がかかる

Java EEは仕様が膨大(今回紹介したものは一部に過ぎません)なため、全機能フル対応した商用のアプリケーションサーバーが出てくるのに、だいたい2年くらいかかっています。
ただ、商用サポートはありませんが、オープンソースGlassFishWildFly(旧JBossオープンソース版)は、タイムリーに出てきます。
特にGlassFishOracleが開発しているため、Java EEの正式リリースと同時に出されます。
また、OracleWebLogic Serverは、EE 7あたりから、少しずつEEの仕様に対応する「段階的対応」を取っています。
Java EE 8は2016年秋リリース予定ですが、特にMVC 1.0はEE 8の目玉機能ですので、WebLogicは真っ先に対応するのではないでしょうか(と、期待しています^^;)。

仕様の進化がスロー

僕の私見ですが、進化の速さ・生産性の高さ・機能の豊富さは、まだSpringや.NETの方が優位だと思います。
これは、Springや.NETは特定企業(Pivotal社・Microsoft社)が主体で開発を進めるのに対して、Javaはコミュニティによる合議制で仕様の策定・開発を進めている点が大きいのでしょう。
ただ、これはどちらが良いとは一概には言えないと思います。
Java後方互換性を大切にしていますし、標準技術による安定性・信頼性を求めるのであれば、Java EEがいいのではないでしょうか。場合によりけり、だと思います。
ただ、最低でも、新規開発でStruts 1.xを使い続けるのは、止めたほうがよいでしょうね(^^;

最後に

長い記事にお付き合いいただき、ありがとうございました。
この記事を読んだ方に、「思ってたよりだいぶ簡単じゃん!Java EEちょっとやってみようかな~」と思っていただければ嬉しいです。

また、4/11(土)のJJUG CCCに、スピーカーとしての登壇が決まりました!
CCCへの登壇は、3回連続3回目になりました。
タイトルは「はまる!JPA(初学者向けライト版)」です。
JPAは初めてという方が対象ですので、他のORマッパーは使ったことあるけど、JPAは全く知らないという方でも大丈夫です。
Sessions / JJUG CCC 2015 Spring(4月11日開催) | 日本Javaユーザーグループ

過去2回の発表資料はこちらです。
JPAの同時実行制御とロック20140518 #ccc_r15 #jjug_ccc
ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

それでは、Enjoy Java EE

JJUG CCC 2015 Springレポート&質疑応答時の訂正事項

$
0
0

おととい、JJUG CCC 2015 Springに参加してきました!

幸運なことに、3回連続でスピーカーに選んでいただけました。テーマは「はまる!JPA(初学者向けライト版)」です。
直前で体調を崩してしまってプレゼンの練習ができず、時間配分ミスしました・・・。
時間が50分間でスライドが80枚だったんですが、最初は急ぎすぎて20分で前半40枚が終わってしまい、後半は明らかにスローペースになりました。
うーむ、何回やっても、反省点が残ります。でも、次回もチャレンジしようと思います!

質疑応答時の訂正事項

質疑応答の時間でご質問いただいた事項について、僕の回答に訂正があります。
セッション終了直後にTwitterでもつぶやいたのですが、改めて書いておきます。
JPAにはバルク(一括)インサートのメソッドはありますか?」というご質問に対して、「あります」とお答えしましたが、申し訳ありません、僕の記憶違いでした。
バルクインサートのメソッドは、JPAのEntityManagerには存在しません。訂正してお詫び申し上げます。
http://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html
バルクインサート専用のメソッドは有りませんが、下記のようなEclipseLinkの独自プロパティは存在します。

<!-- None・Buffered・JDBC・OracleJDBCの4種類 --><property name="eclipselink.jdbc.batch-writing"value="JDBC"/><!-- 整数でサイズを指定 --><property name="eclipselink.jdbc.batch-writing.size"value="50"/>

セッションの感想など

体調を崩したこともあって、午後からの参加にしました。
僕が聞いたセッションは下記の5つです。

  • AB-3 大規模な負荷でもドキドキしないためのJava EE
  • G-4 あなたとAndroid!? 今すぐダウンロード!~Android開発で変わる SIerJava技術事情について~
  • G-5 Grails第3章 進化したSpring-bootベースフレームワーク
  • M-6 MQTTの使いどころ 〜軽量プロトコルで低コストにデータを集めよう〜
  • F-7 JobStreamerではじめるJavaBatchクラウド分散実行

特に興味深かったのは、G-4とF-7です。
G-4は、Android開発が一般的なサーバーサイドWeb開発と違って色々な制約(容量やパフォーマンスなど)があり、それにどのように対応していったのかが紹介されていました。
F-7は、XMLでジョブを定義しなければならないjBatchを、GUIでジョブ定義できるようにした「JobStreamer」が紹介されていました。

懇親会

@backpaper0さんや@s_kozakeさんをはじめ、Twitterでしか見たことのなかった方々と、リアルにお話することができました!とても楽しかった!記念写真も撮ったりして。
もっと、Javaコミュニティに貢献できるようになりたいなあ。

簡単ですが、以上です。まずは体調を戻さないと・・・(^^;

Java EE 7のトランザクション管理(JTA)概要

$
0
0

以前、MVC 1.0の記事で、Java EEトランザクション管理について書きました。
MVC 1.0ではじめる簡単Java EE開発入門! - Java EE 事始め!

@RequestScopedpublicclass ManufacturerService {
    @PersistenceContext(unitName = "ozarkPU")
    private EntityManager manager;
    
    @Transactional(Transactional.TxType.REQUIRED) // JTAトランザクション管理public Manufacturer findById(Integer id) {
        Manufacturer manufacturer = manager.find(Manufacturer.class, id);
        return manufacturer;
    }
}

JTAについては、@den2snさんが以前にブログに書いていらっしゃいましたので、詳しいことはこちらをご参照ください。
Java EE 7のTransactionalアノテーションを試してみる - DENの思うこと


要約すると、こんな感じです。

  1. メソッド内で非チェック例外(RuntimeExceptionおよびそのサブクラス)が発生すると、ロールバックされる
  2. メソッド内でチェック例外(RuntimeExceptionおよびそのサブクラスでない例外)が発生すると、ロールバックされない
  3. @TransactionアノテーションのrollbackOn属性に例外クラス(チェック例外・非チェック例外に関わらず)を指定すると、その例外が発生時にロールバックされる

少し、@den2snさんの記事の補足をします。
最後のほうに、GlassFishのバグとして、rollbackOn = Exception.classと指定しても、そのサブクラスのClassNotFoundExceptionを発生させてもロールバックされない、という現象が書かれていました。
ブログの日付を見る限りでは、@den2snさんはおそらくGlassFish 4.0で試されていたものと思われます。

今回、僕が同様の実験をGlassFish 4.1で行ったところ、このバグは直っていました。
GlassFish 4.1は正しく動きます。

簡単ですが、今回はここまで。

Viewing all 88 articles
Browse latest View live