20190724のJavaに関する記事は12件です。

先輩に質問しやすいJavaコーディング集

はじめに

 本記事は、「先輩エンジニアに質問したいけど、可読性が原因で叱られる」といった方に向けて
 最低限身に着けておきたい書き方をつらつら書いていきます。

対象読者

 ・Java言語が初めての人
 ・暇な人
 ・先輩に怒られて心折れかけたかつての自分のような人

その1:インデントぐちゃぐちゃ&コメントもめちゃめちゃ

 

test.java
package hello;

public class test {
    /**
     *
     * すげー勢いでループしてHello Worldを出力する。
     *
     */
    public static void main(String[] args) {
            System.out.println("Hello World");

            // もっとHello Worldをだす
            test t = new test();
                t.hellowolrdloop();
                                            }
    /**
     *  Hello Worldを 1回出力する。
     */
        public void hellowolrdloop() {
            for(int i = 0 ; i < 10 ; i++) {
                System.out.println("Hello World");
        }
    }
            }

・・・もはや説明不要ですね。
私は一度このレベルでレビュー依頼を出した際にめっちゃ怒られました。申し訳ございません。

しかし、「インデントを揃える」というのは意外と理解に時間がかかる印象がありますので、
 処理ブロック一つで1段下げて、処理が終了したら1段戻すを意識しましょう。

コメントがめちゃめちゃに関しては、正直仕方ない面もあると思いますが、
修正したらコメントを見直す。誰が見ても一目瞭然のコメントは書かないを意識すると良いと思います。

test.java
package hello;

public class test {

    /**
     * Hello Worldを1回出力する。
     *
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("Hello World");

        test t = new test();
        t.hellowolrdloop();
    }

    /**
     *  すげー勢いでループしてHello Worldを出力する。
     *
     */
    public void hellowolrdloop() {
        for(int i = 0 ; i < 10 ; i++) {
            System.out.println("Hello World");
        }
    }
}

どうでしょうか?少しは見やすくなったと思います。

可読性が高いコードと質問の仕方さえ徹底すれば、大抵の事は教えてもらえます。
更に引継ぎが楽になるので指示を仰いだり、嫌になって1日ずる休みする際に有効な手立ての1つです。

その2 なんでもラッパークラス

 

test.java
package hello;

public class test {

    /**
     * 加算を行う。
     *
     * @param args
     */
    public static void main(String[] args) {

        // 将来的にListに格納するかもしれないからラッパークラスを使う。
        Integer targetValue = 0;
        Short shortVal = 10;

        System.out.println(targetValue + shortVal);

    }

}

まずラッパークラスですが、ざっくりいうと
基本データ型を参照型として操作できるクラス群のようなものです。
それぞれのクラスに便利なメソッドが用意されていたり、ListやMap等のコレクションに格納できたり
基本データ型にはないメリットも多いです。
が、基本データ型と比較して4倍程度のメモリを消費します。

そのため、基本データ型で事足りる場合は基本データ型を使用しましょう。

test.java
package hello;

public class test {

    /**
     * 加算を行う。
     *
     * @param args
     */
    public static void main(String[] args) {

        int targetValue = 0;
        short shortVal = 10;

        System.out.println(targetValue + shortVal);

    }

}

その3(?) 変数名・定数名がキャメルケース・スネークケースでない。

 ※この項目は、好みが大きく出ている可能性があります。

Java言語においては、暗黙のルール的に変数名:キャメルケース、定数名:スネークケース
といったものがあります。
体感ですが開発現場の先輩たちは無意識にこのルールにのっとってコーディングを行っている事が多いと思います。
なので、知識として身に着けておくとよいでしょう。

test.java
package hello;

public class test {

    public static void main(String[] args) {

        // NG 全部小文字は推奨されない
        int calcrateresult = 0;

        // OK キャメルケース:先頭小文字、単語の区切りが大文字
        int calcrateResult = 0;

        /* 定数 */

        // NG 全部大文字
        final int CALCLATERESULT = 0;

        // OK 全部大文字、区切りにアンダーバーを入れる
        final int CALCLATE_RESULT = 0;

    }

}

おわりに

 本当はまだまだありますが、こういったことを心がけるだけで意図した回答を得やすくなると思います。
 意図した回答を得られないうえに可読性でボコられると辛いので、可読性の向上を意識したコーディングをしたいですね。
 ※職場では私のソースは汚いと有名です。

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

【Java】プロジェクト新規作成から自動テスト/ビルド/デプロイ実現まで

TL;DR

git branchに変更が加わった際、自動で

  • JUnit test (with MySQL)
  • docker build
  • Kubernetes環境にデプロイ

が行われるJavaプロジェクトを完全にゼロから構築します。

登場するもの

OSS

  • Maven
  • MySQL
  • Docker

サービス

  • GitHub
  • CircleCI
  • AWS (ECR, EKS) ⇦ 微修正でその他マネージドk8sにも応用可能かと思います。

サンプルプロジェクトの実装

最終的にディレクトリ構成はこんな感じになります。順を追って作っていきます。
GitHubからcloneして頂いても結構です。

