20190330のJavaに関する記事は11件です。

Spring-Data-JPAは便利だけど使えるJPQLの機能がはっきりしない。…ので、整理しました。

Spring Boot/Spring Data JPAで使用できるJPQLの機能を整理してみました。
主にこちらの記事を参考にさせていただきました。有難うございました。
Spring Data JPAによるデータアクセス徹底入門 #jsug

■一般的なJPQLではEntityManagerを使います。

JPQL_Original_Use.java
//JPQL オリジナルの使用方法
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;

EntityManagerFactory emf;
EntityManager em;
em = emf.createEntityManager();

TypedQuery<Product> query = em.createQuery(
"SELECT p FROM Product p WHERE p.id <= :maxId ORDER BY p.id", Product.class);
query.setParameter("maxId", 5);
List<Product> productList = query.getResultList();

■Spring Data JPAでは支援機能のおかげでEntityManagerは不要です。
※JpaRepositoryを継承することで下記の支援機能が使用できます。
<1>JpaRepositoryに既に定義されているメソッド群がそのまま使用できます。
  findById(id), findAll(pageable), save(entity), delete(entity)等
<2>@Query,@Modifyingアノテーション
  ・Repositoryインターフェースにメソッドを作成。@Queryアノテーションを付加してJPQLを記述すれば、EntityManagerは不要です。
  ・nativeQuery=trueとすればネイティブSQLも書けます。
  ・@Queryで更新系クエリーを使う場合は、@Modifyingが必要です。
<3>命名規約に従ってメソッド名を記述すれば@QueryでJPQLを記述する必要はありません。
<4>Date and Time API (JSR 310)対応
  Jsr310JpaConverterクラス:LocalDateなどのAttributeConverterが提供されています。

<2>JPQL_SpringDataJPA_Use.java
@Query("SELECT p FROM Product p WHERE p.id <= :maxId ORDER BY p.id")
List<Product> findByMaxIdOrderById(@Param("maxId")Integer maxId)
<3>JPQL_AutoGenerated.java
// 命名規約に従ったメソッド名(@QueryでJPQLを記述する必要なし)
// Product.nameに引数の文字列が含まれているものを検索する。
List<Product> findByNameContaining(String keyword);

<3-補足> メソッド名に使えるキーワード例
And, Or, Between, LessThan, LessThanEqual, Like, StartWith, EndWith, Containing, OrderBy, ...
詳細はこちらを参照「Spring Data JPA - Reference Documentation/ 5.3.2. Query Creation」

■(参考)Spring Data JPA 公式ドキュメント
Spring Data JPA - Reference Documentation
■(参考)TERASOLUNA Server Framework for Java (5.x) Development Guideline
6.3. データベースアクセス(JPA編)

■(参考)JPA実装ライブラリ
Hibernate : Spring Data JPAは基本的にこれを使用。
※JPAの参照実装であるEclipseLinkではありません。
※ライブラリ "org.springframework.boot/spring-boot-starter-data-jpa"

(参考)JPAのVersionとJSR(Java Specification Request)
  1. JPA 2.0 (JSR 317) - This version was released in the last of 2009. Following are the important features of this version: -
    It supports validation
    It expands the functionality of object-relational mapping
    It shares the object of cache support

  2. JPA 2.1 (JSR 338) - The JPA 2.1 was released in 2013 with the following features: -
    It allows the fetching of objects
    It provides support for criteria update/delete
    It generates a schema

  3. JPA 2.2 (JSR 338) - The JPA 2.2 was released in 2017. Some of its important features are: -
    It supports Java 8 Date and Time
    It provides @Repeatable annotation that can be used when we want to apply the same annotations to a declaration or type use
    It allows JPA annotation to be used in meta-annotations
    It provides an ability to stream a query result

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Elastic BeanstalkにSpring Bootアプリケーションをデプロイ

Spring Bootを使ったJavaアプリケーションをElastic Beanstalk(AWSのPaaS)上にデプロイする手順をざっくりおさらいします。
初投稿なので、至らないところもあるかとは思いますが、ご了承ください。

BeanstalkへのデプロイはAWSのコンソールからでもできるのですが、今回はCIツールからも実行したいので、CLI(EB CLI)を使います。

前提

今回はBeanstalkの設定手順を説明します。
SpringBootアプリの作成については割愛します。
ビルドツールはgradleです。

環境

Mac OSX10.14.3

EB CLIのインストール

homebrewでBeanstalkのCLIをインストールします。

$ brew install awsebcli

AWS CLIのインストール

Access KeyとSecret Access Keyの設定を保存するためにAWSのCLIもインストールします。

$ brew install awscli

アクセスキー設定

$ aws configure
AWS Access Key ID [None]: {アクセスキーを入力}
AWS Secret Access Key [None]: {シークレットアクセスキーを入力}

プロジェクト設定

デプロイはプロジェクトのディレクトリで行います。

$ cd workspace/ebdemo/demo

初めにプロジェクトの設定です。

$ eb init

対話型で設定していきます。

リージョン
Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) cn-northwest-1 : China (Ningxia)
14) us-east-2 : US East (Ohio)
15) ca-central-1 : Canada (Central)
16) eu-west-2 : EU (London)
17) eu-west-3 : EU (Paris)
18) eu-north-1 : EU (Stockholm)
(default is 3): 9
アプリケーション
Select an application to use
1) [ Create new Application ]
(default is 1):

Enter Application Name
(default is "demo"):
Application demo has been created.
プラットフォーム→SpringBootの場合はJava
Select a platform.
1) Node.js
2) PHP
3) Python
4) Ruby
5) Tomcat
6) IIS
7) Docker
8) Multi-container Docker
9) GlassFish
10) Go
11) Java
12) Packer
(default is 1): 11

Select a platform version.
1) Java 8
2) Java 7
(default is 1):
SSH
Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n): Y

Select a keypair.
1) key
2) [ Create new KeyPair ]
(default is 2): 2

デプロイ

デプロイの前にアプリケーションのjarをプロジェクトルートにコピーします。

$ cp build/libs/demo-0.0.1-SNAPSHOT.jar .

デプロイします。

$ eb create
Enter Environment Name
(default is demo-dev):
Enter DNS CNAME prefix
(default is demo-dev):

Select a load balancer type
1) classic
2) application
3) network
(default is 2):

数分かかります。

動作確認

動いてます。

$ curl http://demo-dev.ap-northeast-1.elasticbeanstalk.com
Hello, World!

2回目以降のデプロイ

2回目以降は以下のコマンドだけでデプロイできます。
SSH、アクセスキー等設定しておけば、CIツールから自動デプロイ可能です。

$ gradle clean build test
$ cp build/libs/demo-0.0.1-SNAPSHOT.jar .
$ eb deploy
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Maven Failsafeプラグインを使って、結合テストを自動化

Maven Failsafe Pluginを利用すると結合テストを自動化できることが分かったため、備忘録として記載する。

例えば、下記のようなテストを自動化したい場合に使える

  • サーブレット、フィルターにてレスポンス情報を作成するアプリケーションに対して、リクエストを投げ、想定通りのレスポンスが返却されることを確認する。

サンプルプログラム

環境

  • Apache Tomcat
  • JUnit

アプリケーションの実装

  • 下記MavenアーキタイプにてMavenプロジェクトを作成
    image.png

  • 下記の形で依存関係を定義

pom.xml
    <dependencies>
        <!-- Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- クライアント -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.28</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>2.28</version>
            <scope>test</scope>
        </dependency>
        <!-- JUnit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 下記ソースコードを用意
DemoServlet.java
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/demo")
public class DemoServlet extends HttpServlet {

    private static final long serialVersionUID = 2564544155028547344L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getWriter().println("body");
    }
}

DemoFilter.java
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

@WebFilter("/demo")
public class DemoFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response);
        wrapper.setHeader("Header-Name", "value");
        chain.doFilter(request, wrapper);
    }

    @Override
    public void destroy() {
    }
}
  • 下記テストコードを用意
