- 投稿日:2019-07-24T23:11:19+09:00
先輩に質問しやすいJavaコーディング集
はじめに
本記事は、「先輩エンジニアに質問したいけど、可読性が原因で叱られる」といった方に向けて
最低限身に着けておきたい書き方をつらつら書いていきます。対象読者
・Java言語が初めての人
・暇な人
・先輩に怒られて心折れかけたかつての自分のような人その1:インデントぐちゃぐちゃ&コメントもめちゃめちゃ
test.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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; } }おわりに
本当はまだまだありますが、こういったことを心がけるだけで意図した回答を得やすくなると思います。
意図した回答を得られないうえに可読性でボコられると辛いので、可読性の向上を意識したコーディングをしたいですね。
※職場では私のソースは汚いと有名です。
- 投稿日:2019-07-24T18:57:57+09:00
【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 └ Dockerfile1. スキーマ定義
テーブルスキーマを記述します。resourceフォルダに置いても良いのですが、今回はトップディレクトリに配置することにします。
データベース名は指定しません。schema.sqlCREATE 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.javapackage 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.javapackage 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のため、個人開発では大きな差です。DockerfileFROM 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.jar4. k8s yamlファイル作成
3.でビルドされたコンテナをk8sにデプロイするためのyamlを書きます。今回のプログラムは単発でSQLを実行して終了するため、
kind: Jobにしてみます。Docker registryのurlは適宜置換してください。
envでDBへの接続情報を定義します。DB_HOSTの値がmysqlとなっているのは、KubernetesのService経由で接続するためです。k8s-job.yamlapiVersion: 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: 05. circleci configファイル作成
本稿のキモです。CircleCIで自動テスト/ビルドを行うための構成設定を書きます。
.circleci/config.ymlversion: 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 ECR1-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レコードが挿入されます。いかがでしたでしょうか。テストをわざと落とす等色々試してみて頂ければと思います。
- 投稿日:2019-07-24T17:33:09+09:00
【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.javaList<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(); } } }以上、お疲れ様でした!
- 投稿日:2019-07-24T16:48:04+09:00
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
- 投稿日:2019-07-24T16:19:22+09:00
【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.gradleapply plugin: 'war' dependencies { // warにTomcatを含めない providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' }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
- 投稿日:2019-07-24T15:31:23+09:00
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) する処理を追加した。beforeCalendar cal = Calendar.getInstance(); cal.set(9999, 11, 31, 23, 59, 59); cal.getTime();↓
afterCalendar 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 を修正した。
beforeSELECT DISTINCT HOGE, FUGA FROM T_USER ORDER BY USER_ID DESC↓
afterSELECT DISTINCT HOGE, FUGA, USER_ID FROM T_USER ORDER BY USER_ID DESC教訓
発行する SQL が、現在の sql_mode 設定に違反していないか、あるいは違反せざるを得ない場合は該当の sql_mode をオフにしているかを気にしておく。
以上
- 投稿日:2019-07-24T14:20:47+09:00
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 を構成することをレプリケーションと言います。
INSERTやUPDATEといった更新処理をマスターに対して行い、SELECTのような参照処理をスレーブに対して行うことで、DB の負荷分散を図ったり、何らかの理由でマスターがダウンした際に、マスターの代わりとなるフェイルオーバーレプリカを用意しておくことで、ダウンタイムを極小化して高可用性を実現できます。
また、マスターのコピーが存在することになるので、データを冗長化できます。レプリケーション構成にすることで様々なメリットを享受できますが、分散問い合わせや同期タイムラグ対策など、アプリケーションも相応のつくりになっている必要があります。
実装
Spring の
@TransactionalでreadOnlyオプションを指定することにより、トランザクションごとに問い合わせ先を決定するようにしました。先述したように、実装として必要な要件は「分散問い合わせ」と「同期タイムラグ対策」でした。
このうち後者については、トランザクションの範囲を広げることで対応しました。
具体的には、これまでの実装は、トランザクションが Repository 層(= 1 問い合わせ)ごとに作成されていたのを、Service 層(=複数問い合わせ)ごとに作成するよう変更しました。
これはつまり、複数問い合わせの中に更新系、参照系が混在していても、1 回のトランザクションごとに問い合わせ先をマスターかスレーブか決定するということです。
スレーブは Readonly なので、更新系が混在している場合は必然的にマスターへの問い合わせになります。
したがって、更新系が混在するトランザクションでは参照系もマスターへの問い合わせになり、厳密な負荷分散はできていないことになりますが、代わりに同期タイムラグを考慮しなくてよいことになりますので、負荷の大きさと実装の複雑さを天秤にかけた結果、実装をシンプルにする選択をしました。
観点 1 トランザクション / 1 問い合わせ 1 トランザクション / n 問い合わせ 負荷分散 更新系と参照系で厳密に分散できる 厳密には分散できない(更新系がある場合はすべてマスター、更新系がない場合はすべてスレーブ) 実装のシンプルさ 同期タイムラグの考慮が必要で複雑になる(更新→参照の場合は参照時に最新のデータであることを考慮) 同期タイムラグの考慮が不要でシンプル(更新→参照の場合でも必ず最新データになる) そのため、この記事で書くことは「分散問い合わせ」のために行ったこと、になります。
@TransactionalについてSpring の
@Transactionalを使う上で、これについて知っておく必要があったので少し調べました。→ 公式ドキュメント
@Transactionalは、これまでソースコードに入り込んでいたbeginやcommitといったトランザクション管理に関係するコードを、アノテーションとしてメソッドに付与することで、Spring AOP により、そのメソッド開始時にbeginされ、終了時にcommitが行われることで、ソースコードからトランザクション管理を追い出して見通しを良くできる機能です。この
@Transactionalには様々なオプションを設定することができますが、今回使用するreadOnlyはtrue / false (default)を指定することができ、これにより問い合わせ先をマスターかスレーブか決定できます。
デフォルトはfalseで、マスターへの問い合わせとなりますが、trueを指定することでスレーブへの問い合わせにできます。使い方
Spring コンテナで Bean 管理されている(=Spring AOP が実行できる)クラスならば、以下のようにメソッドに付与するだけです。
また、クラス自体に付与することもでき、その場合はクラスのメソッドすべてに付与したのと同義となります。UserService.javapublic 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 はほとんどのアプリで使われるものだと思いますので、今回得た知見を活かしたい次第です。
実際には数ミリ秒の同期遅延が発生するので、アプリ側での考慮が必要な場合がある ↩
- 投稿日:2019-07-24T08:30:15+09:00
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スタータープロジェクト作ってブラウザ表示まであり
- 投稿日:2019-07-24T07:15:56+09:00
【Programming News】Qiitaまとめ記事 July 23, 2019 Vol.9
筆者が昨日2019/7/23(火)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。
Python
- Tips
- Apps
Java
Swift
Kotlin
Rails
JavaScript
- Tips
- Turn.js
jQuery
- Beginner
Vue.js
Angular
Laravel
CakePHP
- Tips
TypeScript
- Beginner
- Tips
Google Apps Script
- Tips
Go言語
- Beginner
- Tips
R言語
- Tips
VBA
Nim
MySQL
PostgreSQL
Oracle
Azure
- Azure DevOps
- Tips
AWS
- Beginner
- AWS CDK
Docker
IoC
Git
UML
- PlantUML
Raspberry
Develop
- Tips
- Apps
更新情報
Kotlin
- Kotlin入門
Android
- Library
Java
IDE
- 投稿日:2019-07-24T06:13:35+09:00
令和時代に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.pdfOracle 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がおすすめです。
- 投稿日:2019-07-24T04:29:35+09:00
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.javapublic 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なインタフェースを使ってるのは名前空間の汚染を抑止するため。
- 投稿日:2019-07-24T00:30:10+09:00
JVM クラスファイルから JSON を生成して階層構造を見る
はじめに
最近ふと JVM を作り始めたのですが、そうなると実際のクラスファイルがどうなっているか確認する機会が多々あります。
javapを使えばいいんですが、クラスファイル全体を階層構造にしてドンと出すようなツールが欲しくなったので、練習を兼ねて、クラスファイルの構造を JSON にして出力するツールを作りました。作ったもの
https://github.com/chgzm/cls2json
Java SE 11 Edition の仕様に基づいています。例
以下のような
Hello.javaからクラスファイルを生成し、ツールに食わせると JSON が出力されます。Hello.javapublic 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 } ] }