testproject/
    ├ src/
    │   ├ main/
    │   │   └ java/
    │   │        └testpackage/
    │   │            └Main.java
    │   └ test/
    │       └ java/
    │           └testpackage/  
    │              └MainTest.java   
    ├ .cifcleci/
    │   └ config.yml
    ├ schema.sql
    ├ pom.xml
    ├ deploy.yaml
    └ Dockerfile

1. スキーマ定義

テーブルスキーマを記述します。resourceフォルダに置いても良いのですが、今回はトップディレクトリに配置することにします。

データベース名は指定しません。

schema.sql
CREATE TABLE user (id int, name varchar(10));

2. mavenプロジェクト作成

先ほどのtableに適当なレコードを挿入する最小限のJavaプロジェクトを実装します。

JDBC、JUnit、maven-assembly-pluginを使用します。

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>aaaanwz</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1</version>
    <name>testproject</name>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- JDBC driver -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <!-- JUnit -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.3.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- JUnit test -->
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M3</version>
            </plugin>
            <!-- Build runnable jar -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>testpackage.Main</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

1レコード挿入するだけのMainクラスを実装します。

データベースへの接続情報は環境変数から取得します。

src/main/java/testpackage/Main.java
package testpackage;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {

  public static void main(String[] args) throws ClassNotFoundException, SQLException {
    final String host = System.getenv("DB_HOST");
    final String dbname = System.getenv("DB_NAME");
    execute(host, dbname);
  }

  static void execute(String host, String dbname) throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection conn = DriverManager.getConnection(
        "jdbc:mySql://" + host + "/" + dbname + "?useSSL=false", "root", "");
    PreparedStatement stmt = conn.prepareStatement("INSERT INTO user(id,name) VALUES(?, ?)");
    stmt.setInt(1, 1);
    stmt.setString(2, "Yamada");
    stmt.executeUpdate();
  }

}

レコードが挿入された事を確認するだけのテストを書きます。

src/test/java/testpackage/MainTest.java
package testpackage;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import testpackage.Main;

class MainTest {
  Statement stmt;

  @BeforeEach
  void before() throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection conn =
        DriverManager.getConnection("jdbc:mySql://localhost/test?useSSL=false", "root", "");
    stmt = conn.createStatement();
  }

  @AfterEach
  void after() throws SQLException {
    stmt.executeUpdate("TRUNCATE TABLE user");
  }

  @Test
  void test() throws Exception {
    Main.execute("localhost", "test");
    try (ResultSet rs = stmt.executeQuery("SELECT * FROM user WHERE id = 1;")) {
      if (rs.next()) {
        assertEquals("Yamada", rs.getString("name"));
      } else {
        fail();
      }
    }
  }
}

3. Dockerfile作成

Dockerfileを書きます。テストは docker buildとは別のステップで行う予定のため、-DskipTestsを付与してビルド時のテストをスキップします。

イメージサイズ削減のためmaven:3.6でビルド、openjdk11:apline-slimで実行するマルチステージビルドを行います。
maven:3.6では約800MB、openjdk11:alpine-slimでは約300MBとなります。現時点でECRの無料枠は500MBのため、個人開発では大きな差です。

Dockerfile
FROM maven:3.6 AS build

ADD . /var/tmp/testproject/
WORKDIR /var/tmp/testproject/
RUN mvn -DskipTests package

FROM adoptopenjdk/openjdk11:alpine-slim

COPY --from=build /var/tmp/testproject/target/test-0.0.1-jar-with-dependencies.jar /usr/local/
CMD java -jar /usr/local/test-0.0.1-jar-with-dependencies.jar.jar

4. k8s yamlファイル作成

3.でビルドされたコンテナをk8sにデプロイするためのyamlを書きます。今回のプログラムは単発でSQLを実行して終了するため、kind: Jobにしてみます。Docker registryのurlは適宜置換してください。

envでDBへの接続情報を定義します。 DB_HOSTの値が mysqlとなっているのは、KubernetesのService経由で接続するためです。

k8s-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: test
spec:
  template:
    spec:
      containers:
      - name: test
        image: your-docker-registry-url/testimage:latest
        imagePullPolicy: Always
        env:
        - name: DB_HOST
          value: "mysql"
        - name: DB_NAME
          value: "ekstest"
      restartPolicy: Never
  backoffLimit: 0

5. circleci configファイル作成

本稿のキモです。CircleCIで自動テスト/ビルドを行うための構成設定を書きます。

.circleci/config.yml
version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@6.1.0
  aws-eks: circleci/aws-eks@0.2.1
  kubernetes: circleci/kubernetes@0.3.0
jobs:
  test: # ①JUnit テストの実行
    docker:
      - image: circleci/openjdk:11
      - image: circleci/mysql:5.7
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          MYSQL_DATABASE: test
        command: [--character-set-server=utf8, --collation-server=utf8_general_ci, --default-storage-engine=innodb]
    steps:
      - checkout
      - run:
          name: Waiting for MySQL to be ready
          command: dockerize -wait tcp://localhost:3306 -timeout 1m
      - run:
          name: Install MySQL CLI; Create table;
          command: sudo apt-get install default-mysql-client && mysql -h 127.0.0.1 -uroot test < schema.sql
      - restore_cache:
          key: circleci-test-{{ checksum "pom.xml" }}
      - run: mvn dependency:go-offline
      - save_cache:
          paths:
            - ~/.m2
          key: circleci-test-{{ checksum "pom.xml" }}
      - run: mvn test
      - store_test_results:
          path: target/surefire-reports
  deploy: # ③EKSへのkubectl apply
    executor: aws-eks/python3
    steps:
      - checkout
      - kubernetes/install
      - aws-eks/update-kubeconfig-with-authenticator:
          cluster-name: test-cluster
          aws-region: "${AWS_REGION}"
      - run:
          command: |
            kubectl apply -f k8s-job.yaml
          name: apply