DemoIT.java
import static org.junit.Assert.assertEquals;

import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;

import org.junit.Test;

public class DemoIT {

    @Test
    public void test() {
        // サーバーへリクエスト送信
        Response response = ClientBuilder.newClient().target("http://localhost:8080/sample-it/demo").request().get();
        // フィルターが想定通り動作していることを確認
        assertEquals("value", response.getHeaderString("Header-Name"));
        // サーブレットが想定通り動作していることを確認
        assertEquals("body", response.readEntity(String.class).trim());
    }
}

Failsafeプラグインを定義

あとは、

  • アプリケーションサーバーを起動
  • サーバーへアプリケーションをデプロイ
  • テストクラスを実行
  • アプリケーションサーバーを停止

の流れで処理を自動実行させるようにする。

そのために、Failsafeプラグインを利用する。
(Failsafeプラグインはテスト実行を制御できるプラグイン。上記のようにテスト実行の前処理や後処理を定義することができる。)
サーバーの起動・アプリケーションのデプロイは、Failsafeプラグインではできないため、
Apache Tomcat Maven pluginを利用。
以下が設定。

pom.xml
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>3.0.0-M3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- Tomcat操作用プラグイン -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <executions>
                    <!-- テスト実行前処理 -->
                    <execution>
                        <id>run</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <!-- Tomcat起動・アプリケーションのデプロイ -->
                            <goal>run</goal>
                        </goals>
                        <!-- run以降のゴールが実行できるようにするための設定 -->
                        <configuration>
                            <fork>true</fork>
                        </configuration>
                    </execution>
                    <!-- テスト実行後処理 -->
                    <execution>
                        <id>shutdown</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <!-- Tomcat停止 -->
                            <goal>shutdown</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

テストは下記コマンドを実行すれば行うことができる。

mvn clean verify

Spring Boot系はSprintBootTestを使えば、結合テストも自動化できそうなのでFailsafeプラグインを利用する価値はないかもしれない。ただし、Spring MVCなどサーバーにデプロイしないといけないアプリケーションの場合は、本プラグインを用いれば自動化することができる。アジャイルなど仕様変更が多く発生する場合は使えそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSVファイルの差分を出力するツール

CSVDiff.java
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * CSV差分出力
 */
public class CSVDiff {

    private String split = null;

    /**
     * 区切り文字
     */
    private String delimiter = null ;

    /**
     * キー項目のインデックス
     * ※0始まり
     */
    private List<Integer> keyIndex = null;

    /**
     * 取込ファイル配置フォルダ
     */
    private String inputDir = null ;

    /**
     * 取込ファイル1
     */
    private String inputFile1 = null ;

    /**
     * 取込ファイル2
     */
    private String inputFile2 = null ;

    /**
     * 差分ファイル出力フォルダ
     */
    private String outputDir = null ;

    /**
     * */
    private List<String[]> recordList1 = null;

    /**
     * */
    private List<String[]> recordList2 = null;

    private List <String>  outputList  = null ;

    /**
     * コンストラクタ
     * @param delimiterType 区切り文字種別(1:カンマ 1以外:タブ)
     * @param args          キー項目のインデックス
     * @param inputDir      取込ファイル配置フォルダ
     * @param inputFile1    取込ファイル1
     * @param inputFile2    取込ファイル2
     * @param outputDir     差分ファイル出力フォルダ
     */
    public CSVDiff(String delimiterType, String args, String inputDir, String inputFile1, String inputFile2,String outputDir) {

        //================================================================================
        // 1.区切り文字種別判定
        //================================================================================
        if ("1".equals(delimiterType)) {
            this.delimiter = ",";
        } else {
            this.delimiter = "\t";
        }
        this.split = "*" + this.delimiter + "*" + this.delimiter + "*";

        //================================================================================
        // 2.レコード比較時のキー項目インデックスを配列に変換する
        //================================================================================
        this.keyIndex = new ArrayList<Integer> ();
        String arr[] = args.split(",");
        for (String item : arr) {
            int index = Integer.parseInt(item);
            if (! this.keyIndex.contains(index) ) {
                this.keyIndex.add(index);
            }
        }

        //================================================================================
        // 3.各種フォルダ名、ファイル名を設定
        //================================================================================
        this.inputDir   = inputDir;
        this.outputDir  = outputDir;
        this.inputFile1 = inputFile1;
        this.inputFile2 = inputFile2;
    }

    /**
     *
     * @param outputType
     * @param array1
     * @param array2
     * @return
     */
    private String makeRecord (int outputType,String[] array1 ,String[] array2) {
        StringBuilder outputRecord = new StringBuilder() ;

        if (outputType == 0) {
            //================================================================================
            // レコード1=レコード2
            // → レコード1、レコード2を出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード1出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                if (i == 0) {
                    outputRecord.append(array1[i]);
                } else {
                    outputRecord.append(this.delimiter + array1[i]);
                }
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード2出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter + array2[i]);
            }

        } else if (outputType == 1) {
            //================================================================================
            // レコード1<レコード2
            // → レコード1のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード1出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                if (i == 0) {
                    outputRecord.append(array1[i]);
                } else {
                    outputRecord.append(this.delimiter + array1[i]);
                }
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード2出力(カンマorタブのみ)
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                outputRecord.append(this.delimiter);
            }

        }  else if (outputType == 2) {
            //================================================================================
            // レコード1>レコード2
            // → レコード2のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード1出力(カンマorタブのみ)
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter);
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
//            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード2出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter + array2[i]);
            }
       }

        return outputRecord.toString();

    }


    /**
     * CSV差分リスト出力
     * @return 0:正常終了 1:異常終了
     */
    protected int outputCSVDiff ()  {
        boolean isError = false ;

        // --------------------------------------------------------------------------------
        // (1)取込・出力ファイル、フォルダ存在チェック
        // --------------------------------------------------------------------------------
        if (this.checkFileExists() != 0) {
            System.err.println("取込・出力用のファイルまたはフォルダが存在しません");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (1)ファイル読込
        // --------------------------------------------------------------------------------
        if (this.readFile() != 0) {
            System.err.println("ファイル読込エラー");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (2)出力用リスト生成
        // --------------------------------------------------------------------------------
        this.outputList = new ArrayList<String> () ;
        int i_file1 = 0 ;
        int i_file2 = 0 ;

        while (i_file1 < this.recordList1.size() && i_file2 < this.recordList2.size()) {
            String[] record1 = this.recordList1.get(i_file1);
            String[] record2 = this.recordList2.get(i_file2);
            String   outputRecord = null ;


            if (record1.length != record2.length) {
                System.err.println("ファイル1とファイル2でレコード数に相違があります");
                isError = true ;
                break ;
            }

            //========================================================================
            // キー項目一致判定
            //========================================================================
            int diffType = 0 ;
            for (int keyIndex :this.keyIndex) {

                if (record1[keyIndex].compareTo(record2[keyIndex]) < 0 ) {
                    // ファイル1レコード < ファイル2レコード
                    diffType = 1;
                    break;
                } else if (record1[keyIndex].compareTo(record2[keyIndex]) > 0 ) {
                    // ファイル1レコード > ファイル2レコード
                    diffType = 2;
                    break;
                }
            }

            if (diffType == 0) {
                // ファイル1レコード=ファイル2レコード
                outputRecord = this.makeRecord(diffType, record1, record2) ;
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
                i_file2 ++;

            } else if (diffType == 1) {
                // ファイル1レコード<ファイル2レコード
                outputRecord = this.makeRecord(diffType, record1, null);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
            } else {
                // ファイル1レコード>ファイル2レコード
                outputRecord = this.makeRecord(diffType, null, record2);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file2 ++;
            }
        } // end-of-while

        if (isError) { return 1; }

        while (i_file1 < this.recordList1.size() ) {
            String[] record1 = this.recordList1.get(i_file1);
            String outputRecord = this.makeRecord(1, record1, null);
            System.out.println(outputRecord);
            this.outputList.add(outputRecord);
            i_file1 ++;
        }

        while (i_file2 < this.recordList2.size()) {
            String[] record2 = this.recordList2.get(i_file2);
            String outputRecord = this.makeRecord(2, null, record2) ;
            System.out.println(outputRecord);
            this.outputList.add(outputRecord);
            i_file2 ++;
        }

        // --------------------------------------------------------------------------------
        // (3)ファイル出力
        // --------------------------------------------------------------------------------
        this.writeFile();


         return 0 ;
    }

    private void writeFile () {
        FileOutputStream fos  = null;
        OutputStreamWriter osw = null;
        // --------------------------------------------------------------------------------
        // (1)出力ファイルタイムスタンプ生成
        // --------------------------------------------------------------------------------
        Timestamp timestamp    = new Timestamp(System.currentTimeMillis());
        SimpleDateFormat sdf   = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = "csvdiff_"+sdf.format(timestamp)+".txt" ;

        try {
            fos = new FileOutputStream(this.outputDir+"/"+fileName);
            osw = new OutputStreamWriter(fos,"Shift_JIS");

            for (String record : this.outputList) {
                osw.write(record + "\r\n") ;
            }
            osw.close();
            fos.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (osw != null ) {
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null ) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        }


    }

    /**
     * 取込・出力ファイル、フォルダの存在チェック
     * @return 0: 取込・出力ファイル、フォルダあり 1: 取込・出力ファイル、フォルダのいずれか存在しない
     */
    private int checkFileExists () {

        // --------------------------------------------------------------------------------
        // (1)取込ファイル1存在チェック
        // --------------------------------------------------------------------------------
        File file1 = new File(this.inputDir+"/"+this.inputFile1) ;
        if (!file1.exists()) {
            System.err.println("エラー -> 取込ファイル1『"+this.inputDir+"/"+this.inputFile1+"』が存在しません!!");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (2)取込ファイル2存在チェック
        // --------------------------------------------------------------------------------
        File file2 = new File(this.inputDir+"/"+this.inputFile2) ;
        if (!file2.exists()) {
            System.err.println("エラー -> 取込ファイル2『"+this.inputDir+"/"+this.inputFile2+"』が存在しません!!");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (3)出力ファイル配置フォルダチェック
        // --------------------------------------------------------------------------------
        File outputDir = new File(this.outputDir) ;
        if (!outputDir.exists()) {
            System.err.println("エラー -> 出力フォルダ『"+this.outputDir+"』が存在しません!!");
            return 1;
        }
        return 0 ;
    }


    /**
     * ファイルを読み込んで配列リストに設定する
     * @return 0:読込OK 1:読込NG
     */
    private int readFile() {
        int retVal = 0 ;
        File file1 = new File(this.inputDir+"/"+this.inputFile1) ;
        File file2 = new File(this.inputDir+"/"+this.inputFile2) ;

        try {
            this.recordList1 = this.makeRecordList(file1);
            this.recordList2 = this.makeRecordList(file2);
        } catch (Exception e) {
            e.printStackTrace();
            retVal = 1;
        }
        return retVal;
    }

    /**
     * CSVファイル内容を配列リストに詰め替える
     * @param file
     * @return
     * @throws Exception
     */
    private List<String[]> makeRecordList (File file) throws Exception {
        boolean isError = false;
        List<String[]> recordList  = new ArrayList<String[]>() ;

        FileInputStream   fis  = null;
        InputStreamReader isr  = null ;
        BufferedReader    br   = null ;

        try {
            fis   = new FileInputStream(file);
            isr   = new InputStreamReader(fis, Charset.forName("MS932"));
            br    = new BufferedReader(isr);

            int i = 1 ;
            String record = null ;
            while((record = br.readLine()) != null) {
                String[] recordArray = record.split(this.delimiter);

                System.out.println("["+i+"]行目:"+Arrays.toString(recordArray));
                recordList.add(recordArray);
//                System.out.println("["+i_file1+"]行目:"+record);
                i ++ ;
            }

        } catch(FileNotFoundException e ) {
            e.printStackTrace();
            isError = true;
        } catch(IOException e ) {
            e.printStackTrace();
            isError = true;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
        }
        if (isError) { throw new Exception(); }

        return recordList ;

    }

以下は実行クラス

CSVDiffExecute.java
/**
 * CSV差分出力実行クラス
 *
 */
public class CSVDiffExecute {
    /**
     * メインメソッド
     * @param args [0] 区切り文字種別        (1:カンマ 1以外:タブ)
     *              [1] キー項目のインデックス(半角カンマ区切りで複数指定可能)
     *              [2] 取込ファイル配置フォルダ
     *              [3] 取込ファイル1
     *              [4] 取込ファイル2
     *              [5] 差分ファイル出力フォルダ
     */
    public static void main(String[] args) {
        //================================================================================
        // 引数チェック
        //================================================================================
        if (args.length != 6) {
            System.err.println("エラー->引数の数が不足しています。");
            System.exit(1);
        }
        //================================================================================
        // CSV差分出力実行
        //================================================================================
        CSVDiff csvDiff = new CSVDiff(args[0], args[1], args[2], args[3], args[4], args[5]);
        csvDiff.outputCSVDiff();
    }
}


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSVファイルの差分を出力するツールをつくってみた

ファイルの差分を比較するツールは沢山出回っているものの
自分が欲しいと思えるものがなかったので
2つのCSVファイルの差分を出力するツールをつくってみた。

引数でキーとなる行番号を指定して、
キーの値が異なれば別行に出力するシンプルなツールです。

CSVDiff.java
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * CSV差分出力
 */
public class CSVDiff {

    private String split = null;

    /**
     * 区切り文字
     */
    private String delimiter = null ;

    /**
     * キー項目のインデックス
     * ※0始まり
     */
    private List<Integer> keyIndex = null;

    /**
     * 取込ファイル配置フォルダ
     */
    private String inputDir = null ;

    /**
     * 取込ファイル1
     */
    private String inputFile1 = null ;

    /**
     * 取込ファイル2
     */
    private String inputFile2 = null ;

    /**
     * 差分ファイル出力フォルダ
     */
    private String outputDir = null ;

    /**
     * */
    private List<String[]> recordList1 = null;

    /**
     * */
    private List<String[]> recordList2 = null;

    private List <String>  outputList  = null ;

    /**
     * コンストラクタ
     * @param delimiterType 区切り文字種別(1:カンマ 1以外:タブ)
     * @param args          キー項目のインデックス
     * @param inputDir      取込ファイル配置フォルダ
     * @param inputFile1    取込ファイル1
     * @param inputFile2    取込ファイル2
     * @param outputDir     差分ファイル出力フォルダ
     */
    public CSVDiff(String delimiterType, String args, String inputDir, String inputFile1, String inputFile2,String outputDir) {

        //================================================================================
        // 1.区切り文字種別判定
        //================================================================================
        if ("1".equals(delimiterType)) {
            this.delimiter = ",";
        } else {
            this.delimiter = "\t";
        }
        this.split = "*" + this.delimiter + "*" + this.delimiter + "*";

        //================================================================================
        // 2.レコード比較時のキー項目インデックスを配列に変換する
        //================================================================================
        this.keyIndex = new ArrayList<Integer> ();
        String arr[] = args.split(",");
        for (String item : arr) {
            int index = Integer.parseInt(item);
            if (! this.keyIndex.contains(index) ) {
                this.keyIndex.add(index);
            }
        }

        //================================================================================
        // 3.各種フォルダ名、ファイル名を設定
        //================================================================================
        this.inputDir   = inputDir;
        this.outputDir  = outputDir;
        this.inputFile1 = inputFile1;
        this.inputFile2 = inputFile2;
    }

    /**
     *
     * @param outputType
     * @param array1
     * @param array2
     * @return
     */
    private String makeRecord (int outputType,String[] array1 ,String[] array2) {
        StringBuilder outputRecord = new StringBuilder() ;

        if (outputType == 0) {
            //================================================================================
            // レコード1=レコード2
            // → レコード1、レコード2を出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード1出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                if (i == 0) {
                    outputRecord.append(array1[i]);
                } else {
                    outputRecord.append(this.delimiter + array1[i]);
                }
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード2出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter + array2[i]);
            }

        } else if (outputType == 1) {
            //================================================================================
            // レコード1<レコード2
            // → レコード1のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード1出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                if (i == 0) {
                    outputRecord.append(array1[i]);
                } else {
                    outputRecord.append(this.delimiter + array1[i]);
                }
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード2出力(カンマorタブのみ)
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                outputRecord.append(this.delimiter);
            }

        }  else if (outputType == 2) {
            //================================================================================
            // レコード1>レコード2
            // → レコード2のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード1出力(カンマorタブのみ)
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter);
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
//            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード2出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter + array2[i]);
            }
       }

        return outputRecord.toString();

    }


    /**
     * CSV差分リスト出力
     * @return 0:正常終了 1:異常終了
     */
    protected int outputCSVDiff ()  {
        boolean isError = false ;

        // --------------------------------------------------------------------------------
        // (1)取込・出力ファイル、フォルダ存在チェック
        // --------------------------------------------------------------------------------
        if (this.checkFileExists() != 0) {
            System.err.println("取込・出力用のファイルまたはフォルダが存在しません");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (1)ファイル読込
        // --------------------------------------------------------------------------------
        if (this.readFile() != 0) {
            System.err.println("ファイル読込エラー");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (2)出力用リスト生成
        // --------------------------------------------------------------------------------
        this.outputList = new ArrayList<String> () ;
        int i_file1 = 0 ;
        int i_file2 = 0 ;

        while (i_file1 < this.recordList1.size() && i_file2 < this.recordList2.size()) {
            String[] record1 = this.recordList1.get(i_file1);
            String[] record2 = this.recordList2.get(i_file2);
            String   outputRecord = null ;


            if (record1.length != record2.length) {
                System.err.println("ファイル1とファイル2でレコード数に相違があります");
                isError = true ;
                break ;
            }

            //========================================================================
            // キー項目一致判定
            //========================================================================
            int diffType = 0 ;
            for (int keyIndex :this.keyIndex) {

                if (record1[keyIndex].compareTo(record2[keyIndex]) < 0 ) {
                    // ファイル1レコード < ファイル2レコード
                    diffType = 1;
                    break;
                } else if (record1[keyIndex].compareTo(record2[keyIndex]) > 0 ) {
                    // ファイル1レコード > ファイル2レコード
                    diffType = 2;
                    break;
                }
            }

            if (diffType == 0) {
                // ファイル1レコード=ファイル2レコード
                outputRecord = this.makeRecord(diffType, record1, record2) ;
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
                i_file2 ++;

            } else if (diffType == 1) {
                // ファイル1レコード<ファイル2レコード
                outputRecord = this.makeRecord(diffType, record1, null);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
            } else {
                // ファイル1レコード>ファイル2レコード
                outputRecord = this.makeRecord(diffType, null, record2);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file2 ++;
            }
        } // end-of-while

        if (isError) { return 1; }

        while (i_file1 < this.recordList1.size() ) {
            String[] record1 = this.recordList1.get(i_file1);
            String outputRecord = this.makeRecord(1, record1, null);
            System.out.println(outputRecord);
            this.outputList.add(outputRecord);
            i_file1 ++;
        }

        while (i_file2 < this.recordList2.size()) {
            String[] record2 = this.recordList2.get(i_file2);
            String outputRecord = this.makeRecord(2, null, record2) ;
            System.out.println(outputRecord);
            this.outputList.add(outputRecord);
            i_file2 ++;
        }

        // --------------------------------------------------------------------------------
        // (3)ファイル出力
        // --------------------------------------------------------------------------------
        this.writeFile();


         return 0 ;
    }

    private void writeFile () {
        FileOutputStream fos  = null;
        OutputStreamWriter osw = null;
        // --------------------------------------------------------------------------------
        // (1)出力ファイルタイムスタンプ生成
        // --------------------------------------------------------------------------------
        Timestamp timestamp    = new Timestamp(System.currentTimeMillis());
        SimpleDateFormat sdf   = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = "csvdiff_"+sdf.format(timestamp)+".txt" ;

        try {
            fos = new FileOutputStream(this.outputDir+"/"+fileName);
            osw = new OutputStreamWriter(fos,"Shift_JIS");

            for (String record : this.outputList) {
                osw.write(record + "\r\n") ;
            }
            osw.close();
            fos.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (osw != null ) {
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null ) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        }


    }

    /**
     * 取込・出力ファイル、フォルダの存在チェック
     * @return 0: 取込・出力ファイル、フォルダあり 1: 取込・出力ファイル、フォルダのいずれか存在しない
     */
    private int checkFileExists () {

        // --------------------------------------------------------------------------------
        // (1)取込ファイル1存在チェック
        // --------------------------------------------------------------------------------
        File file1 = new File(this.inputDir+"/"+this.inputFile1) ;
        if (!file1.exists()) {
            System.err.println("エラー -> 取込ファイル1『"+this.inputDir+"/"+this.inputFile1+"』が存在しません!!");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (2)取込ファイル2存在チェック
        // --------------------------------------------------------------------------------
        File file2 = new File(this.inputDir+"/"+this.inputFile2) ;
        if (!file2.exists()) {
            System.err.println("エラー -> 取込ファイル2『"+this.inputDir+"/"+this.inputFile2+"』が存在しません!!");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // (3)出力ファイル配置フォルダチェック
        // --------------------------------------------------------------------------------
        File outputDir = new File(this.outputDir) ;
        if (!outputDir.exists()) {
            System.err.println("エラー -> 出力フォルダ『"+this.outputDir+"』が存在しません!!");
            return 1;
        }
        return 0 ;
    }


    /**
     * ファイルを読み込んで配列リストに設定する
     * @return 0:読込OK 1:読込NG
     */
    private int readFile() {
        int retVal = 0 ;
        File file1 = new File(this.inputDir+"/"+this.inputFile1) ;
        File file2 = new File(this.inputDir+"/"+this.inputFile2) ;

        try {
            this.recordList1 = this.makeRecordList(file1);
            this.recordList2 = this.makeRecordList(file2);
        } catch (Exception e) {
            e.printStackTrace();
            retVal = 1;
        }
        return retVal;
    }

    /**
     * CSVファイル内容を配列リストに詰め替える
     * @param file
     * @return
     * @throws Exception
     */
    private List<String[]> makeRecordList (File file) throws Exception {
        boolean isError = false;
        List<String[]> recordList  = new ArrayList<String[]>() ;

        FileInputStream   fis  = null;
        InputStreamReader isr  = null ;
        BufferedReader    br   = null ;

        try {
            fis   = new FileInputStream(file);
            isr   = new InputStreamReader(fis, Charset.forName("MS932"));
            br    = new BufferedReader(isr);

            int i = 1 ;
            String record = null ;
            while((record = br.readLine()) != null) {
                String[] recordArray = record.split(this.delimiter);

                System.out.println("["+i+"]行目:"+Arrays.toString(recordArray));
                recordList.add(recordArray);
//                System.out.println("["+i_file1+"]行目:"+record);
                i ++ ;
            }

        } catch(FileNotFoundException e ) {
            e.printStackTrace();
            isError = true;
        } catch(IOException e ) {
            e.printStackTrace();
            isError = true;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
        }
        if (isError) { throw new Exception(); }

        return recordList ;

    }

以下は実行クラス

CSVDiffExecute.java
/**
 * CSV差分出力実行クラス
 *
 */
public class CSVDiffExecute {
    /**
     * メインメソッド
     * @param args [0] 区切り文字種別        (1:カンマ 1以外:タブ)
     *              [1] キー項目のインデックス(半角カンマ区切りで複数指定可能)
     *              [2] 取込ファイル配置フォルダ
     *              [3] 取込ファイル1
     *              [4] 取込ファイル2
     *              [5] 差分ファイル出力フォルダ
     */
    public static void main(String[] args) {
        //================================================================================
        // 引数チェック
        //================================================================================
        if (args.length != 6) {
            System.err.println("エラー->引数の数が不足しています。");
            System.exit(1);
        }
        //================================================================================
        // CSV差分出力実行
        //================================================================================
        CSVDiff csvDiff = new CSVDiff(args[0], args[1], args[2], args[3], args[4], args[5]);
        csvDiff.outputCSVDiff();
    }
}


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JVM-Math-LanguageをGradleプロジェクトとして手元で動かす

久しぶりに記事書きます。

TCGの方は気力がないので当分は書かないかもしれません。

ほぼ役に立つ文章は方法② 設定からプロセッサを指定する。だけなので、読み飛ばしてください。

要旨

JVM-Math-Languageを手元に落として動かした。

コンパイル時に -processor オプションを付けると実行できた。

設定から Annotation Processor (アノテーションプロセッサ) を設定すると、ソースコードがプロジェクトに紐づけられた。

動いた。

動機

Truffleを使ったGraalVM上で動くプログラミング言語を作りたかったです。

GraalのSLは思っていたよりもシンプルじゃなかったので、もっと簡単なインタプリタを基に自分の言語を実装しようと思いました。

graalvm/simplelanguage

Truffle Tutorial Slides

スライドの雑翻訳も前にちょっと書いた。

Truffle Tutorial Slides 自分用翻訳メモ①

JVM-Math-Language とは?

JJUG CCC 2017 Fall オレオレJVM言語を作ってみる(四則演算するだけだけど)というセッションで紹介された、 ANTLR + Truffle で作成されたプログラミング言語です。

jyukutyo/JVM-Math-Language

An AST interpreter with Truffle. This program can execute four
arithmetic operations. You can use numbers, +, -, *, /, "()", and
"->" for running in another thread.

四則演算ができるTruffleのAST(抽象構文木)インタプリタだそうです。

プロジェクトの作成

プロジェクトを作成します。

Gradle プロジェクトで、コンパイラは JDK 1.8 を利用しました。

IDEはIntelliJを利用しています。

手法は省略しますが、一応似たような記事を前に書いたのでそれとか見てもいいと思います。

で、GitHubからプロジェクトのフォルダをzipで落としてきます。(Clone or Download)

解凍したら、srcフォルダをまるまるプロジェクト直下に落としてきます。

JVM-Math-LanguageはMavenプロジェクトっぽいので、build.gradleを書いてやる必要があります。

一例として、JVM-Math-Languageのpom.xmlを雑に移植しといた(dependances書いただけ)奴を置いておきます。

build.gradle
plugins {
    id 'java'
}

version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    //ANTLRを動かすのに必要
    compile group: 'org.antlr', name: 'antlr4-runtime', version: '4.7'
    //Truffleを動かすのに必要
    compile group: 'org.graalvm.truffle', name: 'truffle-api', version: '1.0.0-rc12'
    //アノテーションからコードを生成するのに必要
    compile group: 'org.graalvm.truffle', name: 'truffle-dsl-processor', version: '1.0.0-rc12'
}

余談ですが、Gradleをコンソールからいい感じに使うと、pom.xmlからプロジェクトを(もちろんbuild.gradleも)生成できるらしいです。

Maven Pomをgradle buildに変換する

具体的には、pom.xmlを置いたディレクトリ(プロジェクトディレクトリになる)に、以下のコマンドを入力します。

gradle init --type pom

コンパイルエラー

さて、とりあえずビルド.....といったところで、コンパイルエラーが6件ぐらい発生します。

ついでにいえば赤線も生えていますね。

確認してみると、以下のクラスが足りないようです。

・jvmmathlang.truffle.JvmMathLangTypesGen

・nodes.ops.AddNodeGen (以下パッケージ略)

・DivNodeGen

・MulNodeGen

・SubNodeGen

~~Genクラスとは?

これらのクラスは、JSR 269: Pluggable Annotation Processing APIというものを用いて、Truffle DSL Processorが自動で生成してくれるコードらしいです。

ざっくり言うと、特定のアノテーションを付けた抽象クラスを作ると、その実装が自動で生成されるらしいです。

で、今はその生成周りがなにも設定されていないがためにコンパイルエラーになっているわけです。

では、アノテーションプロセッサの設定をしていきましょう。

また、Annotation Processing APIについては、以下の記事が分かりやすかったです。

Pluggable Annotation Processing API 使い方メモ

方法①はCompileJavaでしか動きませんが、CompileJavaではオプションを指定しなくてもコンパイルに成功するようです。(つまり意味がない)

ただ、-processorオプションやgradleでの設定方法が説明されている記事にたどり着くのに苦労したため、文章自体は置いておこうと思います。

方法① コンパイルオプションで指定する。

Javaのコンパイラには、プロセッサクラスを指定してコンパイルするオプションがあります。

javac -processor 完全修飾クラス名,完全修飾クラス名,完全修飾クラス名

また、Gradleプロジェクトではコンパイルオプションはビルドスクリプトに記述します。

アノテーションプロセッサーで自動生成するときにハマったあたりの話 #ソースコード自動生成

build.gradle
compileTestJava.options.compilerArgs += ['-processor', '完全修飾クラス名,完全修飾クラス名,完全修飾クラス名']

ここで、一つ問題が発生します。

私はプロセッサクラスの一覧を持っていません。

しかしそれは幸運なことに、それは私のすぐ手元にありました。

truffle-dsl-processor-1.0.0-rc12.jar内の META-INF -> service に、javax.annotation.processing.Processorというファイルがあります。

その中に、完全なクラス名の一覧があります。

javax.annotation.processing.Processor
com.oracle.truffle.dsl.processor.TruffleProcessor
com.oracle.truffle.dsl.processor.verify.VerifyTruffleProcessor
com.oracle.truffle.dsl.processor.LanguageRegistrationProcessor
com.oracle.truffle.dsl.processor.InstrumentRegistrationProcessor
com.oracle.truffle.dsl.processor.InstrumentableProcessor
com.oracle.truffle.dsl.processor.verify.VerifyCompilationFinalProcessor
com.oracle.truffle.dsl.processor.OptionProcessor
com.oracle.truffle.dsl.processor.interop.InteropDSLProcessor
com.oracle.truffle.object.dsl.processor.LayoutProcessor

これを先ほどのbuild.gradleに当てはめます。

build.gradle
//略
compileJava.options.compilerArgs += ['-processor', 'com.oracle.truffle.dsl.processor.TruffleProcessor,' +
        'com.oracle.truffle.dsl.processor.LanguageRegistrationProcessor,' +
        'com.oracle.truffle.dsl.processor.InstrumentRegistrationProcessor,' +
        'com.oracle.truffle.dsl.processor.InstrumentableProcessor,' +
        'com.oracle.truffle.dsl.processor.verify.VerifyCompilationFinalProcessor,' +
        'com.oracle.truffle.dsl.processor.OptionProcessor,' +
        'com.oracle.truffle.dsl.processor.interop.InteropDSLProcessor,' +
        'com.oracle.truffle.object.dsl.processor.LayoutProcessor']

サイドバーのGradle -> Tasks -> compileJavaからやるとうごきます。

また、Gradleにすべてのクラス名を記述すると見通しが悪くなるので、

src -> main -> resource -> META-INF -> services にjavax.annotation.processing.Processorをもってくることで、コンパイルオプションを省略できます。

方法② 設定からプロセッサを指定する。

はい、ここからが本命です。

ファイル -> 設定 -> ビルド、実行、デプロイ -> コンパイラー -> 注釈プロセッサ を開きます。

Defaultを選択し、注釈処理を使用可能にする にチェックを入れます。

次の場所に生成されたソース関連を保管 で、モジュール・コンテント・ルート を選択します。

これで、ビルド時にsrc/main内にコードが生成されます。

が、まだgenerated及びgenerated_testsフォルダがソースとして認識されていません。

そこで、build.gradleを編集してソースフォルダとして認識されるようにします。

逆引きマニュアル: IntelliJ IDEAでAnnotation Processorsを使用する方法

build.gradle
apply plugin: 'idea'

idea {
    module {
        sourceDirs += file('src/main/generated')
        generatedSourceDirs += file('src/main/generated')
        testSourceDirs += file('src/test/generated_tests')
    }
}

これで、エラーがすべてなくなったと思います。

最後に

方法②とかいう30秒で終わりそうな設定にたどり着くために1週半ぐらいかかってしまいました。

皆さんもそういうことがあればぜひ記事を書いて教えてください。

同じ苦労をする人を減らしていきましょう!

P.S.

ネットソースの独学はいつもしんどい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テーブル定義でスネークケースをやめたい

スネークケースとは

テーブル定義では、スネークケース(_区切り)で定義することが多い。
例えば、ユーザー情報を格納するマスタの例で説明する。
テーブル名:mst_user

論理名 物理名
ユーザーID user_id
ユーザー名 user_name

アプリケーション開発との関連

アプリケーション開発言語はスネークケースではないことが多い。
例えば、javaの場合、パスカルケースとキャメルケースだったりする。

何が問題か

javaを例にあげると、mst_userに対応するクラス(DTO)はJavaの規約(パスカルケース、キャメルケース)に従うと以下のようになる。

public class MstUser {
    private String userId;
    private String userName;

    // getter,setterは省略
}

テーブルはスネークケースなのにjavaの規約が異なるためマッピングが必要となる。
これに付随する問題がいくつか発生する。

  1. 仕様書作成時の負担が大きい
    例)API仕様書作成時に間違える。
    大抵は項目が多いのでテーブル定義から項目をコピーしてスネークケースをキャメルケースに置き換えていくことになるが間違えやすい。
    user_id => userid => 間違えた!
    特に、設計だけして開発を外部に依頼する場合、「userIdでは?」とQ&Aがきたりして時間の無駄。

  2. 開発時にマッピングミスすると面倒
    SQLがスネークケースのため、javaのクラスへのマッピングを記載する必要がある。
    ORマッパーで自動生成できれば、結合(join)するような仕様だと地道にマッピングを書くことになるのでミスする可能性がある。値の取得結果がnullになる=>マッピングミスだったということが起こる。

  3. スネークケースとキャメルケースの相互変換が面倒
    1,2とも関連するが、そもそもマッピングをする際にスネークケース=>キャメルケース、キャメルケース=>スネークケースの変換をするのがかなりの手間になる。
    ツールを作れば多少はマシになるかもしれないが、やっぱり手間である。

スネークケースをやめて開発言語の規約に合わせる

javaの規約に従ってみる。

テーブル名:MstUser

論理名 物理名
ユーザーID userId
ユーザー名 userName

DTO

public class MstUser {
    private String userId;
    private String userName;

    // getter,setterは省略
}

これでマッピングの手間がなくなるので、効率よく設計、開発ができそう。
デメリットはあるだろうか?

目次

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Online Computer Science Homework Help

Have you difficulties in doing programming in computer science. Our full support through Computer science assignment help will overcome your difficulties. To acquire proper grip over computer science visit our webpage domyhomework.co now. You will surely score well after that. Our highly qualified experts provide depth explanation to make each concept understandable. for more info visit my website:https://www.domyhomework.co/computer-science-assignment-help.html
computer-science.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スタックっぽい何かをJavaで実装する

はじめに

 とある事情により、Javaの学習を始めた人向けに急遽スタックっぽいプログラムをつくり、解説することになりました。しかし、時間の都合上ほとんど解説できずじまいで終わってしまったため、その補完ならびにQiita布教を兼ねて、本記事にてその解説の続きを行う所存です。

 対象者としては、Javaを始めたばかりの方になります。もう少し具体的に申し上げると、Hello Worldを経て、データ型や配列の基礎を知り、if文やfor文・while文の書き方を学んだ直後くらいを想定しています。

 とは言え、いきなりスタック(っぽい何か)を実装と言っても難しいと思いますので、第1章では最低限の枠組みだけをつくり、第2章以降で順次スタックとして必要とされる機能を追加実装していきます。ですので、第1章から順番にコーディングを行っていけば、さほど混乱することなくスタック(っぽい何か)をつくることができるはずです。

 なお、開発環境としては、paiza.ioを利用します。タイトル通り、言語はJavaです。

 それでは、しばらくの間お付き合いくださいませ。

第0章 スタックについて

 スタックとはデータ構造の1つであり、最後に入れたものを初めに出す仕組み(LIFO : Last In First Out)をもっています。エレベータをイメージしてみてください。社会人のマナーなどを考慮しない場合、エレベータに乗ったらまず奥から詰めていきます。そしてエレベータから降りるときは、ドアに近い人、すなわち最後に乗った人から降りていきます。

 本記事では、入力された整数をスタックの役割をもつ配列にpush(格納)していき、最後に入力した整数から順にpop(出力)するスタックっぽいプログラムを作成します。最終的な目標として、以下の要件を満たすことを目指します。

  • 5個の整数を入れる配列をスタックとして実装する。
  • コマンドプロンプトから文字列および整数を入力し続ける。
  • 「push 整数」が入力されたら、整数を配列の先頭に追加する。もし、すでに5個入力されている場合は、「push error」と出力する。
  • 「pop」が入力されたら、配列の先頭をプロンプトに出力し、出力された整数は配列から削除する(もしくは、削除したかのように見せかける)。もし、スタックに1つも整数がない場合は、「pop error」と出力する。
  • 「end」が入力されたらプログラム終了。

 第1章から順に、少しずつ実装していきましょう。

第1章 入力処理

 この章では、比較的簡単な次の項目から実装していきましょう。

  • コマンドプロンプトから文字列および整数を入力し続ける。

 paiza.ioにアクセスし、「コード作成を試してみる(無料)」をクリックしてください。初めはPHPなどの他の言語のサンプルが表示されるかも知れませんが、その場合はJavaを選択しましょう。すると、printlnメソッドを含む簡単なプログラムが次のように表示されるはずです。

import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // Your code here!

        System.out.println("XXXXXXXX");
    }
}

 さて、文字列および整数の入力を受け付けるために、一旦String型で受け取り、整数として扱うときだけint型に変換することにします。それでは、とりあえず入力された文字列をString型に格納し続けるプログラムを書いてみてください。また、実行したときに正しく入力を受け付けているかどうか確認するために、「Your input is [入力された文字列]」と表示する処理も追加してください。書けたら、下の解答例1-1を確認してみましょう。