workflows:
  test-and-deploy:
    jobs: 
      - test: # ①JUnit テストの実行
          filters:
            branches:
              only: develop
      - aws-ecr/build-and-push-image: # ②コンテナのビルドとECRへのpush
          filters:
            branches:
              only: master
          create-repo: true
          repo: "testimage"
          tag: "latest"
      - deploy: # ③EKSへのkubectl apply
          requires:
            - aws-ecr/build-and-push-image

①JUnitテストの実行

filterによって、developブランチに変更が加わった際に mvn testが実行されるようにしてみました。この際テスト用MySQLインスタンスが立ち上がり、 testデータベースが準備されます。

公式ドキュメントで詳しく解説されています。  
- Language Guide: Java
- Database Configuration Examples

②コンテナのビルドとECRへのpush

masterブランチに変更が加わった際に、Dockerfileに従ってbuildし、ECRにpushします。

Orbクイックスタートガイドで詳しく解説されています。

③EKSへのkubectl apply

②が実施された後、4.で定義したJobを実行します。

circleci/aws-eksに解説がありますが、サンプルコードをよりシンプルに変更しました。

@0.2.1のドキュメントによるとパラメータaws-regionはRequiredとなっていませんが、実際は必須のようです。ECRのpushで環境変数AWS_REGIONを要求されているので、これをそのまま使いました。

インフラ設定

ほとんど公式ドキュメントへのリンク集です。

1. ECR, EKS環境準備

1-1. 基本設定

test-clusterを立ち上げます。
- Getting Started with Amazon EKS
- Setting Up with Amazon ECR

1-2. EKS上にMySQLをデプロイ

Kubernetes公式にDeploy MySQLというドキュメントが用意されています。

kubectl exec -it mysql-0 mysqlコマンドからekstestデータベースとuserテーブルを用意します。

2. CircleCI projectの設定

2-1. プロジェクトのセットアップ

2-2. 環境変数の設定

  • circleci/aws-ecrでRequiredとなっている環境変数をCircleCI Projectに設定します。
    Roleに要求される権限の詳細は割愛します。

まとめ

  • develop branchにpush
     mvn testが実行され、テストレポートがCircleCIのTest summaryに表示されます。
  • master branchにpush
    ECRにdocker imageがpushされ、EKSでJobが開始します。EKS上のMySQLに id:1 name:Yamadaレコードが挿入されます。

いかがでしたでしょうか。テストをわざと落とす等色々試してみて頂ければと思います。

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

【MyBatis】List<Map<>> 入れ子をMapperパラメータとして渡す

MyBatis のMapperパラメータに入れ子で渡したい!

  • Mapは key, value で可能
  • Listは 普通に入れ子で可能

環境

開発PC: Windows 10
STS: 4
mybatis:3.2.5
mybatis-spring:1.2.2
Java: 8

公式ドキュメント

http://www.mybatis.org/mybatis-3/ja/dynamic-sql.html
入れ子の説明は特になし

SQL用意

  • マッパー:xml
SampleMapper.xml
  <select id="selectTestMapList" resultMap="BaseResultMap">
  SELECT
    sample.*
  FROM
    sample
  WHERE
    sample.id IN
    <foreach item="internalMap" collection="nestedMapList">
        <foreach item="value" index="key" collection="internalMap"  open="(" separator="," close=")">
            #{value}
        </foreach>
    </foreach>
  </select>

java定義

  • マッパー:java
SampleMapper.java
List<Sample> selectTestMapList(@Param("nestedMapList") List<Map<String, Integer>> nestedMapList);

実行してみる

TestMapperExecutor.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/sample/spring/test-context.xml"})
public class TestMapperExecutor {

    @Autowired
    private SampleMapper sampleMapper;

    @Test
    public void test() {
        List<Map<String, Integer>> nestedMapList = new CopyOnWriteArrayList<>();
        Map<String, Integer> internalMap = new ConcurrentHashMap<>();
        internalMap.put("iKey", 1);
        nestedMapList.add(internalMap);

        List<Sample> result = null;
        try {

            result = sampleMapper.selectTestMapList(nestedMapList);
        }
        catch (Throwable t) {
            t.printStackTrace();
        }

    }

}

以上、お疲れ様でした!

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

Regarder =>||FREE-TV| | Tour De France 2019 live/stream Gratuit

Tour de France 2019 : liveStream, time

GO LIVE?► >http://nufilm.live/allsports/Tour-de-France-2019

?►CLICK HERE?►

GO LIVE?► >http://nufilm.live/allsports/Tour-de-France-2019