解答例1-1(クリックすると開きます)
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        while (true) {
            String input = sc.nextLine();
            System.out.println("Your input is [" + input + "]");
        }
    }
}

 試しに実行してみましょう。paiza.ioでは、コマンドプロンプトから入力するかわりに、入力タブを選んで出てくるウィンドウ内に入力したい内容をすべて書いておく必要があります。そこで、入力タブをクリックしたら次のように書いておきましょう。

push 1
push 2
push 3
end

 ここまでできたら、いよいよ本当に実行してみます。すると、実行時エラーというタブができて、「No line found」というメッセージが表示されます。これはどういうことかと言うと、while文の繰り返し条件がtrueになっているため、永遠に繰り返すという処理になっていることが原因です。入力を最後まで受け付けたにも関わらず、さらに入力を受け取ろうとしたため、「もうねえよ」と言われた訳です。実際、出力タブをクリックすると、ちゃんと「end」までは受け付けていることが分かります。

 そこで、今度は次の項目について考えます。

  • 「end」が入力されたらプログラム終了。

 入力された文字列が「end」に等しいかどうかを判定するには、Stringクラスのオブジェクトに用意されているequalsメソッドを使うと良いでしょう。詳しい使い方は各自で調べていただきつつ、「end」が入力されたらwhile文のループを抜け出す処理を書いてみてください。書けたら、下の解答例1-2を確認してみましょう。

解答例1-2(クリックすると開きます)
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        while (true) {
            String input = sc.nextLine();
            System.out.println("Your input is [" + input + "]");
            if (input.equals("end")) {
                break;
            }
        }
    }
}

 実行もしてみてください。「end」を受け付けた後にwhile文のループを抜け、プログラムが正常に終了できたため、実行時エラーが出ていませんね。これで、入力処理はOKです。

第2章 push処理

 この章では、次の項目を実装します。

  • 「push 整数」が入力されたら、整数を配列の先頭に追加する。もし、すでに5個入力されている場合は、「push error」と出力する。

 スタックの役割をもつint型配列を用意しましょう。配列の名前はstackにしてください。また、配列の宣言を行う際、要素の個数を指定するのに直接5と書くのはやめましょう。その代わりに、事前にint型変数Nに5を代入しておき、配列の宣言時にはNを用いるようにしてください。これらは、while文の手前に書いておきます。

 次に、「push 整数」の入力をどのように処理するかを考えましょう。入力された文字列はpushと半角スペースと整数のため、まず初めの4文字だけを見てpushコマンドであることを判定できるようにします。それにはStringクラスに用意されているsubstringメソッドを利用してください。

 また、入力された文字列から整数だけを取り出すため、これもStringクラスに用意されているsplitメソッドを用いて、pushと整数に分けましょう。ただし、分けただけでは整数が文字列として扱われていますので、文字列をint型に変換するためにIntegerクラスのparseIntメソッドを利用してください。parseIntメソッドは次のように使いましょう。