Check out the extended highlights from Stage 3 of the 2019 Tour de France. #NBCSports #TourdeFrance #TDF2019 » Subscribe to NBC Sports: All the action from Stage 3 on a day in which the riders endured a long 215km route from Binche to Épernay, as the French fans got to toast a French stage winner and GC leader. Become the manager of a professional cycling team and compete against today's best riders in over 200 races (500+ stages) across the world, including iconic races such as La Vuelta and the iconic Route in 3D - Tour de France 2019 Tour de France. LoadingUnsubscribe from Tour de France? Best Moments - Tour de France 2018 - Duration: 12:37. Tour de France 469,232 views. The world's most iconic bike race kicks off today with Stage 1: Bruxelles - Brussel 07/06/2019 - Stage 1 - 194,5 km - Flat. Myself, Von, DesFit, and DCRainmaker made our way to three vantage Tour de France 2019 builds on the solid foundation that made this long-running series such a success by creating an enhanced experience with innovative new features. Race as one of the game's 761 Tour de France 2019 Stage 12 Highlights: First Pyrenean Mountain Stage - Duration: 5:59. GCN Racing 137,616 views. New; 5:59. Tour de France Greatest Moments - Part 1/5 - Duration: 8:53. The 2019 Tour de France starts this weekend in Brussels, the hometown of pro cycling legend Eddy Merckx. With 5 summit finishes, three days in the high mountains, a team time trial and an

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

【Java】Spring BootアプリケーションをAzure App Serviceにデプロイする

目的

Spring BootのWebアプリを、AzureのApp Serviceにデプロイする。

前提条件

Eclipse + Gradle でSpring スタータープロジェクトとして作成したものをデプロイする。

App Serviceの作成

・TomcatとJavaのバージョンをWebアプリケーションと合わせる。
・OSはWindowsにすると、App Service Editorが使えるので便利。

Warファイルの作成

Create a Deployable War File
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable-war-file

上記手順に従ってデプロイ可能なwarファイルを作る。
@SpringBootApplicationの付いたクラスにSpringBootServletInitializerを継承させる。
かつ、build.gradleにwarのプラグインを追加し、TomcatをprovidedRuntimeにする。

build.gradle
apply plugin: 'war'

dependencies {
    // warにTomcatを含めない
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}

'bootWar'タスクを実行し、warファイルを作る。
01.PNG

WARファイルをAzureへ置く

App Service Editorを使用すると簡単。App Service Editorとは、App Service上のファイルをVisual Studio CodeみたいなGUIで直接編集できるツール。App ServiceのOSがWindowsの場合のみ使用できる。

既にROOTフォルダがある場合は削除しておく。

App Service Editorのエクスプローラーの部分へ、作成したWARファイルをドラッグ&ドロップしてアップロードする。

アップロード先のフォルダ、またはエクスプローラーの余白で右クリック→「Upload File」でもアップロードできる。
しばらく待つと、WARファイルが展開される。

おまけ:Tomcat入りのjarファイルをデプロイする方法

Spring Boot Deployment On Azure App Service — Zero Code Approach
https://medium.com/@k32y/spring-boot-deployment-on-azure-app-service-zero-code-approach-88305c8d0818

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

MySQL 5.5 -> 5.7 でつまづいたこと

MySQL 5.5 を使っていた Java アプリを MySQL 5.7 に上げた際に、いくつか問題が発生したので、原因と対処を残しておきます。

Datetime Field Overflow

事象

datetime 型のカラムを持つテーブルに、最大値の 9999/12/31 23:59:59 を含むレコードを INSERT したところ、Datetime Field Overflow が発生して失敗した。

原因

MySQL 5.6 で、datetime 型の小数点以下が切り捨て (floor) から四捨五入 (round) になったため。
公式ドキュメント

対処

java.util.Calendar でインスタンス化したオブジェクトのミリ秒を取得していた箇所について、ミリ秒をリセット (.000) する処理を追加した。

before
Calendar cal = Calendar.getInstance();
cal.set(9999, 11, 31, 23, 59, 59);
cal.getTime();

after
Calendar cal = Calendar.getInstance();
cal.set(9999, 11, 31, 23, 59, 59);
cal.clear(Calendar.MILLISECOND);
cal.getTime();

オブジェクトとしては Calendar.getInstance() したタイミングでのミリ秒を取得することから、実行タイミングごとに変化するのだが、このとき、ミリ秒が .499 未満ならセーフだが .500 以上だとアウトとなる。
これまでは切り捨てられていたので、どのタイミングのミリ秒でもセーフだった。

教訓

この件に限らず、アプリケーションは DB 仕様に依存した実装にしないことが大切。

ちなみに、この変更は SQL 標準に従ったものらしい。(公式ドキュメントより)

No warning or error is given when such rounding occurs. This behavior follows the SQL standard, and is not affected by the server sql_mode setting.

DISTINCT 指定の SQL で構文エラー

事象

DISTINCT を指定した SQL 実行時に構文エラーが発生した。

原因

MySQL 5.7.5 から、デフォルトの sql_mode に ONLY_FULL_GROUP_BY が指定されたため。
公式ドキュメント

sql_mode は、構文の妥当性をチェックするいわばバリデーションのような機能で、いくつか種類がある。
このうち ONLY_FULL_GROUP_BY は「GROUP BY で集計するとき、SELECT 句や ORDER BY 句で指定するカラムが GROUP BY 句に含まれているか」と「ORDER BY 句のカラムが DISTINCT のカラムリストに含まれているか」をチェックするもので、今回は後者に違反していた。