int number = Integer.parseInt(string);

 最後に、整数をpushする場合に配列stackの何番目に格納するか判定する処理を考えましょう。配列に手前も奥もあるかと思われるかも知れませんが、ここでは0番目の要素が最も手前、N $-$ 1 ($=$ 4)番目の要素が最も奥であると決めることにします。すなわち、整数は奥のN $-$ 1番目から順に手前の0番目に向かって格納されるということです。ただし、すでに0番目まで整数が格納されている場合はエラーメッセージを出力する必要がありますので、このことにも注意して書いてみてください。書けたら、下の解答例2を確認してみましょう。

解答例2(クリックすると開きます)
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int N = 5;
        int[] stack = new int[N];
        int indexPointer = N;

        while (true) {
            String input = sc.nextLine();
            System.out.println("Your input is [" + input + "]");
            if (input.equals("end")) {
                break;
            } else if (input.substring(0, 4).equals("push")) {
                if (indexPointer >= 1 && indexPointer <= N) {
                    String[] commands = input.split(" ");
                    int number = Integer.parseInt(commands[1]);
                    indexPointer --;
                    stack[indexPointer] = number;
                    System.out.println(number + " pushed. index:" + indexPointer);
                } else {
                    System.out.println("push error.");
                }
            }
        }
    }
}

 少し補足します。配列の何番目の要素に整数を格納するかを把握するために、新たにint型変数indexPointerを用意しました。pushの処理を行う際、直前に見ていた要素より1つ手前に整数を格納できるかどうか、すなわちindexPointerが1以上N以下かどうかを判定します。それが真であれば、indexPointerを1減らし、その値の場所に整数を格納します。この処理のため、indexPointerを宣言したときの初期値はNにしておく必要があります。

 さて、ここまでの処理が正しいかどうか、少しテストしてみましょう。入力タブを選び、次のように書いてください。