対処

発行する SQL を修正した。

before
SELECT
    DISTINCT HOGE,
    FUGA
FROM
    T_USER
ORDER BY
    USER_ID DESC

after
SELECT
    DISTINCT HOGE,
    FUGA,
    USER_ID
FROM
    T_USER
ORDER BY
    USER_ID DESC

教訓

発行する SQL が、現在の sql_mode 設定に違反していないか、あるいは違反せざるを得ない場合は該当の sql_mode をオフにしているかを気にしておく。

以上

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

Spring + Hibernate + MySQL なアプリをレプリケーション対応させた話

概要

10年くらい前のスタンダード構成なレガシー Java アプリを、DB のレプリケーション化に対応させたときの知見をまとめておきます。
使用技術とバージョンは以下の通りです。

  • Spring 4.1.6.RELEASE
  • Hibernate 4.3.10.Final
  • MySQL Connector/J 5.1.36
  • MySQL (InnoDB) 5.7.x
    • Master
    • Failover Replica
    • Replica

レプリケーションについて

具体的な内容に入る前に、レプリケーションについておさらいしておきます。

データベースのレプリケーションは多くのデータベース管理システムが採用している概念で、データベースのオリジナルとコピーのマスタースレーブ関係を意味する。マスター側は更新を記録し、それがスレーブ群に通知される。スレーブ側は更新を正しく受け取ったというメッセージを送り、次の更新を受け付けられる状態であることを通知する。

出典 : Wikipedia

参照および更新が可能なマスターと、常にマスターと同期されており1、参照のみ可能なスレーブで DB を構成することをレプリケーションと言います。
INSERTUPDATE といった更新処理をマスターに対して行い、SELECT のような参照処理をスレーブに対して行うことで、DB の負荷分散を図ったり、何らかの理由でマスターがダウンした際に、マスターの代わりとなるフェイルオーバーレプリカを用意しておくことで、ダウンタイムを極小化して高可用性を実現できます。
また、マスターのコピーが存在することになるので、データを冗長化できます。

レプリケーション構成にすることで様々なメリットを享受できますが、分散問い合わせや同期タイムラグ対策など、アプリケーションも相応のつくりになっている必要があります。

実装

Spring の @TransactionalreadOnly オプションを指定することにより、トランザクションごとに問い合わせ先を決定するようにしました。

先述したように、実装として必要な要件は「分散問い合わせ」と「同期タイムラグ対策」でした。

このうち後者については、トランザクションの範囲を広げることで対応しました。
具体的には、これまでの実装は、トランザクションが Repository 層(= 1 問い合わせ)ごとに作成されていたのを、Service 層(=複数問い合わせ)ごとに作成するよう変更しました。
これはつまり、複数問い合わせの中に更新系、参照系が混在していても、1 回のトランザクションごとに問い合わせ先をマスターかスレーブか決定するということです。
スレーブは Readonly なので、更新系が混在している場合は必然的にマスターへの問い合わせになります。
したがって、更新系が混在するトランザクションでは参照系もマスターへの問い合わせになり、厳密な負荷分散はできていないことになりますが、代わりに同期タイムラグを考慮しなくてよいことになりますので、負荷の大きさと実装の複雑さを天秤にかけた結果、実装をシンプルにする選択をしました。

観点 1 トランザクション / 1 問い合わせ 1 トランザクション / n 問い合わせ
負荷分散 更新系と参照系で厳密に分散できる 厳密には分散できない(更新系がある場合はすべてマスター、更新系がない場合はすべてスレーブ)
実装のシンプルさ 同期タイムラグの考慮が必要で複雑になる(更新→参照の場合は参照時に最新のデータであることを考慮) 同期タイムラグの考慮が不要でシンプル(更新→参照の場合でも必ず最新データになる)

そのため、この記事で書くことは「分散問い合わせ」のために行ったこと、になります。

@Transactional について

Spring の @Transactional を使う上で、これについて知っておく必要があったので少し調べました。

公式ドキュメント

@Transactional は、これまでソースコードに入り込んでいた begincommit といったトランザクション管理に関係するコードを、アノテーションとしてメソッドに付与することで、Spring AOP により、そのメソッド開始時に begin され、終了時に commit が行われることで、ソースコードからトランザクション管理を追い出して見通しを良くできる機能です。

この @Transactional には様々なオプションを設定することができますが、今回使用する readOnlytrue / false (default) を指定することができ、これにより問い合わせ先をマスターかスレーブか決定できます。
デフォルトは false で、マスターへの問い合わせとなりますが、true を指定することでスレーブへの問い合わせにできます。

使い方

Spring コンテナで Bean 管理されている(=Spring AOP が実行できる)クラスならば、以下のようにメソッドに付与するだけです。
また、クラス自体に付与することもでき、その場合はクラスのメソッドすべてに付与したのと同義となります。

UserService.java
public class UserService implements Service {

    @Transactional(readOnly = false) // このメソッドが呼び出された段階で begin され、抜けるときに commit される
    public User createUser() {
        // INSERT
    }

    @Transactional(readOnly = true) // 参照系なのでスレーブへの問い合わせ
    public User getUser() {
        // SELECT
    }