push 1
push 2
push 3
push 4
push 5
push 6
end

 実行してみます。

Your input is [push 1]
1 pushed. index:4
Your input is [push 2]
2 pushed. index:3
Your input is [push 3]
3 pushed. index:2
Your input is [push 4]
4 pushed. index:1
Your input is [push 5]
5 pushed. index:0
Your input is [push 6]
push error.
Your input is [end]

 ちゃんと配列の奥から順に整数を格納し、5個格納した後はエラーメッセージを表示していますね。

第3章 pop処理

 ここまで進めてこれたなら、もう少しでスタックの完成です。前章において、今配列の何番目の要素に格納したかを変数indexPointerで把握することにしました。この変数はそのままpop処理にも利用できます。書けたら、下の解答例3を確認してみましょう。

解答例3(クリックすると開きます)
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int N = 5;
        int[] stack = new int[N];
        int indexPointer = N;

        while (true) {
            String input = sc.nextLine();
            System.out.println("Your input is [" + input + "]");
            if (input.equals("end")) {
                break;
            } else if (input.length() >= 4 && input.substring(0, 4).equals("push")) {
                if (indexPointer >= 1 && indexPointer <= N) {
                    String[] commands = input.split(" ");
                    int number = Integer.parseInt(commands[1]);
                    indexPointer --;
                    stack[indexPointer] = number;
                    System.out.println(number + " pushed. index:" + indexPointer);
                } else {
                    System.out.println("push error.");
                }
            } else if (input.equals("pop")) {
                if (indexPointer >= 0 && indexPointer < N) {
                    System.out.println(stack[indexPointer] + " poped. index:" + indexPointer);
                    indexPointer ++;
                } else {
                    System.out.println("pop error.");
                }
            }
        }
    }
}

 いかがでしょうか。push処理よりはやや簡単だったと思います。それでは、入力タブに次のように入力してみてください。

pop
push 1
push 2
push 3
pop
pop
pop
push 4
push 5
push 6
push 7
push 8
push 9
end

 実行してみます。

Your input is [pop]
pop error.
Your input is [push 1]
1 pushed. index:4
Your input is [push 2]
2 pushed. index:3
Your input is [push 3]
3 pushed. index:2
Your input is [pop]
3 popped. index:2
Your input is [pop]
2 popped. index:3
Your input is [pop]
1 popped. index:4
Your input is [push 4]
4 pushed. index:4
Your input is [push 5]
5 pushed. index:3
Your input is [push 6]
6 pushed. index:2
Your input is [push 7]
7 pushed. index:1
Your input is [push 8]
8 pushed. index:0
Your input is [push 9]
push error.
Your input is [end]

 ちゃんと、pushされた1、2、3が逆順にpopされていますね。また、何も整数が格納されていないのにpopしようとした場合や、すでに整数が5個格納されているのにpushしようとした場合はエラーメッセージを出力するようになっています。

おわりに

 今回は、Javaの基本的な文法を用いて簡単なスタックを実装しました。上記プログラムを少し変更するだけで、実はキューも実装することができますので、お時間に余裕があればぜひ挑戦してみてください。

 ここまでお読みいただき、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java1.8+]LocalDateで次の×曜日の日付を取得する

TemporalAdjustersnext(DayOfWeek dayOfWeek)メソッドが返すTemporalAdjusterのインスタンスを使ってLocalDateの日付を次のx曜日の日付に変更できます。

サンプルコード

LocalDate d = LocalDate.of(2019, 3, 25).with(TemporalAdjusters.next(DayOfWeek.MONDAY));
LocalDate d2 = LocalDate.of(2019, 3, 29).with(TemporalAdjusters.next(DayOfWeek.valueOf("MONDAY")));
LocalDate d3 = LocalDate.of(2019, 3, 31).with(TemporalAdjusters.next(DayOfWeek.of(1)));

 System.out.println(d.toString());
 System.out.println(d2.toString());
 System.out.println(d3.toString());

出力

2019-04-01
2019-04-01
2019-04-01

LocalDateの日付がすでにその曜日の場合、翌週の日付に設定されます。

nextOrSame(DayOfWeek dayOfWeek)を使用すると、LocalDateがすでにその曜日である場合何もしません。