    @Transactional(readOnly = false) // 更新系なのでマスターへの問い合わせ
    public void updateUser() {
        // UPDATE
    }

    @Transactional(readOnly = false)
    public void deleteUser() {
        // DELETE
    }
}

Spring の設定ファイルに以下のような記述を追記し、アノテーションを有効にしておきます。

Spring.xml
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="dataSourceTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

MySQL Connector/J によってコネクションを行いますが、接続先として DataSource を以下のように記述します。
公式ドキュメント

最初にマスターのホストを書き、以降、カンマ区切りでスレーブホストを書きます。

Spring.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.ReplicationDriver"/>
    <property name="url" value="jdbc:mysql:replication://127.0.0.1:3306,127.0.0.1:13306"/>
    <property name="username" value="user"/>
    <property name="password" value="password"/>
</bean>

まとめ

レガシーなアプリへの対応だったので、具体的な部分は今後あまり参考にならなさそうですが、レプリケーション自体はスタンダードな構成だと思うので、概念や、どのような対応が必要かを知れたことは収穫だと思います。
DB はほとんどのアプリで使われるものだと思いますので、今回得た知見を活かしたい次第です。


  1. 実際には数ミリ秒の同期遅延が発生するので、アプリ側での考慮が必要な場合がある 

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

Spring boot メモ書き(1)

Spring Boot とは

Spring Framework は Java プラットフォーム向けのオープンソースアプリケーションフレームワークであり、 Webアプリケーションに限らず、広範囲のJavaアプリケーションを開発できる

特徴

DI ( Dependency Injection ) コンテナから始まった経緯があり、 現在でもこのフレームワークのコアになっている機能が DI です。この DI コンテナから始まり、数々の機能を備え、現在ではWebアプリケーションフレームワークとして広く使われ、 コミュニティーも盛んです。

モジュール間のバージョンを決めてくれたり、 Spring FrameworkではXMLで記述する必要があった設定を Java のコードとして記載できたり、 TomcatなどのWebアプリケーションコンテナを内蔵して手早くアプリケーションが動く

脆弱性はあるのか

Spring Boot に深刻な脆弱性が存在

以下のバージョン
1): Spring Boot 1.x

2): Spring Boot 2.x

※Pivotal Softwareより公式発表がないため、上記の中でも影響を受けないマイナーバージョンがある可能性があります。今回は注意喚起も含めて、広くバージョンを記述しています。

https://www.shadan-kun.com/news/20190426_2/

参考にした記事↑(19/4/26)

インストールからSTS起動について

https://eng-entrance.com/java-springboot

↑ここのサイトが分かりやすいかと!

新規Springスタータープロジェクト作ってブラウザ表示まであり

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

【Programming News】Qiitaまとめ記事 July 23, 2019 Vol.9

筆者が昨日2019/7/23(火)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら

2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。

Python

Java

Swift

Kotlin

Rails

JavaScript

jQuery

Vue.js

Angular

Laravel

CakePHP

TypeScript

Google Apps Script

Go言語

R言語

VBA

Nim

MySQL

PostgreSQL

Oracle

Azure

AWS

Docker

IoC

Git

UML

Raspberry

Develop

更新情報

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

令和時代にJava言語の採用を考える〜無難なSDKの選び方

本記事のターゲット

今までWebアプリケーションをJavaで開発してけれど、最近有償化されたの?商用では使えない?など、お悩みの方。

Javaの有償化?ざっくりと概要

Oracle JDKの料金形態

公式ページよりざっくり要約すると「Webサービスの場合1プロセッサ = 月額3,000円」です。
AWSなどのクラウド環境でも大体料金は同じです。EC2では細かく言うとハイパースレッディングの有効/無効で金額が違いますが、JavaでデプロイするようなWebアプリケーションサーバでハイパースレッディングを考えることはあまりなさそうです。
https://www.oracle.com/technetwork/jp/java/javaseproducts/overview/javasesubscriptionfaq-4891443-ja.html
https://www.oracle.com/jp/corporate/pricing/mcpu-189131-ja.html
https://www.oracle.com/technetwork/jp/java/eol-135779-ja.html
http://www.oracle.com/us/corporate/pricing/cloud-licensing-070579.pdf

Oracle JDK以外

OpenJDK

こちらもOracleがビルド。6ヶ月毎にアップデート。サポートも6ヶ月。6ヶ月に一回アップデートが必要。

Amazon Corretto

Amazonが新しく提供をはじめたJDK。
JDK8互換は2023年6月まで無償サポート。
JDK11互換は最短2024年8月まで無償サポート。

他にもいっぱい

こちらのスライドに丁寧なまとめがありました。
https://www.slideshare.net/TakahiroYamada3/how-to-choose-the-best-openjdk-distribution-201905

どうする?

既存のサーバ環境をあまりいじれない場合

Javaのアップデートにリリース計画が必要だったり、JDKのバージョンアップ自体がプロジェクト化されるような規模な会社や部署の場合

有償化といえども「1プロセッサ = 月額3,000円」なので、人経費を掛けて移行するよりも、Oracle JDKを契約したほうが殆どの場合安上がりになりそうです。

それ以外

Amazon CorrettoはAmazon Linux以外もWindows、MacやDebian系LinuxのCentOSなどもサポートしており、ベンダーロックインにはなりません。MacやWindowsのインストーラも付随しており、Webアプリケーションの利用であればAmazon Correttoがおすすめです。

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

javaで配列を使わずにmapする

先日こんな記事を書いた。

ネストしすぎの()を何とかしたい
https://qiita.com/tkturbo/items/04960f4e3e7226de3b46

ここで書いた、

// using custom object
const applier = (val)=>{
  const  o={
    value : val,
    apply : (unary)=>{ o.value=unary(o.value); return o; }
  };
  return o;
};
console.log(applier(name).apply(f0).apply(f1).apply(f2).value);

今日はこれをjava化してみる。

Applier.java
public class Applier<T> {
  private T value;
  public Applier(T value) {
    this.value = value;
  }
  // import java.util.function.Function;
  public <R> Applier<R> apply(Function<? super T, ? extends R> appliable){
    return new Applier<R>(appliable.apply(this.value));
  }
  public T get() { return this.value; }
}

java.util.function.Functionインタフェースを使えばこの通り。

キモになるのはこの1行。

  public <R> Applier<R> apply(Function<? super T, ? extends R> appliable){

これがあるので、入力と出力で型が違っても処理可能となる。

例えば、

System.out.println(
  new Applier<String>("0001")
    .apply(v->Integer.parseInt(v,10))
    .apply(v->v+1)
    .apply(v->String.format("%04d", v))
    .get()
);

こんな風に使える。

個人的には、

    return new Applier<R>(appliable.apply(this.value));

この1行に違和感あるので、こう書きたい所。

public class Applier<T> {
  private T value;
  public Applier(T value) {
    this.value = value;
  }
  public <R> Applier<R> apply(Appliable<? super T, ? extends R> appliable){
    return new Applier<R>(appliable.applyTo(this.value));
  }
  public T get() { return this.value; }
  @FunctionalInterface
  public static interface Appliable<T, R> {
    public R applyTo(T value);
  }
}

public staticなインタフェースを使ってるのは名前空間の汚染を抑止するため。

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

JVM クラスファイルから JSON を生成して階層構造を見る

はじめに

最近ふと JVM を作り始めたのですが、そうなると実際のクラスファイルがどうなっているか確認する機会が多々あります。
javap を使えばいいんですが、クラスファイル全体を階層構造にしてドンと出すようなツールが欲しくなったので、練習を兼ねて、クラスファイルの構造を JSON にして出力するツールを作りました。

作ったもの

https://github.com/chgzm/cls2json
Java SE 11 Edition の仕様に基づいています。

以下のような Hello.java からクラスファイルを生成し、ツールに食わせると JSON が出力されます。

Hello.java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World.");
    }
}
$ javac Hello.java
$ ./cls2json Hello.class 
{"magic":"0xcafebabe","minor_version":0,"major_version":55,"constant_pool_count":29,"constant_pool":["null",{"tag":10,"class_index":6,"name_and_type_index":15},{"tag":9,"class_index":16,"name_and_type_index":17},{"tag":8,"string_index":18},{"tag":10,"class_index":19,"name_and_type_index":20},{"tag":7,"name_index":21},{"tag":7,"name_index":22},{"tag":1,"length":6,"bytes":"<init>"},{"tag":1,"length":3,"bytes":"()V"},{"tag":1,"length":4,"bytes":"Code"},{"tag":1,"length":15,"bytes":"LineNumberTable"},{"tag":1,"length":4,"bytes":"main"},{"tag":1,"length":22,"bytes":"([Ljava/lang/String;)V"},{"tag":1,"length":10,"bytes":"SourceFile"},{"tag":1,"length":10,"bytes":"Hello.java"},{"tag":12,"name_index":7,"descriptor_index":8},{"tag":7,"name_index":23},{"tag":12,"name_index":24,"descriptor_index":25},{"tag":1,"length":13,"bytes":"Hello, World."},{"tag":7,"name_index":26},{"tag":12,"name_index":27,"descriptor_index":28},{"tag":1,"length":5,"bytes":"Hello"},{"tag":1,"length":16,"bytes":"java/lang/Object"},{"tag":1,"length":16,"bytes":"java/lang/System"},{"tag":1,"length":3,"bytes":"out"},{"tag":1,"length":21,"bytes":"Ljava/io/PrintStream;"},{"tag":1,"length":19,"bytes":"java/io/PrintStream"},{"tag":1,"length":7,"bytes":"println"},{"tag":1,"length":21,"bytes":"(Ljava/lang/String;)V"}],"access_Flags":"0x33","this_class":5,"super_class":6,"interfaces_count":0,"interfaces":[],"fields_count":0,"fields":[],"methods_count":2,"methods":[{"access_flags":1,"name_index":7,"descriptor_index":8,"attributes_count":1,"attributes":[{"attribute_name_index":9,"attribute_length":29,"max_stack":1,"max_locals":1,"code_length":5,"code":[42,183,0,1,177],"exception_table_length":0,"exception_table":[],"attributes_count":1,"attributes":[{"attribute_name_index":10,"attribute_length":6,"line_number_table_length":1,"line_number_table":[{"start_pc":0,"line_number":1}]}]}]}, {"access_flags":9,"name_index":11,"descriptor_index":12,"attributes_count":1,"attributes":[{"attribute_name_index":9,"attribute_length":37,"max_stack":2,"max_locals":1,"code_length":9,"code":[178,0,2,18,3,182,0,4,177],"exception_table_length":0,"exception_table":[],"attributes_count":1,"attributes":[{"attribute_name_index":10,"attribute_length":10,"line_number_table_length":2,"line_number_table":[{"start_pc":0,"line_number":3},{"start_pc":8,"line_number":4}]}]}]}],"attributes_count":1,"attributes":[{"attribute_name_index":13,"attribute_length":2,"source_file_index":14}]}