サンプル

LocalDate d = LocalDate.of(2019, 3, 25).with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
LocalDate d2 = LocalDate.of(2019, 3, 29).with(TemporalAdjusters.nextOrSame(DayOfWeek.valueOf("MONDAY")));
LocalDate d3 = LocalDate.of(2019, 3, 31).with(TemporalAdjusters.nextOrSame(DayOfWeek.of(1)));

出力

2019-03-25
2019-04-01
2019-04-01
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QuarkusアプリをGKE(Google Kubernetes Engine)に載せてみた。

お題

Supersonic Subatomic Java
A Kubernetes Native Java stack tailored for GraalVM & OpenJDK HotSpot, crafted from the best of breed Java libraries and standards

という謳い文句のQuarkusを試してみた。
既に↓のように、紹介、ないし、試してみた記事はいろいろあるので、違いとして、GKE(Google Kubernetes Engine)に載せてみることを試してみた。

作業環境

# OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"

# Java

$ java -version
openjdk version "10.0.2" 2018-07-17
OpenJDK Runtime Environment (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4)
OpenJDK 64-Bit Server VM (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4, mixed mode)

※ちなみに、Java12で試したら動きませんでした。

# Docker

$ sudo docker version
  〜〜〜
Server: Docker Engine - Community
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       6247962
  Built:            Sun Feb 10 03:42:13 2019
  OS/Arch:          linux/amd64
  Experimental:     false

# Maven

$ mvn -version
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T03:41:47+09:00)

# gcloud

$ gcloud version
Google Cloud SDK 240.0.0
 〜〜〜
kubectl 2019.03.22

gcloud authは済んでいる状態。

実践

Quarkusアプリ作成

↓のチュートリアルに沿ってMavenプロジェクトを作成し、あとは、作成したプロジェクトに含まれている mvnw コマンドを叩くだけ。
https://quarkus.io/guides/getting-started-guide

ソースを一部抜粋すると、↓のようなSpringライクなWebAPIコードとなる。
GCPのCloud Endpointsを使おうとした時も、やはり似たようなコードになったし。

[org.acme.quickstart.GreetingResource]
@Path("/hello")
public class GreetingResource {

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(@PathParam("name") String name) {
        return service.greeting(name);
    }
}

Dockerコンテナ

これもチュートリアル通りだけど、下記のようなDockerfileをプロジェクト配下に作成。

[Dockerfile]
FROM openjdk:11-jre-slim
RUN mkdir /app
COPY target/lib /app/lib
COPY target/*-runner.jar /app/application.jar
EXPOSE 8080
CMD ["java", "-jar", "/app/application.jar"]

※チュートリアルでは openjdk:8-jre-slim だったのだけど、試した時の Quarkus のバージョンの問題か、コンテナ作成後に docker run した時にうまく起動しなかったため「11」に上げた。

ビルド by Cloud Build

下記のようにCloud Buildを利用するためのYamlファイルをプロジェクト配下に作成。

[cloudbuild.yaml]
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/quarkus-microservice', '.' ]
images:
- 'gcr.io/$PROJECT_ID/quarkus-microservice'

 
で、ビルド実行。すると、出来上がったコンテナイメージがGCR(Google Container Registry)に上がる。

$ gcloud builds submit --config cloudbuild.yaml .
Creating temporary tarball archive of 137 file(s) totalling 9.6 MiB before compression.
Uploading tarball of [.] to [gs://【GCPプロジェクトID】_cloudbuild/source/1553878212.23-a25ba1029b4a4979b2a049769da45570.tgz]
Created [https://cloudbuild.googleapis.com/v1/projects/【GCPプロジェクトID】/builds/1bff9b28-a9da-4113-9c41-c9de7f9af1d7].
   〜〜〜
DONE
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                  IMAGES                                              STATUS
1bff9b28-a9da-4113-9c41-c9de7f9af1d7  2019-03-29T16:50:16+00:00  46S       gs://【GCPプロジェクトID】_cloudbuild/source/1553878212.23-a25ba1029b4a4979b2a049769da45570.tgz  gcr.io/【GCPプロジェクトID】/quarkus-microservice (+1 more)  SUCCESS

実際にビルド成功してコンテナイメージが格納されていることをGCPコンソールで確認。
screenshot-console.cloud.google.com-2019-03-30-01-54-34-080.png
screenshot-console.cloud.google.com-2019-03-30-01-57-09-002.png

ちなみに、このへんの手順は下記の過去記事に基づく。
GKE試行(その2:「GitHub -> CSR へ。そしてGCBによりGCRへ」)

GKEクラスタ作成

GCPにおけるk8sのマネージドサービスを利用するため、まずはクラスタを作成。
GCPコンソールからも作成可能だけど、(節約のため)作ったり消したりするのでコマンドとして残しておくことにする。
サービス公開でなくただのトライ記事用なのでプリエンプティブで作成。

$ gcloud container clusters create clst-pe-01 --preemptible --machine-type=f1-micro --num-nodes=3 --disk-size=10
WARNING: In June 2019, node auto-upgrade will be enabled by default for newly created clusters and node pools. To disable it, use the `--no-enable-autoupgrade` flag.
   〜〜〜
Creating cluster clst-pe-01 in asia-northeast1-c... Cluster is being health-checked (master is healthy)...done.                                                                                              
Created [https://container.googleapis.com/v1/projects/【GCPプロジェクトID】/zones/asia-northeast1-c/clusters/clst-pe-01].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/asia-northeast1-c/clst-pe-01?project=【GCPプロジェクトID】
kubeconfig entry generated for clst-pe-01.
NAME        LOCATION           MASTER_VERSION  MASTER_IP       MACHINE_TYPE  NODE_VERSION   NUM_NODES  STATUS
clst-pe-01  asia-northeast1-c  1.11.7-gke.12   xx.xxx.xxx.xxx  f1-micro      1.11.7-gke.12  3          RUNNING

実際にクラスタが作られていることを確認。
screenshot-console.cloud.google.com-2019-03-30-02-07-20-951.png

ちなみに、このへんの手順は下記の過去記事に基づく。
GKE試行(その3:「クラスタ作成」)
また、節約のためのクラスタ作成云々は下記が参考になる。
安価なGKE(k8s)クラスタを作って趣味開発に活用する

GCR(Container Registry)にあるコンテナイメージをGKE(Kubernetes Engine)にデプロイ

$ kubectl run quarkus-microservice --image gcr.io/【GCPプロジェクトID】/quarkus-microservice@sha256:18a20d4307785a7aec3b19738a849430e2181720cb357d8a7ea6f8fe4ed1f850 --port 80
deployment.apps/quarkus-microservice created

はい。デプロイメントできてる。
screenshot-console.cloud.google.com-2019-03-30-02-21-46-308.png

アプリを公開

デプロイメントできただけだと外部からのアクセスの口がないので、公開用にサービスを作る。

$ kubectl expose deployment quarkus-microservice --type "LoadBalancer"
service/quarkus-microservice exposed

はい。サービスできてる。
screenshot-console.cloud.google.com-2019-03-30-02-25-48-306.png

アクセス確認

screenshot-34.85.95.35-8080-2019-03-30-02-48-08-274.png
簡素すぎてわからないけど、成功。

ちなみに、このへんの手順は下記の過去記事に基づく。
GKE試行(その4:「GKEデプロイ->サービス公開」

まとめ

今回は、ただの初回チュートリアルレベルの自動生成アプリをGKEに載せただけ。
GitHubのサンプルコードとしては、JSON-REST、WebSocket、ORM、バリデーションなどいろいろある。
https://github.com/quarkusio/quarkus-quickstarts#getting-started-guides
screenshot-github.com-2019.03.30-01-31-12.png
k8sに載せるアプリを考えた時、これまでは「Golangで書こう。」になっていたけど、Quarkus登場のおかげで、今後は豊富な既存資産を使えるJavaも候補になるかも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む