jq を使って整形してみると、階層構造が確認できます。

$ ./cls2json Hello.class | jq
{
  "magic": "0xcafebabe",
  "minor_version": 0,
  "major_version": 55,
  "constant_pool_count": 29,
  "constant_pool": [
    "null",
    {
      "tag": 10,
      "class_index": 6,
      "name_and_type_index": 15
    },
    {
      "tag": 9,
      "class_index": 16,
      "name_and_type_index": 17
    },
    {
      "tag": 8,
      "string_index": 18
    },
    {
      "tag": 10,
      "class_index": 19,
      "name_and_type_index": 20
    },
    {
      "tag": 7,
      "name_index": 21
    },
    {
      "tag": 7,
      "name_index": 22
    },
    {
      "tag": 1,
      "length": 6,
      "bytes": "<init>"
    },
    {
      "tag": 1,
      "length": 3,
      "bytes": "()V"
    },
    {
      "tag": 1,
      "length": 4,
      "bytes": "Code"
    },
    {
      "tag": 1,
      "length": 15,
      "bytes": "LineNumberTable"
    },
    {
      "tag": 1,
      "length": 4,
      "bytes": "main"
    },
    {
      "tag": 1,
      "length": 22,
      "bytes": "([Ljava/lang/String;)V"
    },
    {
      "tag": 1,
      "length": 10,
      "bytes": "SourceFile"
    },
    {
      "tag": 1,
      "length": 10,
      "bytes": "Hello.java"
    },
    {
      "tag": 12,
      "name_index": 7,
      "descriptor_index": 8
    },
    {
      "tag": 7,
      "name_index": 23
    },
    {
      "tag": 12,
      "name_index": 24,
      "descriptor_index": 25
    },
    {
      "tag": 1,
      "length": 13,
      "bytes": "Hello, World."
    },
    {
      "tag": 7,
      "name_index": 26
    },
    {
      "tag": 12,
      "name_index": 27,
      "descriptor_index": 28
    },
    {
      "tag": 1,
      "length": 5,
      "bytes": "Hello"
    },
    {
      "tag": 1,
      "length": 16,
      "bytes": "java/lang/Object"
    },
    {
      "tag": 1,
      "length": 16,
      "bytes": "java/lang/System"
    },
    {
      "tag": 1,
      "length": 3,
      "bytes": "out"
    },
    {
      "tag": 1,
      "length": 21,
      "bytes": "Ljava/io/PrintStream;"
    },
    {
      "tag": 1,
      "length": 19,
      "bytes": "java/io/PrintStream"
    },
    {
      "tag": 1,
      "length": 7,
      "bytes": "println"
    },
    {
      "tag": 1,
      "length": 21,
      "bytes": "(Ljava/lang/String;)V"
    }
  ],
  "access_Flags": "0x33",
  "this_class": 5,
  "super_class": 6,
  "interfaces_count": 0,
  "interfaces": [],
  "fields_count": 0,
  "fields": [],
  "methods_count": 2,
  "methods": [
    {
      "access_flags": 1,
      "name_index": 7,
      "descriptor_index": 8,
      "attributes_count": 1,
      "attributes": [
        {
          "attribute_name_index": 9,
          "attribute_length": 29,
          "max_stack": 1,
          "max_locals": 1,
          "code_length": 5,
          "code": [
            42,
            183,
            0,
            1,
            177
          ],
          "exception_table_length": 0,
          "exception_table": [],
          "attributes_count": 1,
          "attributes": [
            {
              "attribute_name_index": 10,
              "attribute_length": 6,
              "line_number_table_length": 1,
              "line_number_table": [
                {
                  "start_pc": 0,
                  "line_number": 1
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "access_flags": 9,
      "name_index": 11,
      "descriptor_index": 12,
      "attributes_count": 1,
      "attributes": [
        {
          "attribute_name_index": 9,
          "attribute_length": 37,
          "max_stack": 2,
          "max_locals": 1,
          "code_length": 9,
          "code": [
            178,
            0,
            2,
            18,
            3,
            182,
            0,
            4,
            177
          ],
          "exception_table_length": 0,
          "exception_table": [],
          "attributes_count": 1,
          "attributes": [
            {
              "attribute_name_index": 10,
              "attribute_length": 10,
              "line_number_table_length": 2,
              "line_number_table": [
                {
                  "start_pc": 0,
                  "line_number": 3
                },
                {
                  "start_pc": 8,
                  "line_number": 4
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "attributes_count": 1,
  "attributes": [
    {
      "attribute_name_index": 13,
      "attribute_length": 2,
      "source_file_index": 14
    }
  ]
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む