- 投稿日:2020-03-16T23:13:55+09:00
Minecraft modding 1.12.2で参考になるサイトまとめ
導入
intelij IDEAのプラグインを利用してコマンドを一切打たずに簡単に導入する方法
https://qiita.com/Hiroya_W/items/7ad9bad0387ef3688d1e
動画で基本的なMOD製作が学べる(英語)
https://www.youtube.com/watch?v=rmWBP5ifDlw&list=PLDhiRTZ_vnoX4bx_BJccGV7MjpXUfVJSn
ブロックの追加からTileEntitySpecialRenderまで、幅広く学べるサイト
https://shadowfacts.net/tutorials/forge-modding-112/
サイトのコード
https://github.com/shadowfacts/TutorialMod
1.12.2では、setUnlocalizedNameをsetTranslationKeyにする、などの変更が必要です、またCreativeTabの追加はエラーが多いので、削除して、他のサイトをもとに作成するのがいいかもしれません。面倒な変更はありますが、非常におすすめのサイトなのでぜひチェックしてみてください。公開
Curse Forgeでの公開
https://qiita.com/C6H2Cl2/items/a972d6c25cee66b032c5
プロジェクト作成はこちらのアドレスに変更されています。くれぐれもご注意ください。
https://www.curseforge.com/project/create
- 投稿日:2020-03-16T22:54:31+09:00
初心者から始めるJava、継承
追記2020/3/16
クラス名のつけ方で間違いがあったことを、コメントで指摘いただいたので訂正。はじめに
この記事は備忘録である。
参考書レベルの内容だが、本記事に掲載するコードについては、
間違えたものが中心となる。これは実際にコーディング中に間違えた部分を掲載し、自分で反省するために投稿するという目的によるもの。
また、後日にJavaSilver試験問題の勉強を兼ねて復習するため、深い部分の話はここでは触れない。環境
言語:Java11、JDK13.0.2
動作環境:Windows10クラスの拡張
今まで書いてきたクラスはどれも独立して書かれたものだったが、毎回メンバ(フィールドとメソッド)を設定する必要があった。
Cat
クラス程度ならば頑張れば作れるだろうが、膨大なメンバ(フィールドとメソッド)を持つクラスを毎回すべて書くのは現実的ではない。
Javaは、既存のクラスが持っているメンバをそのまま引き継ぎつつ、独自のクラスを作るためのクラスの拡張(extend)機能がある。extendedHousecatclass WildCat { protected double weight; protected double height; protected String name; public static int sumCats = 0; public wildCat() { weight = 0.0; height = 0.0; sumCats++; System.out.println("ニャーゴ、猫をまた一匹見つけたよ。"); } public WildCat(double we,double he) { weight = we; height = he; sumCats++; System.out.println("ニャーゴ、猫をまた一匹見つけたよ。"); System.out.println("見つけた猫は、体重" + weight + "kg、身長" + height + "cmみたい。"); } public void measureWeightHeight(double w,double h) { weight = w; height = h; System.out.println("この猫を計測してみました。"); System.out.println("体重は" + weight + "kg、身長は" + height + "cmあります。"); } public static void countCats() { System.out.println("全部で" + sumCats + "匹の猫を見つけたよ。"); } } //ヤマネコ(Wildcat)のクラスをベースに、イエネコ(Housecat)のクラスを作ろう。 class HouseCat extends WildCat { private String collorColor; private String nickname; public void callNickname(String s) { nickname = s; System.out.printlm("この猫に、新しく" + nickname + "ってニックネームを付けたよ。"); } }ヤマネコ(
WildCat
)クラスを拡張して、イエネコ(HouseCat,Cat
……色々呼び方はある)クラスを作った。このとき、Wildcat
とHouseCat
は親と子の関係になり、それぞれsuper class
とsub class
になる。スーパーなのかサブなのか
WildCat
クラスを拡張したHouseCat
クラスには、親であるWildCat
クラスが持っていたメンバ(フィールドとメソッド)が引き継がれているので、
HouseCat cat1 = new HouseCat();
cat1.measureWeightHeight(5.0,50.5);
とすることで、体重5.0kgで身長50.5cmの
HouseCat
オブジェクトを生成できる。
もちろん、collorColor(首輪の色)とnickname(あだ名)フィールドも持っているし、
public void callNickname()
メソッドであだ名をつけることもできる。あえて親のコンストラクタを使いたい
HouseCat
クラスにはコンストラクタがないが、sub class
でコンストラクタを用意しなかった場合、super class
のコンストラクタのうち引数のないコンストラクタが、HouseCat
オブジェクトを生成したときに自動的に呼び出される。今回はpublic WildCat()
の方だ。
だが例えば、super(8.0,60.8)
としてコンストラクタ内で呼び出してやれば、8.0kg、60.8cmのイエネコとして呼び出されることになる。privateより内向的で、privateより社交的
WildCat
クラスのフィールドがいつもと違うのに気が付いただろうか?(実はここまで書き進めてから直した)。protected
は、「同じクラスか、子であるサブクラスからであればアクセスできる」という意味の修飾子になる。
サブクラスをどんどん作って発展させたいスーパークラスになると予想できるときは、protected
にしておくと後から扱いやすいだろう。
アクセス制限としてはパッケージも控えているが、ここでは割愛。終わりに
継承後のサブクラスが持っていなければならないもの、スーパークラスのメンバがどこからまでのアクセスを認めているのか。こういった問題にまだ悩まされているので、ここの理解はまだまだ甘いのが現状。
以前扱ったJava11のリファレンスも、少し覗いただけでextends
がわちゃわちゃと出てきている。この家系図を理解したとき、本当のJavaが見えてくるのだろう、多分。参考
出来るだけ自分で変数や式を書いてコンパイルしているので、完全に引用する場合はその旨記述する。
やさしいJava 第7版
Java SE11 Silver 問題集(通称黒本)Java® Platform, Standard Edition & Java Development Kit
バージョン11 API仕様
- 投稿日:2020-03-16T22:05:55+09:00
EclipseのCheckStyleでCheckstyle execution failed due to an internal error.となった時の取あえずの対応方法
※. このページには、根本的な解決方法は書いていません。解決方法のわかる方は教えてください。
EclipseのProblemビューにエラーが出た。
何年も続いているプロジェクトをインポートするとちょくちょく出る。
Discription ...省略... 型 Checkstyle execution failed due to an internal error. Please check the error log for details Checkstyle 問題 Javaのソースで見つけた CHECKSTYLE:OFF って何?から知るCheckstyleなんてQiitaに投稿しちゃうぐらいCheckStyleがわかっていない。
このエラーが出ていても動く・・・がプロジェクトに赤×印が出ているのは嫌。このエラーが出たら取あえず
{ワークスペースのディレクトリ}/.metadata/.log
を見てみます。事象 : Property 'allowMissingPropertyJavadoc' does not exist
- 環境
- Eclipse IDE for Enterprise Java Developers.Version: 2018-12 (4.10.0)
- Eclipse Checkstyle 8.28.0.202001092018
.log!ENTRY net.sf.eclipsecs.core 4 0 2020-03-16 10:17:02.345 !MESSAGE Checkstyle-Plugin: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'allowMissingPropertyJavadoc' does not exist, please check the documentation !STACK 0 com.puppycrawl.tools.checkstyle.api.CheckstyleException: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'allowMissingPropertyJavadoc' does not exist, please check the documentation at com.puppycrawl.tools.checkstyle.Checker.setupChild(Checker.java:473) ...省略...checkstye.xml<module name="Checker"> <property name="severity" value="warning"/> <module name="TreeWalker"> ...省略... <module name="JavadocMethod"> <!-- ↓これがエラー --> <property name="allowMissingPropertyJavadoc" value="true"/> <property name="suppressLoadErrors" value="true"/> </module>原因 :
allowMissingPropertyJavadoc
がバージョン8.25で削除されたからRelease 8.25
JavadocMethodCheck: remove deprecated properties ignoreMethodNamesRegex, minLineCount, allowMissingJavadoc, allowMissingPropertyJavadoc. Author: rnveach #7096
checkstyle – Release Notes対応 :
allowMissingPropertyJavadoc
を削除してMissingJavadocMethodCheck
を追加する(ざっくり訳)
このプロパティーは、8.20でMissingJavadocMethodCheckに移行したため、8.25で削除されました。前と同じ動作を継続するには、MissingJavadocMethodCheckを追加することをお勧めします。
checkstyle - Gradle checkstyleTest fails "CheckstyleException: Property 'allowMissingPropertyJavadoc' does not exist" - Stack Overflow書き方これで本当にあっているのだろうか・・・・
checkstye.xml<module name="Checker"> <property name="severity" value="warning"/> <module name="TreeWalker"> ...省略... <module name="JavadocMethod"> <property name="suppressLoadErrors" value="true"/> </module> <!-- ↓追加してみた --> <module name="MissingJavadocMethodCheck"> <property name="allowMissingPropertyJavadoc" value="true"/> </module>事象 : Property 'suppressLoadErrors' does not exist
- 環境
- Eclipse IDE for Enterprise Java Developers.Version: 2018-12 (4.10.0)
- Eclipse Checkstyle 8.28.0.202001092018
.log!ENTRY net.sf.eclipsecs.core 4 0 2020-03-16 10:53:47.566 !MESSAGE Checkstyle-Plugin: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'suppressLoadErrors' does not exist, please check the documentation !STACK 0 com.puppycrawl.tools.checkstyle.api.CheckstyleException: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'suppressLoadErrors' does not exist, please check the documentation at com.puppycrawl.tools.checkstyle.Checker.setupChild(Checker.java:473) ...省略...checkstye.xml<module name="Checker"> <property name="severity" value="warning"/> <module name="TreeWalker"> ...省略... <module name="JavadocMethod"> <!-- ↓これがエラー --> <property name="suppressLoadErrors" value="true"/> </module> <module name="MissingJavadocMethodCheck"> <property name="allowMissingPropertyJavadoc" value="true"/> </module>原因 :
suppressLoadErrors
は削除された、かもしれないからcheckstyle – Release Notesを
suppressLoadErrors
で検索しても削除された的な記述はない。
が、checkstyle – Javadoc CommentsのJavadocMethod
にsuppressLoadErrors
はない・・・なんだろう・・・きっと削除されたんだろう・・・8.26あたりで。(雑な訳)
プロパティーlogLoadErrors
とsuppressLoadErrors
を廃止し、これらの機能を削除したようです。
upgrade to checkstyle 8.26 · Issue #246 · checkstyle/sonar-checkstyle対応 : 取あえず
suppressLoadErrors
を削除する代わりに何かを定義したほうがいい気がするが、どうすればいいのかわからないので取あえず削除しておく
checkstye.xml<module name="Checker"> <property name="severity" value="warning"/> <module name="TreeWalker"> ...省略... <module name="JavadocMethod" /> <module name="MissingJavadocMethodCheck"> <property name="allowMissingPropertyJavadoc" value="true"/> </module>事象 : TreeWalker is not allowed as a parent of LineLength
- 環境
- Eclipse IDE for Enterprise Java Developers.Version: 2018-12 (4.10.0)
- Eclipse Checkstyle 8.28.0.202001092018
!ENTRY net.sf.eclipsecs.core 4 0 2020-03-16 12:25:10.250 !MESSAGE Checkstyle-Plugin: cannot initialize module TreeWalker - TreeWalker is not allowed as a parent of LineLength Please review 'Parent Module' section for this Check in web documentation if Check is standard. !STACK 0 com.puppycrawl.tools.checkstyle.api.CheckstyleException: cannot initialize module TreeWalker - TreeWalker is not allowed as a parent of LineLength Please review 'Parent Module' section for this Check in web documentation if Check is standard. at com.puppycrawl.tools.checkstyle.Checker.setupChild(Checker.java:473) ...省略...checkstye.xml<module name="Checker"> <property name="severity" value="warning"/> <module name="TreeWalker"> ...省略... <module name="LineLength"> <property name="max" value="120"/> </module> ...省略...原因 : 8.24から
LineLength
の親モジュールがTreeWalker
からChecker
になったからRelease 8.24
Change LineLength Check parent from TreeWalker to Checker. Author: rnveach, Roman Ivanov #2116
checkstyle – Release Notes対応 :
LineLength
をChecker
直下に移動するcheckstye.xml<module name="Checker"> <property name="severity" value="warning"/> <module name="LineLength"> <property name="max" value="120"/> </module> <module name="TreeWalker"> ...省略...CheckStyleのチェック項目で関わったのぐらいは調べてみた
- 参考
module property 説明 削除と追加情報 JavadocMethod - メソッドまたはコンストラクタのJavadocをチェックする 3.0追加 JavadocMethod allowMissingPropertyJavadoc プロパティメソッドのJavadocそのものが存在しない事を許可するかどうかを設定します。
プロパティメソッドとは、いわゆるGetter/Setterメソッドの事を指します。8.25削除 JavadocMethod logLoadErrors 英語が難しくてよくわからない・・・ 8.26削除? JavadocMethod suppressLoadErrors logLoadErrorsがtrueに設定されている場合、
logLoadErrorsがtrueに設定されたときに生成された違反は、
チェックスタイルレポートで違反として報告されなくなる。8.26削除? MissingJavadocMethod - メソッドまたはコンストラクタの欠落したJavadocコメントをチェックします。 8.21追加 MissingJavadocMethod allowMissingPropertyJavadoc プロパティ(セッターとゲッター)の
アクセサー・メソッドで欠落しているJavadocを許可するかどうかを制御します。8.21追加
- 投稿日:2020-03-16T21:12:17+09:00
【アルゴリズム入門】Javaで挿入ソートを実装する
スケベな物の見過ぎだと思いますが挿入という文字列を見るだけでよからぬ気持ちになりますね。
挿入ソートは基本のソートアルゴリズムの一つで、配列を整列済みの部分と未整列の部分に分けてソートを行うものです。
ソースコード
InsertSort.javaclas InsertSort { public static void main(String[] args) { int[] data = {30, 60, 70, 90, 20}; sort(data); for(int element : data) { System.out.println(element); } } public static void sort(int[] data) { for(int i = 1; i < data.length; i ++) { int temp = data[i]; int j = i; while(i > 0 && temp < data[j - 1]) { data[j] = data[j - 1]; j --; } data[j] = temp; } } }
- 投稿日:2020-03-16T20:35:22+09:00
Visual Studio CodeによるSpring5 MVC Webアプリ開発 SQLServer編
はじめに
HelloWorld作成編で作成したプロジェクトを拡張していきます。
SQLServerはこちらで作成したDB、テーブルを流用します。環境
OS:Windows 10 Pro 64bit
Editor:Visual Studio Code 1.42.1
JDK:AdoptOpenJDK 11.0.6+10 x64
Apache Maven:v3.6.3
Apache Tomcat:v9.0.31プロジェクト作成
HelloWorldで作成したプロジェクトを「D:\JAVA\Project\sqlSample」にコピーして作成しました。
pom.xml
SQLServerにアクセスする為に必要なRepositoryを追加する。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>8.2.1.jre11</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> <scope>compile</scope> </dependency>pom.xml全体です。
pom.xml<?xml version="1.0" encoding="UTF-8"?> <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>com.example</groupId> <artifactId>sqlSample1</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>11</java.version> <spring.version>5.2.4.RELEASE</spring.version> <!-- web.xmlが無い場合でもビルドを実行する --> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>8.2.1.jre11</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.11.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> <version>3.0.4.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency> </dependencies> </project>Model作成
ビジネスロジックを記述するservice部とDB接続(SQL文発行)するpersistence部の作成、及びconfigファイルを作成します。
service部作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example」serviceフォルダを作成します。
その直下にconfigフォルダを作成します。
configフォルダにServiceConfig.javaを作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example └─service └─config └ServiceConfig.javaServiceConfig.javapackage com.example.service.config; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan(basePackages = "com.example.service") @EnableTransactionManagement public class ServiceConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); return transactionManager; } }persistence部作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example」persistenceフォルダを作成します。
その直下にconfigフォルダ、entityフォルダ、repositoryフォルダを作成します。
configフォルダにPersistenceConfig.javaを作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example └─persistence ├─config │ └─PersistenceConfig.java ├─entity └─repositoryPersistenceConfig.javaにはSQLServerに接続するための設定を記述します。
PersistenceConfig.javapackage com.example.persistence.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import com.zaxxer.hikari.HikariDataSource; @Configuration @ComponentScan(basePackages = "com.example.persistence.repository") @PropertySource("classpath:jdbc.properties") public class PersistenceConfig { @Bean public DataSource dataSource(@Value("${jdbc.driverClassName}") String driverClassName, @Value("${jdbc.url}") String url, @Value("${jdbc.username}") String username, @Value("${jdbc.password}") String password) { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setJdbcUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setMinimumIdle(10); dataSource.setMaximumPoolSize(300); dataSource.setConnectionInitSql("SELECT 0"); return dataSource; } @Bean public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) { NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); return jdbcTemplate; } }resources部作成
「D:\JAVA\Project\sqlSample\src\main」にresourcesフォルダを作成する
resourcesフォルダにjdbc.propertiesを作成します。D:\JAVA\Project\sqlSample\src\main └─resources └─jdbc.propertiesSQLServerに接続するための接続情報を記述します。
jdbc.propertiesjdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.url=jdbc:sqlserver://xxx:1433;databaseName=Training01;QuotedID=NO // SQLServerのホスト名もしくはIPアドレスを指定する。 jdbc.username=xxx // 接続ユーザー名を指定する。 jdbc.password=xxx // 接続ユーザーのパスワードを指定する。WebAppInitializer.java変更
DB接続とビジネスロジック用のModelが使用出来るようにgetRootConfigClassesにPersistenceConfig.classとServiceConfig.classを追加する。
@Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class}; }WebAppInitializer.java全体です。
WebAppInitializer.javapackage com.example.web.config; import java.nio.charset.StandardCharsets; import javax.servlet.Filter; import com.example.persistence.config.PersistenceConfig; import com.example.service.config.ServiceConfig; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /** * ビジネスロジックなど、Spring MVC以外に関するJava Configクラスを指定します。 */ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class}; } /** * Spring MVCに関するJava Configクラスを指定します。 */ @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] {MvcConfig.class}; } /** * DispatcherServletに対するURLパターンを指定します。 * "/"を指定することで、全リクエストをDispatcherServletが受け取ります。 */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } /** * サーブレットフィルターを指定します。 * 複数のフィルターがあった場合、配列に指定した順番に実行されます。 */ @Override protected Filter[] getServletFilters() { return new Filter[]{ new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)}; } }ここまでが前準備になります。
entity作成
DBからのデータを受けるentityを作成します。
D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence └─entity └─ProductsMaster.javaProductsMaster.javapackage com.example.persistence.entity; import lombok.Data; @Data public class ProductsMaster { private String ProductsCode; private String ProductsName; private Integer UnitPrice; public ProductsMaster() {} public ProductsMaster( String ProductsCode, String ProductsName, Integer UnitPrice ) { this.ProductsCode = ProductsCode; this.ProductsName = ProductsName; this.UnitPrice = UnitPrice; } }repository作成
DBに接続してSQL文を発行するrepositoryを作成します。
D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence └─repository ├─ProductsMasterRepository.java └─ProductsMasterRepositoryImpl.javaProductsMasterRepository.javapackage com.example.persistence.repository; import java.util.List; import com.example.persistence.entity.ProductsMaster; public interface ProductsMasterRepository { List<ProductsMaster> productsMasterList(); }ProductsMasterRepositoryImpl.javapackage com.example.persistence.repository; import java.util.List; import com.example.persistence.entity.ProductsMaster; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class ProductsMasterRepositoryImpl implements ProductsMasterRepository { @Autowired NamedParameterJdbcTemplate jdbcTemplate; @Override public List<ProductsMaster> productsMasterList() { String strSQL = "SELECT * FROM TB_TestTable ORDER BY ID"; List<ProductsMaster> pMList = jdbcTemplate.query(strSQL, (rs, rowNum) -> new ProductsMaster( rs.getString("ProductsName"), rs.getString("ProductsCode"), rs.getInt("UnitPrice") ) ); return pMList; } }service作成
ビジネスロジックとなるserviceを作成します。
今回の内容ではわざわざ作成する必要性はないのですが、1例として作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example └─service ├─ProductsMasterService.java └─ProductsMasterServiceImpl.javaProductsMasterService.javapackage com.example.service; import java.util.List; import com.example.persistence.entity.ProductsMaster; public interface ProductsMasterService { List<ProductsMaster> productsMasterList(); }ProductsMasterServiceImpl.javapackage com.example.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.persistence.entity.ProductsMaster; import com.example.persistence.repository.ProductsMasterRepository; @Service public class ProductsMasterServiceImpl implements ProductsMasterService { @Autowired ProductsMasterRepository productsMasterRepository; @Override public List<ProductsMaster> productsMasterList() { List<ProductsMaster> pMList = productsMasterRepository.productsMasterList(); return pMList; } }form作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example\web」にresourcesフォルダを作成します。
resourcesフォルダにjdbc.propertiesを作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example\web └─form └─ProductsMasterForm.javaProductsMasterForm.javapackage com.example.web.form; import java.util.List; import com.example.persistence.entity.ProductsMaster; import lombok.Data; @Data public class ProductsMasterForm { private List<ProductsMaster> helloList; }Controllerの作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example\web\controller」にProductsMasterController.javaを作成します。
D:\JAVA\Project\sqlSample\src\main\java\com\example\web └─controller └─ProductsMasterController.javaProductsMasterController.javapackage com.example.web.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.example.service.ProductsMasterService; import com.example.web.form.ProductsMasterForm; @Controller @RequestMapping("/hello") public class ProductsMasterController { @Autowired ProductsMasterService productsMasterService; @GetMapping("/index") public String indexGet(Model model) { ProductsMasterForm productsMasterForm = new ProductsMasterForm(); productsMasterForm.setHelloList(productsMasterService.productsMasterList()); model.addAttribute("productsMasterForm", productsMasterForm); return "productsMasterForm/index"; } }Viewの作成
「D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates」にproductsMasterフォルダを作成します。
productsMasterフォルダにindex.htmlを作成します。D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates └─productsMaster └─index.htmlindex.html<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Spring5 MVC sqlSample01</title> </head> <body> <h1>Hello Spring Products List</h1> <table border="1"> <thead> <tr> <th>製品コード</th> <th>製品名</th> <th>単価</th> </tr> </thead> <tbody> <tr th:each="pm : ${productsMasterForm.pmList}" th:object="${pm}"> <td th:text="*{ProductsCode}"></td> <td th:text="*{ProductsName}"></td> <td th:text="*{UnitPrice}"></td> </tr> </tbody> </table> </body> </html>コンパイル/パッケージ
次のコマンドでコンパイル及びwarファイルの作成が出来ます。
mvn package動作確認
コマンドパレット
Tomcat: Add Tomcat ServerTomcatフォルダ選択ダイアログが表示されますので、Tomcatフォルダ(D:\JAVA\Tomcat\apache-tomcat-9.0.31)を選択します。
コマンドパレット
Tomcat: Run on Tomcat Serverwarファイル選択ダイアログが表示されますので、「mvn package」で作成されたwarファイル(D:\JAVA\Project\SpringSample01\target\sqlSample1-1.0-SNAPSHOT.war)を選択します。
「http://localhost:8080/」にアクセスして下さい。
sqlSample-1.0-SNAPSHOTをクリックして下さい。
コード修正→動作確認の際、Tomactに配置したwarファイルを都度削除しないと上手く反映されませんでした。
hot deployが出来れば良いのですか、見つけられませんでした。今回のサンプルソース
GitHubにアップしました。
https://github.com/t-skri1/SpringSample02まとめ
簡易的ですが、Webアプリになりました。
次回はSpring Securityを組み込みます。
- 投稿日:2020-03-16T20:35:22+09:00
Visual Studio CodeによるSpring5 MVC Webアプリ開発 SQLServer接続編
はじめに
HelloWorld作成編で作成したプロジェクトを拡張していきます。
SQLServerはこちらで作成したDB、テーブルを流用します。環境
OS:Windows 10 Pro 64bit
Editor:Visual Studio Code 1.42.1
JDK:AdoptOpenJDK 11.0.6+10 x64
Apache Maven:v3.6.3
Apache Tomcat:v9.0.31プロジェクト作成
HelloWorldで作成したプロジェクトを「D:\JAVA\Project\sqlSample」にコピーして作成しました。
pom.xml
SQLServerにアクセスする為に必要なRepositoryを追加する。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>8.2.1.jre11</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> <scope>compile</scope> </dependency>pom.xml全体です。
pom.xml<?xml version="1.0" encoding="UTF-8"?> <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>com.example</groupId> <artifactId>sqlSample1</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>11</java.version> <spring.version>5.2.4.RELEASE</spring.version> <!-- web.xmlが無い場合でもビルドを実行する --> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>8.2.1.jre11</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.11.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> <version>3.0.4.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency> </dependencies> </project>Model作成
ビジネスロジックを記述するservice部とDB接続(SQL文発行)するpersistence部の作成、及びconfigファイルを作成します。
service部作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example」serviceフォルダを作成します。
その直下にconfigフォルダを作成します。
configフォルダにServiceConfig.javaを作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example └─service └─config └ServiceConfig.javaServiceConfig.javapackage com.example.service.config; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan(basePackages = "com.example.service") @EnableTransactionManagement public class ServiceConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); return transactionManager; } }persistence部作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example」persistenceフォルダを作成します。
その直下にconfigフォルダ、entityフォルダ、repositoryフォルダを作成します。
configフォルダにPersistenceConfig.javaを作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example └─persistence ├─config │ └─PersistenceConfig.java ├─entity └─repositoryPersistenceConfig.javaにはSQLServerに接続するための設定を記述します。
PersistenceConfig.javapackage com.example.persistence.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import com.zaxxer.hikari.HikariDataSource; @Configuration @ComponentScan(basePackages = "com.example.persistence.repository") @PropertySource("classpath:jdbc.properties") public class PersistenceConfig { @Bean public DataSource dataSource(@Value("${jdbc.driverClassName}") String driverClassName, @Value("${jdbc.url}") String url, @Value("${jdbc.username}") String username, @Value("${jdbc.password}") String password) { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setJdbcUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setMinimumIdle(10); dataSource.setMaximumPoolSize(300); dataSource.setConnectionInitSql("SELECT 0"); return dataSource; } @Bean public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) { NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); return jdbcTemplate; } }resources部作成
「D:\JAVA\Project\sqlSample\src\main」にresourcesフォルダを作成する
resourcesフォルダにjdbc.propertiesを作成します。D:\JAVA\Project\sqlSample\src\main └─resources └─jdbc.propertiesSQLServerに接続するための接続情報を記述します。
jdbc.propertiesjdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.url=jdbc:sqlserver://xxx:1433;databaseName=Training01;QuotedID=NO // SQLServerのホスト名もしくはIPアドレスを指定する。 jdbc.username=xxx // 接続ユーザー名を指定する。 jdbc.password=xxx // 接続ユーザーのパスワードを指定する。WebAppInitializer.java変更
DB接続とビジネスロジック用のModelが使用出来るようにgetRootConfigClassesにPersistenceConfig.classとServiceConfig.classを追加する。
@Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class}; }WebAppInitializer.java全体です。
WebAppInitializer.javapackage com.example.web.config; import java.nio.charset.StandardCharsets; import javax.servlet.Filter; import com.example.persistence.config.PersistenceConfig; import com.example.service.config.ServiceConfig; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /** * ビジネスロジックなど、Spring MVC以外に関するJava Configクラスを指定します。 */ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class}; } /** * Spring MVCに関するJava Configクラスを指定します。 */ @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] {MvcConfig.class}; } /** * DispatcherServletに対するURLパターンを指定します。 * "/"を指定することで、全リクエストをDispatcherServletが受け取ります。 */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } /** * サーブレットフィルターを指定します。 * 複数のフィルターがあった場合、配列に指定した順番に実行されます。 */ @Override protected Filter[] getServletFilters() { return new Filter[]{ new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)}; } }ここまでが前準備になります。
entity作成
DBからのデータを受けるentityを作成します。
D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence └─entity └─ProductsMaster.javaProductsMaster.javapackage com.example.persistence.entity; import lombok.Data; @Data public class ProductsMaster { private String ProductsCode; private String ProductsName; private Integer UnitPrice; public ProductsMaster() {} public ProductsMaster( String ProductsCode, String ProductsName, Integer UnitPrice ) { this.ProductsCode = ProductsCode; this.ProductsName = ProductsName; this.UnitPrice = UnitPrice; } }repository作成
DBに接続してSQL文を発行するrepositoryを作成します。
D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence └─repository ├─ProductsMasterRepository.java └─ProductsMasterRepositoryImpl.javaProductsMasterRepository.javapackage com.example.persistence.repository; import java.util.List; import com.example.persistence.entity.ProductsMaster; public interface ProductsMasterRepository { List<ProductsMaster> productsMasterList(); }ProductsMasterRepositoryImpl.javapackage com.example.persistence.repository; import java.util.List; import com.example.persistence.entity.ProductsMaster; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class ProductsMasterRepositoryImpl implements ProductsMasterRepository { @Autowired NamedParameterJdbcTemplate jdbcTemplate; @Override public List<ProductsMaster> productsMasterList() { String strSQL = "SELECT * FROM TB_TestTable ORDER BY ID"; List<ProductsMaster> pMList = jdbcTemplate.query(strSQL, (rs, rowNum) -> new ProductsMaster( rs.getString("ProductsName"), rs.getString("ProductsCode"), rs.getInt("UnitPrice") ) ); return pMList; } }service作成
ビジネスロジックとなるserviceを作成します。
今回の内容ではわざわざ作成する必要性はないのですが、1例として作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example └─service ├─ProductsMasterService.java └─ProductsMasterServiceImpl.javaProductsMasterService.javapackage com.example.service; import java.util.List; import com.example.persistence.entity.ProductsMaster; public interface ProductsMasterService { List<ProductsMaster> productsMasterList(); }ProductsMasterServiceImpl.javapackage com.example.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.persistence.entity.ProductsMaster; import com.example.persistence.repository.ProductsMasterRepository; @Service public class ProductsMasterServiceImpl implements ProductsMasterService { @Autowired ProductsMasterRepository productsMasterRepository; @Override public List<ProductsMaster> productsMasterList() { List<ProductsMaster> pMList = productsMasterRepository.productsMasterList(); return pMList; } }form作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example\web」にresourcesフォルダを作成します。
resourcesフォルダにjdbc.propertiesを作成します。D:\JAVA\Project\sqlSample\src\main\java\com\example\web └─form └─ProductsMasterForm.javaProductsMasterForm.javapackage com.example.web.form; import java.util.List; import com.example.persistence.entity.ProductsMaster; import lombok.Data; @Data public class ProductsMasterForm { private List<ProductsMaster> helloList; }Controllerの作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example\web\controller」にProductsMasterController.javaを作成します。
D:\JAVA\Project\sqlSample\src\main\java\com\example\web └─controller └─ProductsMasterController.javaProductsMasterController.javapackage com.example.web.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.example.service.ProductsMasterService; import com.example.web.form.ProductsMasterForm; @Controller @RequestMapping("/hello") public class ProductsMasterController { @Autowired ProductsMasterService productsMasterService; @GetMapping("/index") public String indexGet(Model model) { ProductsMasterForm productsMasterForm = new ProductsMasterForm(); productsMasterForm.setHelloList(productsMasterService.productsMasterList()); model.addAttribute("productsMasterForm", productsMasterForm); return "productsMasterForm/index"; } }Viewの作成
「D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates」にproductsMasterフォルダを作成します。
productsMasterフォルダにindex.htmlを作成します。D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates └─productsMaster └─index.htmlindex.html<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Spring5 MVC sqlSample01</title> </head> <body> <h1>Hello Spring Products List</h1> <table border="1"> <thead> <tr> <th>製品コード</th> <th>製品名</th> <th>単価</th> </tr> </thead> <tbody> <tr th:each="pm : ${productsMasterForm.pmList}" th:object="${pm}"> <td th:text="*{ProductsCode}"></td> <td th:text="*{ProductsName}"></td> <td th:text="*{UnitPrice}"></td> </tr> </tbody> </table> </body> </html>コンパイル/パッケージ
次のコマンドでコンパイル及びwarファイルの作成が出来ます。
mvn package動作確認
コマンドパレット
Tomcat: Add Tomcat ServerTomcatフォルダ選択ダイアログが表示されますので、Tomcatフォルダ(D:\JAVA\Tomcat\apache-tomcat-9.0.31)を選択します。
コマンドパレット
Tomcat: Run on Tomcat Serverwarファイル選択ダイアログが表示されますので、「mvn package」で作成されたwarファイル(D:\JAVA\Project\SpringSample01\target\sqlSample1-1.0-SNAPSHOT.war)を選択します。
「http://localhost:8080/」にアクセスして下さい。
sqlSample-1.0-SNAPSHOTをクリックして下さい。
コード修正→動作確認の際、Tomactに配置したwarファイルを都度削除しないと上手く反映されませんでした。
hot deployが出来れば良いのですか、見つけられませんでした。今回のサンプルソース
GitHubにアップしました。
https://github.com/t-skri1/SpringSample02まとめ
簡易的ですが、Webアプリになりました。
次回はSpring Securityを組み込みます。
- 投稿日:2020-03-16T16:29:53+09:00
Tomcatにおけるシングルテナント対応非同期処理の実装
Tomcatにおけるシングルテナント対応非同期処理の実装
この記事は、「Tomcatにおける非同期処理の実装」の一部分です。
- 「Tomcatにおける非同期処理の実装」
- 「Tomcatにおけるシングルテナント対応非同期処理の実装」←この記事
- 「Tomcatにおけるマルチテナント対応非同期処理の実装」
はじめに
ここでは、シングルテナントにおいて、スレッドプールを利用した非同期タスクの流動制限を行うTomcatアプリケーションを実装していきます。
動作確認ソフトウェア
次のソフトウェアおよびライブラリの環境で動作を確認しています。
その他の依存ライブラリについては、後述のpom.xmlを参考にしてご確認してください。
- Windows 10
- Java SE 8
- Maven 3.6.2
- Eclipse IDE for Enterprise Java Developer 2019-06 (4.12.0)
- Tomcat 8.5.43
- Spring Boot 1.5.22
- Spring Framework 4.3.25
pom.xmlの作成
EclipseかMavenを使ってMavenプロジェクトを作成し、pom.xmlに下図の内容を追加します。
なお、dependencyのspring-boot-starter-actuatorはTomcatモニタリング用のライブラリですが、運用環境ではセキュリティリスクとなるので外します。<project xmlns=...> : <build> : <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.1.4.RELEASE</version> </plugin> </plugins> : </build> <dependencies> : <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.22.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>1.5.22.RELEASE</version> </dependency> : </dependencies> </project>図3.2 pom.xmlへの追加内容
設定ファイルの作成
Spring Bootの設定ファイルである「application.properties」を作成します。
# Tomcatモニタリング # Tomcatコンテナの監視やシャットダウンするなどの運用監視向けの設定 # 運用環境ではセキュリティリスクとなるので外す endpoints.shutdown.enabled=true management.security.enabled=false management.endpoints.web.exposure.include=** # スレッドプール # 非同期タスクの受理数と同時実行数を指定 # queueCapacityの数だけタスクをキューイングし、maxPoolSizeの数だけバックグランドで動作 # 最大「maxPoolSize + queueCapacity」の数だけ非同期タスクを受け入れ、その数を超えたらRejectされる(受け付けない) # corePoolSize = 初期のThread数 # queueCapacity = corePoolSizeが一杯になったときにキューイングする数 # maxPoolSize = queueCapacityを越えたときの最大Thread数 threadPool.corePoolSize=1 threadPool.queueCapacity=2 threadPool.maxPoolSize=2 threadPool.keepAliveSeconds=1 # Loggingのログ出力レベルを指定 # Loggingの出力レベルを指定 # https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/Level.html logging.level.root=INFO logging.level.org.springframework.web=INFO logging.level.web01=INFO図3.3 application.propertiesの内容
スレッドプール設定のPOJOクラスの作成
スレッドプール設定を保持するPOJOクラスを作成します。
Spring Frameworkによりapplication.propertiesの"threadPool"で始まる設定値が、自動的にこのクラスに注入されます。@Component @ConfigurationProperties(prefix = "threadPool") public class ThreadPoolSettings { private String corePoolSize; private String queueCapacity; private String maxPoolSize; private String keepAliveSeconds; public void setCorePoolSize(String corePoolSize) {this.corePoolSize = corePoolSize;} public void setQueueCapacity(String queueCapacity) {this.queueCapacity = queueCapacity;} public void setMaxPoolSize(String maxPoolSize) {this.maxPoolSize = maxPoolSize;} public void setKeepAliveSeconds(String keepAliveSeconds) {this.keepAliveSeconds = keepAliveSeconds;} public int getCorePoolSize() {return Integer.valueOf(corePoolSize);} public int getQueueCapacity() {return Integer.valueOf(queueCapacity);} public int getMaxPoolSize() {return Integer.valueOf(maxPoolSize);} public int getKeepAliveSeconds() {return Integer.valueOf(keepAliveSeconds);} }図3.4 ThreadPoolSettingsクラスの内容
Tomcat起動クラスの作成
組み込みのTomcatコンテナを起動するApplicationクラスを作成します。
@SpringBootApplicationは、Spring Frameworkに自動的に@Configurationや@Componentなどのクラスを走査するよう指示するものです。@Repository、@Service、@Controllerも@Componentの一つなので、これらも検出されます。ただし、デフォルトでは走査対象は、同一パッケージかその配下のパッケージに限るので、注意が必要です。
@SpringBootApplication public class Application { public static void main(final String[] args) { SpringApplication.run(Application.class, args); } }図3.5 Applicationクラスの内容
@Configurationクラスの作成
Tomcat起動時にアプリケーションの初期設定を行うSampleConfigクラスを作成します。
このクラスには、初期設定を行うクラスであることを宣言する@Configurationと、非同期呼び出しを有効化するための@EnableAsyncがあります。taskExecutor1()にある@Beanは、Spring FrameworkにBeanを登録するものです。デフォルトでは、Bean名はメソッド名と同じで、ここでは「taskExecutor」がBean名となります。
taskExecutor1()では、キューを持つスレッドプールであるThreadPoolTaskExecutorを生成します。運用環境のコア数とメモリ容量に応じて、前述のapplication.propertiesの設定値を変えることにより、キューとスレッドプールの容量をチューニングできるというわけです。
また、Tomcat終了時に、動作中のスレッドを終了させないよう、スレッドプールが保持するスレッドは「非デーモン」にします。ThreadPoolTaskExecutorはデフォルトで非デーモンですが、ここでは明示的に指定しています。ただし、スレッドプールのシャットダウンでは、キューに滞留しているタスクはすべて破棄されてしまいます。Tomcatアプリケーションを再起動したときに、キューに滞留していたタスクを実行させたい場合は、追加の実装が必要ですが、ここでは割愛します。
@Configuration @EnableAsync public class SampleConfig { //application.propertiesの"threadPool"で始まる設定値を保持する変数 @Autowired ThreadPoolSettings threadPoolSettings; //@Async向けのスレッドプールをSpring Beanとして登録する //Bean名はメソッド名と同じ「taskExecutor1」となる @Bean public ThreadPoolTaskExecutor taskExecutor1() { final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(threadPoolSettings.getCorePoolSize()); executor.setQueueCapacity(threadPoolSettings.getQueueCapacity()); executor.setMaxPoolSize(threadPoolSettings.getMaxPoolSize()); executor.setKeepAliveSeconds(threadPoolSettings.getKeepAliveSeconds()); executor.setThreadNamePrefix("Executor1-"); executor.setDaemon(false); executor.initialize(); return executor; } }図3.6 SampleConfigクラスの内容
@Serviceクラスの作成
下図のSampleServiceクラスを作成します。
このクラスの@Serviceは、DDD(Domain-Driven Design)の構成要素の一つであり、ビジネスロジックを処理する債務を担います。
cmd()では、引数で指定されたコマンドを起動し、コマンドの実行状況をノンブロッキングで確認できるFutureオブジェクトを返します。非同期呼び出しの対象であることを宣言するために@Asyncを付加してあり、"taskExcecutor1"パラメータを付けてSampleConfigクラスで定義したスレッドプールへ紐付けています。
@Service public class SampleService { @Async("taskExecutor1") public CompletableFuture<String> cmd(final String[] command) { final int exitValue = run(command); return CompletableFuture.completedFuture("exitValue="+exitValue); } //外部コマンドを実行 public static int run(final String[] command) { try{ final Process p = new ProcessBuilder(command).redirectErrorStream(true).start(); try(final BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))){ String str; while((str = r.readLine()) != null) { System.out.println("Command output: "+str); } } return p.waitFor(); }catch(IOException | InterruptedException e) { e.printStackTrace(); return -1; } } }図3.7 SampleServiceクラスの内容
@Controllerクラスの作成
下図のSampleControllerクラスを作成します。
@Controllerは、MVCモデルのControllerに該当し、クライアントからの要求に応対する債務を担います。
このクラスには、@Autowiredを付加したフィールドがありますが、@Componentや@Beanなどで定義された部品を自動的に注入するようSpring Frameworkへ指示するするものです。
cmd()は「GET http://.../cmd」に応対して、SampleServiceのcmd()を呼び出しています。同期呼び出しのように見えますが、呼び出し先には@Asyncを付加してあるためSpring Frameworkが自動的に非同期呼び出しに変えてくれるわけです。
スレッドプールのキューやスレッドが満杯になり非同期処理を受理できない場合は、RejectedExecutionExceptionがスローされます。これにより非同期処理の流量制御を実現できるわけです。ここでは簡単のため、「Rejected」の文字列をクライアントに返していますが、本来はHTTPの「429 Too Many Requests」といったステータスコードを返すのが筋でしょう。
status()は「GET http://.../status」に応対して、SampleServiceのcmd()が返してきたFutureオブジェクトを走査し、非同期呼び出しの実行状況を文字列で返します。
@Controller public class SampleController { private static final String[] COMMAND = new String[] {"cmd", "/c", "ping", "-n", "10", "localhost", ">", "nul"}; private AtomicInteger counter = new AtomicInteger(1); private HashMap<String, Future<String>> futures = new HashMap<>(); @Autowired private SampleService sampleService; @RequestMapping(value="/cmd", method=RequestMethod.GET) @ResponseBody public String cmd() { final String id = String.format("<noTenantId>#%03d", counter.getAndIncrement()); try{ final CompletableFuture<String> future = sampleService.cmd(COMMAND) .exceptionally(ex -> id+": Exception: "+ex.getMessage()+"\n"); synchronized(this.futures) { this.futures.put(id, future); } return "Accepted: id="+id+"\n"; }catch(RejectedExecutionException e) { final CompletableFuture<String> future = new CompletableFuture<>(); future.complete("Rejected"); synchronized(this.futures) { this.futures.put(id, future); } return "Rejected: id="+id+"\n"; } } @RequestMapping(value="/status", method=RequestMethod.GET) @ResponseBody public String status() { final Map<String, Future<String>> map; synchronized(this.futures) { map = (Map<String, Future<String>>)this.futures.clone(); } return map.entrySet().stream() .map(entry->SampleController.futureGet(entry.getKey(), entry.getValue())) .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString(); } //非同期タスクの状況を文字列に変換 private static String futureGet(String tenantId, Future<String> future) { if(future.isDone()==false && future.isCancelled()==false) { return tenantId+": Running\n"; }else if(future.isCancelled()) { return tenantId+": Canceled\n"; } try { return tenantId+": "+future.get()+"\n"; } catch (InterruptedException | ExecutionException | CancellationException e) { return tenantId+": Exception\n"; } } }図3.8 SampleControllerクラスの内容
動作確認
ローカルでTomcatコンテナを起動し、次のようにコマンドを実行して動作確認できます。
$ curl -X GET http://localhost:8080/cmd Accepted: id=<noTenantId>#001 # 10秒強待ってから/statusをGETする $ curl -X GET http://localhost:8080/status <noTenantId>#001: exitValue=0 # Tomcatコンテナを通常終了させる $ curl -X POST http://localhost:8080/shutdown図3.9 非同期タスクの動作確認
「Tomcatにおけるマルチテナント対応非同期処理の実装」へ続く...
- 投稿日:2020-03-16T16:24:11+09:00
Tomcatにおけるマルチテナント対応非同期処理の実装
Tomcatにおけるマルチテナント対応非同期処理の実装
この記事は、「Tomcatにおける非同期処理の実装」の一部分です。
- 「Tomcatにおける非同期処理の実装」
- 「Tomcatにおけるシングルテナント対応非同期処理の実装」
- 「Tomcatにおけるマルチテナント対応非同期処理の実装」←この記事
はじめに
「Tomcatにおける非同期処理の実装」では、@Asyncとスレッドプールの組み合わせで、非同期タスクの流動制御ができることを紹介しました。しかし、@Asyncとスレッドプールは一対一の対応であるため、マルチテナントには対応できないわけです。
マルチテナントとは、1つのアプリケーションのインスタンスで、複数のお客様向けにサービスを提供する仕組みです。たとえば、次のようにテナントIDを指定した場合に、テナント「t1」と「t2」を異なるスレッドプールに割り当て、データストアの異なるスキーマへアクセスするというふうに、テナントごとに異なる資源・資産を利用するようなイメージです。
- 「GET http://localhost/async?tenantId=t1」 - 「GET http://localhost/async?tenantId=t2」図4.0 マルチテナント対応のHTTPリクエスト
そこで、マルチテナント対応の非同期処理を実装する方法を考えてみます。また、Spring Frameworkの管理から外れる部分があり、スレッドプールのライフサイクル管理を自前で実装する必要があることも紹介します。
スレッドプール管理クラスを追加
マルチテナント対応のスレッドプールを管理するThreadPoolTaskExecutorsクラスを作成します。
このクラスでは、テナントIDとスレッドプールをkey-valueとしたMap型をフィールドで保持し、getExecutor()ではテナントIDに対応したスレッドプールを返しています。ところが、このクラスをBeanとして登録しても、Spring Frameworkはスレッドプールを内包していることを認識するわけではありません。
そこで、自前でスレッドプールのシャットダウンを行うために、@PreDestroyを付加したdestroy()を追加しています。Tomcat通常終了時に@PreDestroyのメソッドがコールバックされるため、そのタイミングでスレッドプールをシャットダウンさせるわけです。ただし、@Configurationクラスの作成で述べたように、このシャットダウンでもキューに滞留しているタスクが破棄される点に注意してください。
public class ThreadPoolTaskExecutors { private Map<String, ThreadPoolTaskExecutor> map = new HashMap<>(); public ThreadPoolTaskExecutors(ThreadPoolSettings settings) { //テナントごとにスレッドプールを生成 map.put("Tenant1", newExecutor("Tenant1", settings)); map.put("Tenant2", newExecutor("Tenant2", settings)); map.put("Tenant3", newExecutor("Tenant3", settings)); } //スレッドプールを生成 private static ThreadPoolTaskExecutor newExecutor(String tenantId, ThreadPoolSettings settings) { final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(settings.getCorePoolSize()); executor.setQueueCapacity(settings.getQueueCapacity()); executor.setMaxPoolSize(settings.getMaxPoolSize()); executor.setKeepAliveSeconds(settings.getKeepAliveSeconds()); executor.setThreadNamePrefix("Executor2-"+tenantId+"-"); executor.setDaemon(false); executor.initialize(); return executor; } //テナントIDに対応したスレッドプールを返す public ThreadPoolTaskExecutor getExecutor(String tenantId) { return map.get(tenantId); } //スレッドプールをシャットダウンさせる @PreDestroy private void destroy() { this.map.values().forEach(ThreadPoolTaskExecutor::shutdown); } }図4.1 ThreadPoolTaskExecutorsクラスの内容
@Configurationクラスの改修
前述のSampleConfigクラスに、下図のようにtaskExecutors2()を追加します。
このメソッドでは@Beanを利用して、マルチテナント用スレッドプール管理オブジェクトをBeanとして登録します。public class SampleConfig { : @Bean public ThreadPoolTaskExecutors taskExecutors2() { return new ThreadPoolTaskExecutors(threadPoolSettings); }図4.2 SampleConfig#taskExecutors2()の内容
@Serviceクラスの改修
前述のSampleServiceクラスに、下図のようにcmd2()を追加します。
cmd()と異なり、@Asyncは付けず、復帰値はFutureではなくStringのオブジェクトです。public class SampleService { : public String cmd2(final String[] command) { final int exitValue = run(command); return "exitValue="+exitValue; } : }図4.3 SampleService#cmd2()の内容
@Controllerクラスの改修
前述のSampleControllerクラスを改修します。
まず、taskExecutors2フィールドを追加します。@AutowiredがあるのでSampleConfigクラスで登録したBeanが自動的に注入されます。public class SampleController { : @Autowired private ThreadPoolTaskExecutors taskExecutors2; : }図4.4.1 SampleService#taskExecutors2の内容
次に、SampleServiceのcmd2()を呼び出すメソッドを追加します。
このメソッドの処理の流れはcmd()メソッドと同じですが、スレッドプールを取得して明に非同期呼び出しを行っている点で異なります。public class SampleController { : @RequestMapping(value="/cmd2", method=RequestMethod.GET) @ResponseBody public String cmd2(@RequestParam(value="id", required=true)final String id) { final String tenantId = String.format("%s#%03d", id, counter.getAndIncrement()); //ThreadPoolを取得 final ThreadPoolTaskExecutor executor = taskExecutors2.getExecutor(id); if(executor == null) { return "Invalid id: id="+id+"\n"; } try{ final Future<String> future = executor.submit(()->{ return sampleService.cmd2(COMMAND); //Serviceを呼び出す }); synchronized(this.futures) { this.futures.put(tenantId, future); } return "Accepted: id="+tenantId+"\n"; }catch(RejectedExecutionException e) { final CompletableFuture<String> future = new CompletableFuture<>(); future.complete("Rejected"); synchronized(this.futures) { this.futures.put(tenantId, future); } return "Rejected: id="+tenantId+"\n"; } }図4.4.2 SampleService#cmd2()の内容
動作確認
ローカルでTomcatコンテナを起動し、次のようにコマンドを実行して動作確認できます。
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1 Accepted: id=Tenant1#001 $ curl -X GET http://localhost:8080/cmd2?id=Tenant1 Accepted: id=Tenant1#001 $ curl -X GET http://localhost:8080/cmd2?id=Tenant1 Accepted: id=Tenant1#001 $ curl -X GET http://localhost:8080/cmd2?id=Tenant1 $ curl -X GET http://localhost:8080/cmd2?id=Tenant1 Rejected: id=Tenant1#001 $ curl -X GET http://localhost:8080/cmd2?id=Tenant1 Rejected: id=Tenant1#001 # スレッドプールが「maxPoolSize=2」、「queueCapacity=2」のとき # 10秒強待ってから/statusをGETすると # 実行が終了したタスクが2個、実行中のタスクが2個、Rejectされたタスクが2個 # であることが出力される $ curl -X GET http://localhost:8080/status Tenant1#001: exitValue=0 Tenant1#002: Running Tenant1#003: Running Tenant1#004: exitValue=0 Tenant1#005: Rejected Tenant1#006: Rejected # Tomcatコンテナを通常終了させる $ curl -X POST http://localhost:8080/shutdown図4.5 マルチテナント対応非同期タスクの動作確認
- 投稿日:2020-03-16T15:32:04+09:00
Tomcatにおける非同期処理の実装
Tomcatにおける非同期処理
はじめに
Webアプリケーションでは、クライアントからのHTTP要求に対して長時間を要する処理を行うとHTTP応答がタイムアウトになり、HTTP要求を正常に受け付けられたかどうかを確認しづらくなります。また、長時間を要する処理をバックグランドで行う場合でも、非同期タスクの同時実行数を制限しなければ、スレッド数がどんどん増え運用環境の資源が枯渇すると、動作不能に陥るおそれがあります。
Tomcatコンテナ上で動作するTomcatアプリケーションで、下図のように長時間を要する処理を非同期で呼び出す方法を考えます。
図1 非同期処理の流れ
図1では、Tomcatコンテナの中にController、Service、Entityの3つがあります。クライアントからHTTP要求が来たときに、Tomcatコンテナを介してControllerからServiceを非同期で呼び出しています。非同期呼び出し後に、すぐクライアントへHTTP応答を返せるため、HTTP要求を受け付けたのかそれとも拒否したのかをクライアントへ通知できます。非同期タスクの実行状況をEntityへ反映しておけば、後から別のHTTP要求を出してその実行状況を確認できます。
非同期呼び出しの実装にあたっては、冒頭で述べたとおり非同期タスクの流動制御が必要ですが、ここで知っておきたいのは、アプリケーションが「適当に」スレッドを生成するのは推奨されていない点です。なぜなら、アプリケーションが生成したスレッドをTomcatコンテナやJava EEコンテナが認識しないため、スレッドのライフサイクルを適切に管理できなくなるためです。そこで、スレッドの個数やライフサイクルなどの管理を行う仕様である「Concurrency Utilities for Java EE(JSR 236)」が提唱され、Java EE 7に導入されました。
TomcatコンテナはJava EEコンテナのサブセットのようなものであり、JSR 236をサポートしているわけではありませんが、Spring Frameworkを導入すればスレッドのライフサイクル管理ができるようになります。ここでは、Spring Frameworkを活用した「シングルテナント対応」および「マルチテナント対応」の非同時処理の流動制御の実装方法を考えていきます。
記事構成
この記事は3つの記事で構成しています。
- 「Tomcatにおける非同期処理の実装」←この記事
サンプルソース
この記事で作成したソースコードは、GitHubにあります。
まとめ
- シングルテナントの場合
- 非同期メソッドとリクエストが1対1の対応なら、Spring Frameworkの機能で対応可能
- マルチテナントの場合
- 非同期メソッドとリクエストが1対多の対応なら、自前でスレッドプールとそのライフサイクルを用意する必要あり
- 投稿日:2020-03-16T15:16:34+09:00
【Java】初めてのJava体験(基礎・コード)
最初に
自身で学習してきた内容を書き出していきます。
基本的には自分自身でわかるような内容になりますので、ご容赦ください。
また、誤っている点がありましたら、コメントにてご指摘ください。(要確認)マークがあるものは、「実行前に必ずググる」べき内容。
⚠️今回はアウトプットを含む、インプット寄りの投稿です⚠️
今回は、外部サイト「Progate」にて学習した内容を吐き出します。
index
- Javaってなんぞ
- 試しにコードを書いてみた
- データ型
- 変数
Javaってなんぞ
世界中にたくさんの開発者がいる有名な言語のなかの一つ!
大規模システム、Webアプリケーション、スマートフォンアプリなど、Javaはあなたの周りの様々な場所で使われている。そして・・・
⚠️JavaとJavaScriptは全くの無関係である⚠️
試しにコードを書いてみた
文字列
やりたいこと:「文字列」を、コンソールに出力させたい。
Main.javaclass Main { // ここにクラスを記述 public static void main(String[] args) { // ここにメゾットを記述 System.out.println("Hello,World!"); // ここに処理を記述 } }
- 文字列は「"(ダブルクォーテーション)」で囲んであげる。
- 「;(セミコロン)」は忘れがちになる!
- System.out.println()というのは、「()の中身を出力(表示)せよ」という「命令」
- print「l」nは小文字のエルだぞ
- クラス>メゾット>処理の構成で記述するようだ
文字列の連結
やりたいこと:「文字列」を、文字列の連結させてコンソールに出力させたい。
Main.javaclass Main { public static void main(String[] args) { System.out.println("Hello" + "World!"); System.out.println("5" + "3"); //結果:53 } }
- 後述の「数値」と「文字列」の違いをイメージすると良い!
数値(四則演算処理)
やりたいこと:「数値」を、コンソールに出力させたい。
Main.javaclass Main { // ここにクラスを記述 public static void main(String[] args) { // ここにメゾットを記述 System.out.println(3); // ここに処理を記述 System.out.println(1 + 2); System.out.println(5 - 2); } }
- 数値はそのままでOK。
- 四則演算もできちゃう!
- 今回の記述だと、全て「3」だが処理内容が違うぞ。
やりたいこと:いろんな計算結果を出力させたい。
Main.javaclass Main { // ここにクラスを記述 public static void main(String[] args) { // ここにメゾットを記述 System.out.println(5 * 2); // 結果:10 System.out.println(6 / 2); // 結果:3 System.out.println(8 % 5); // 結果:3 } }
- 「*」は掛け算、「/」は割り算
- 「%」は割り算の余りを計算できるぞ!(今回の場合は「1余り3」なので出力は「3」となる)
- 四則演算処理を見やすくする為に、半角スペースを入れると良き♪
文字列と数値の違いがわかる例
Main.javaSystem.out.println(3); // 結果:3 System.out.println("1 + 2"); // 結果:1 + 2データ型
String型とint(integer)型
Ruby on Railsで開発をしていた時にも、テーブル設計時にお世話になりました、String型とint(integer)型についてです。
String型 int型 "Hello,World"(文字列) 3(整数) "イロハ" 1995
- String型
- 文字の並び
- int(integer)型
- 整数
- (iは小文字)
後日、様々なデータ型をまとめた記事を投稿予定です。
変数
データのやりとりをやりやすくするための、定義できる書類BOXや名札みたいな感じ。
投稿者の過去記事に、rubyでの簡単な説明があるので、よければ読んでみてね!
Rubyについて(基礎)Javaにおいての変数
まずは定義付けが必要!
Javaで変数を定義するには下記の記述が必須!
①変数にいれる値のデータ型を指定
②変数の名前を指定
③値を代入するここでのポイントは「=」は「等しい」を意味するのではないこと!
2020/3/16 15:00 執筆中・・・
2020/3/18 ??:?? 記事完成予定Main.java
- 投稿日:2020-03-16T15:16:34+09:00
【Java】初めてのJava学習:その1 Javaってなんぞ?から変数の基礎まで(基礎・コード)
最初に
自身で学習してきた内容を書き出していきます。
基本的には自分自身でわかるような内容になりますので、ご容赦ください。
また、誤っている点がありましたら、コメントにてご指摘ください。(要確認)マークがあるものは、「実行前に必ずググる」べき内容。
⚠️今回はインプット寄りの投稿です⚠️
今回は、外部サイト「Progate」にて学習した内容を吐き出します。
index
- Javaってなんぞ
- 試しにコードを書いてみた
- データ型
- Javaでの変数
Javaってなんぞ
世界中にたくさんの開発者がいる有名な言語のなかの一つ!
大規模システム、Webアプリケーション、スマートフォンアプリなど、Javaはあなたの周りの様々な場所で使われている。そして・・・
⚠️JavaとJavaScriptは全くの無関係である⚠️試しにコードを書いてみた
やりたいこと:「文字列」を、コンソールに出力させたい。
Main.javaclass Main { // ここにクラスを記述 public static void main(String[] args) { // ここにメゾットを記述 System.out.println("Hello,World!"); // ここに処理を記述 } }
- 文字列は「"(ダブルクォーテーション)」で囲んであげる。
- 「;(セミコロン)」は忘れがちになる!
- System.out.println()というのは、「()の中身を出力(表示)せよ」という「命令」
- print「l」nは小文字のエルだぞ
- クラス>メゾット>処理の構成で記述するようだ
文字列の連結
やりたいこと:「文字列」を、文字列の連結させてコンソールに出力させたい。
Main.javaclass Main { public static void main(String[] args) { System.out.println("Hello" + "World!"); System.out.println("5" + "3"); //結果:53 } }
- 後述の「数値」と「文字列」の違いをイメージすると良い!
数値(四則演算処理)
やりたいこと:「数値」を、コンソールに出力させたい。
Main.javaclass Main { // ここにクラスを記述 public static void main(String[] args) { // ここにメゾットを記述 System.out.println(3); // ここに処理を記述 System.out.println(1 + 2); System.out.println(5 - 2); } }
- 数値はそのままでOK。
- 四則演算もできちゃう!
- 今回の記述だと、全て「3」だが処理内容が違うぞ。
やりたいこと:いろんな計算結果を出力させたい。
Main.javaclass Main { // ここにクラスを記述 public static void main(String[] args) { // ここにメゾットを記述 System.out.println(5 * 2); // 結果:10 System.out.println(6 / 2); // 結果:3 System.out.println(8 % 5); // 結果:3 } }
- 「*」は掛け算、「/」は割り算
- 「%」は割り算の余りを計算できるぞ!(今回の場合は「1余り3」なので出力は「3」となる)
- 四則演算処理を見やすくする為に、半角スペースを入れると良き♪
文字列と数値の違いがわかる例
System.out.println(3); // 結果:3 System.out.println("1 + 2"); // 結果:1 + 2データ型
String型とint(integer)型
Ruby on Railsで開発をしていた時にも、テーブル設計時にお世話になりました、String型とint(integer)型についてです。
String型 int型 "Hello,World"(文字列) 3(整数) "イロハ" 1995
- String型
- 文字の並び
- int(integer)型
- 整数
- (iは小文字)
後日、様々なデータ型をまとめた記事を投稿予定です。
Javaでの変数
データのやりとりをやりやすくするための、定義できる書類BOXや名札みたいな感じ。
投稿者の過去記事に、rubyでの簡単な説明があるので、よければ読んでみてね!
Rubyについて(基礎)やりたいこと:Javaで変数を定義させ、定義した変数から値を取り出したい
①変数にいれる値のデータ型を指定
②変数の名前を指定
③値を代入する「=」は「等しい」を意味するのではない!
あとは変数から値を取り出せばOK!!
Main.javaclass Main { public static void main(String[] args) { int a; // 整数として「a」の名札がついた箱を用意 a = 10; // その箱に「10」を代入 System.out.println(a); // 結果:10 String b; // 整数として「b」の名札がついた箱を用意 b = "hello,world!" // その箱に「"hello,world!"」を代入 System.out.println(b); // 結果:"hello,world!" } }やりたいこと:変数の初期化をしてみる
変数の定義と代入を一行で表現できる!
上記のコードにある変数を初期化してみる。Main.javaclass Main { public static void main(String[] args) { int a = 10; // 変数の初期化 System.out.println(a); // 結果:10 String b = "hello,world!" // 変数の初期化 System.out.println(b); // 結果:"hello,world!" } }✨見ていて気持ちいいコードになった✨
やりたいこと:「int型の計算」と「String型の連結」
Main.javaclass Main { public static void main(String[] args) { // 変数を用いたint型の計算 int hoge1 = 100; System.out.println(hoge1 + 200); // 結果:300 int hoge2 = 10; System.out.println(hoge1 + hoge2); // 結果:110 // 変数を用いたString型の連結 String hello = "こんにちは!"; System.out.println(hello + "hoge1"); // 結果:こんにちは!hoge1 String name = "hoge2"; System.out.println(hello + name); // 結果:こんにちは!hoge2 } }変数の上書きと注意点
中に入っている値を更新すること(上書き)も可能。
ただし、注意として、「同じ処理内で同一名の変数を定義できない」。やりたいこと:変数の上書きとエラーの出る記述
String型の変数を呼び出す時も、変数名に「"」は記述しない。
Main.javaclass Main { public static void main(String[] args) { // 変数を用いたint型の計算 int hoge1 = 100; System.out.println(hoge1 + 200); // 結果:300 hoge1 = 400; // 上書き System.out.println(hoge1 + 200); // 結果:600 int hoge1 = 700; //結果を900にしたいが「int」を記述 System.out.println(hoge1 + 200); // 結果:同じ処理内で同一名の変数を定義しているのでエラーが出る } }ここで地味なポイント
コードは基本的に上から処理されていく。Main.javaclass Main { public static void main(String[] args) { int number = 10; String text = "Ruby"; System.out.println(number); // ここで一度「10」が出力される System.out.println(text); // ここで一度「Ruby」が出力される number = 5; text = "Java"; System.out.println(number); // 上書きされた「5」が出力される System.out.println(text); // 上書きされた「Java」が出力される } }結果
10 Ruby 5 Java
自己代入
やりたいこと:変数xに10が入っている時、40を足して、変数xを上書きしたい
代入の「=」は等しいわけではないことがPOINT!
Main.javaclass Main { public static void main(String[] args) { int number = 10; System.out.println(number); // 結果:10 number = number + 40; // number = 10に40を足して、numberを上書き System.out.println(number); // 結果:50 } }上記の記述を省略してみる。
Main.javaclass Main { public static void main(String[] args) { int number = 10; System.out.println(number); // 結果:10 number += 40; // 省略記述 System.out.println(number); // 結果:50 } }やりたいこと:自己代入の省略記述
int number = 10; number += 40; number -= 40; number *= 40; number /= 40; number %= 40; //変数に「1」を足す、もしくは「1」を引く場合はさらに省略できる number++; number--;変数の命名規則(Javaの場合)
変数は自由に命名することができる・・・が!!!
一定のルールと理由がある。GOOD?
例 理由 date 英単語を用いている userName 2語以上の場合は大文字で区切る(キャメルケース) BAD?
例 理由 1name 数字開始はエラーが出るぞ user_name アンダーバーは望ましくない(スネークケース) ohisama ローマ字もわかりにくいので良くない 名前 日本語はよろしくない(プログラミングでは日本語良くないぞ...) 誰が見てもわかりやすく、短い変数名を定義してあげよう!!
命名に悩んだら下記参照。小数点について
やりたいこと:小数点を用いて計算をしてみたい
小数点のデータ型は「double型」。
Main.javaclass Main { public static void main(String[] args) { double number1 = 10.0; double number2 = 3.3; System.out.println(number1 + number2); // 結果:13.3 System.out.println(number1 - number2); // 結果:6.7 } }やりたいこと:自動型変換を用いて整数と小数点で計算したい
Main.javaclass Main { public static void main(String[] args) { System.out.println(5 / 2.0); // 結果:2.5 //因みに System.out.println(5 / 2); // 結果:2 System.out.println(5.0 / 2.0); // 結果:2.5 } }
- 「+」などの操作は、本来であれば同じデータ型同士でないとできない。
- 「自動型変換」で対応してみる。(手動もあるようだ)
- 因みに「String型」と「int型」で「+」すると、String型で出力される。
- このことから、値をわかりやすく表現できるデータ型に自動変換してくれていることがわかる!!
- 整数の「5」は、文字列でも"5"の表現ができるし、小数点でも5.0の表現ができるから。
- また、int型の計算結果とdouble型の計算結果で変化することがあるので注意した方が良いぞ。
やりたいこと:強制型変換を用いて整数と小数点で計算したい
Main.javaclass Main { public static void main(String[] args) { int x = 13; int y = 4; System.out.println(x / y); // 結果:3(3.25ではない) System.out.println((double)x / y); // 結果:3.25 } }
- 変数xのint型を「double型」に強制変換させたので、自動型変換が発火して、変数yも「double型」になったぞ!!
追加あれば、どんどん更新していきます。
駆け出しとして頑張ります。
- 投稿日:2020-03-16T09:48:04+09:00
JdbcTemplateで@OneToMany的な振る舞いをさせる
TL;DR
ORMにを頼らずNamedParameterJdbcTemplateとRowMapperを組み合わせて結果を取得するシステムでOneToManyなデータの持ち方を実現したい場合はResultSetExtractorを実装したクラスを作ればいいが、構造が複雑になりがちなので新しいシステムを作る時は素直にJPA等に頼ったほうが良い
問題点
Spring JDBCには
NamedParameterJdbcTemplate
等のクラスをDIすることで気軽にクエリを発行できる仕組みがある。@Repository public class HogeRepository { @Resource private NamedParameterJdbcTemplate namedParameterJdbcTemplate; @Override public HogeWithPiyo findAll() { return namedParameterJdbcTemplate.query("SELECT * FROM hoge LEFT OUTER JOIN piyo USING(hoge_id)", new BeanPropertyRowMapper<>(HogeWithPiyo.class)); } }public class HogeWithPiyo { Integer hogeId; List<Piyo> piyoList; public Integer getHogeId() { return hogeId; } public void setHogeId(Integer hogeId) { this.hogeId = hogeId; } public List<Piyo> getPiyoList() { return piyoList; } public void setPiyoList(List<Piyo> piyoList) { this.piyoList = piyoList; } public class Piyo { Integer piyoId; public Integer getPiyoId() { return piyoId; } public void setPiyoId(Integer piyoId) { this.piyoId = piyoId; } }問題
例えば、
hoge
,piyo
テーブルに対して結果セットが次のように帰ってくるとする。
hoge.hoge_id piyo.hoge_id piyo.piyo_id 1 1 1 1 1 2 この時、BeanPropertyRowMapper等のRowMapper系でデータを取得するしかないシステムで
Hogeリストとそれに紐づくPiyoリスト
を取得するために、ビジネスロジックでPiyoを取得するためにクエリを発行しているがそもそものHogeの件数自体がめちゃくちゃ多いので件数分クエリを発行するとスループットがめちゃくちゃ遅い、という問題があったと仮定する。BeanPropertyRowMapperを使うとどうしても1行ずつ読んでしまうため、結果は2行のリストになってしまう。public class HogeExtractor<T extends Hoge> implements ResultSetExtractor<List<T>> { BeanPropertyRowMapper<T> parentRowMapper; BeanPropertyRowMapper<Piyo> childRowMapper = new BeanPropertyRowMapper<>(Piyo.class); String uniqueColumn = "hoge_id"; @SafeVarargs public HogeExtractor(T... e) { @SuppressWarnings("unchecked") Class<T> type = (Class<T>) e.getClass().getComponentType(); this.parentRowMapper = new BeanPropertyRowMapper<>(type); } @Override public List<T> extractData(ResultSet rs) throws SQLException, DataAccessException { List<T> rows = new ArrayList<>(); // 結果のリスト List<Object> childs = null; Long key = null; T current = null; int parentIdx = 0; int childIdx = 0; while (rs.next()) { if (current == null || !key.equals(rs.getLong(uniqueColumn))) { // 親テーブルのデータが無い時やJOINするカラムの値が変わったら親テーブルのオブジェクトを作る key = rs.getLong(uniqueColumn); current = parentRowMapper.mapRow(rs, parentIdx++); childs = new ArrayList<>(); current.setPiyo(childs); childIdx = 0; rows.add(current); } // 毎行、小テーブルの要素を親テーブルのオブジェクトに追加する childs.add(childRowMapper.mapRow(rs, itemIdx++)); } return rows; } }@Override public HogeWithPiyo findAll() { return namedParameterJdbcTemplate.query( "SELECT * FROM hoge LEFT OUTER JOIN piyo USING(hoge_id)", new HogeExtractor<>()); }そんな時はこんな感じでHoge専用のResultSetExtractorを実装するクラスを無理やり作っちゃう。
問題点
- 取得したいオブジェクトごとにResultSetExtractorを実装しなきゃいけないのでしんどい
- 継承したクラスならジェネリクス使えばなんとかなる
- リストの要素を追加するメソッド名をリフレクションで解決すればなんとなく1つのクラスで済む気がするけどしんどい
- OneToManyしたいフィールドが2箇所以上ある時とかもしんどい
- JOINするキーが2個以上ある時もしんどい
結論
- 既存の仕組みを守りつつ最小のコストでスループットを上げるためのテクニックとしてならアリなんじゃないかな……と思っています
- ちゃんとテストが書ける前提で負債を最小限にコントロールできるなら良いと思うけど、どちらかというと負債を作る行為
- 実運用した事は無いのでベンチマークはありません
参考にしました
java - ParameterizedRowMapper That Maps Object List to Object - Stack Overflow
- 投稿日:2020-03-16T08:41:41+09:00
オブジェクト指向プログラミング(素人が浅い知識でまとめさせていただきました)
クラスとインスタンス(オブジェクト指向)
クラスはくくり、インスタンスはくくりのルールで定義された物。
イメージは車というクラスからプリウスというインスタンスを生み出すって感じ。クラス内でやる事
インスタンスが持っておくべき変数や関数を定義する。
またインスタンス作成時にどのように初期設定をするか書かれる。
→コンストラクタ関数を定義し、引数をもらって初期値を作れるようにする。
→インスタンス作成時に引数として値を渡して、各インスタンスに変数の初期値を入れる。class Person { private Stirng name; //ここではインスタンスに必要な変数が用意されている。 Person(String name, ...) { ////()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる... } public void detailData() { //ここでインスタンスに必要な関数が書かれる } }補足
・クラス内で書かれているthisは、作られたこのインスタンス自身という意味になる。
private Stirng name; //このクラスでnameという変数を定義 public void nameInput(String input) { //文字列の変数inputを引数にnameInputを実行すると this.name = input; //このインスタンスの変数nameは受け取ったinputと同じ値になる。 }・インスタンスのための変数(インスタンスフィード)もあるがクラスのための変数(クラスフィード)も存在する。クラスフィードはクラスの中の変数のため、何かあるたび(例 インスタンスが作られるたび)に変更されるようにしたりする。
Person.Javaclass Person { private static int count; //これはクラスのための変数 private Stirng name; //これはインスタンスのための変数 Person(String name, ...) { ////インスタンス作成のための初期設定 Person.count ++; //これでPersonインスタンスが作成されるたびに、Personクラスのcountが+1される } }初期値設定、コンストラクタメソッド
これはインスタンス作成時、引数を受け取り初期値設定するのに必要な関数。
しかし初期値設定の引数入力パターンは1種類ではない。例えばmiddle nameがある人は、普通の人より入力する項目が増える。そのためmiddle nameがある人用の入力関数も必要。
↓
引数の入力パターンによって、コンストラクタメソッドもパターンを設ける。
overloadという方法で、複数の引数入力パターンに対応した同名のコンストラクタメソッドを作ることができる。Person.Javaclass Person { private Stirng firstname; //ここではインスタンスに必要な変数が用意されている。 private Stirng lastname; private Stirng middlename; Person(String firstname, String lastname) { //firstnameとlastnameだけの人の初期設定 this.firstname = firstname; //引数に使用された文字列であるfirstnameの値ががこのインスタンスのfirstnameになる。左と右で示しているfirstnameは違う。 this.lastname = lastname; } //同じ名前の関数を書く Person(String firstname, String middlename, String lastname) { //firstnameとlastnameとmiddlenameの人の初期設定 this.firstname = firstname; this.lastname = lastname; this.middlename = middlename; } }overloadとoverride
overload
overloadは同名の関数を複数作る時、名前は同名でも引数の入力パターンは異なるため、そのパターンから最適な関数を選択してくれること。主に初期設定の関数に行われることが多い。
↓
初期設定関数作成時、オーバーロードで作った関数同士の処理内容が重複している時がある。
↓
例えば初期設定関数である関数Aがある。
関数Aの中で、this(name等)を実装すると
関数Aと同名の初期設定関数を探し、その中でnameの処理をしている関数Bを探してきて、nameの処理を行ってくれる。以下のコードでは一つ前のコードをきれいにしている。Person.Javaclass Person { private Stirng firstname; //ここではインスタンスに必要な変数が用意されている。 private Stirng lastname; private Stirng middlename; Person(String firstname, String lastname) { this.firstname = firstname; //引数に使用された文字列であるfirstnameの値ががこのインスタンスのfirstnameになる。左と右で示しているfirstnameは違う。 this.lastname = lastname; } Person(String firstname, String middlename, String lastname) { //firstnameとlastnameとmiddlenameの人の初期設定 this(firstname, lastname); //ここが変わった。同名関数に存在するfirst,lastnameの処理を行った。 this.middlename = middlename; } }override
overrideは親クラスと同名の関数を定義する時に、子クラスの内容を上書きすること。
↓
普通に親クラスの関数と同名の関数を子クラスで定義してしまうと、その関数は親クラスの時の処理内容を引き継がず子クラスの処理内容だけを実行することになる。
↓
親クラスの時の処理内容も子クラスの関数に引き継ぎたいならば、子クラスの関数の中でsuper.メソッド名をしてやらないといけない。Person.Javaclass Person { . . . public void detailData() { //インスタンスの詳細な情報を表示する関数 System.out.println("名前は" + this.name); //インスタンスの変数を表示 System.out.println("年齢は" + this.age); } }Child.Javaclass Child extends Person { //ChildクラスはPersonクラスを継承 . . . public void detailData() { //親であるPersonクラスに同名の関数あり super.detailData(); //これで親であるPersonクラスにあるdetailData()の処理内容を引き継げる //ここからchildクラス特有の処理内容を書く . . } }カプセル化
カプセル化とは無駄を省いてわかりやすい物を作ることだそうです(詳しくは理解できていません。)
カプセル化の中の一つの機能をここでは書かせていただきます。
それはあるクラスで定義した変数や関数へのアクセス回路を制限する事(セキュリティーのため?)
↓
変数や関数定義の時に、直前にこれをつけるとアクセス制限ができる。
・public→外部のクラスからのアクセスが可能。
・private→外部のクラスからのアクセスが不可能。その変数、関数を定義したクラスの内部にあるメソッドを使ったアクセスなら可能。
・protected→基本的にprivateと同様に外部のクラスからのアクセスが不可能。しかしその変数、関数を定義したクラスを継承した子クラスならばアクセス可能。お作法
・インスタンスの情報となるクラスの変数はprivateにして、インスタンスの命令となるクラスの関数はpublicにして外部からアクセスできるようにする。変数を取得、変更するような関数(例 get~()やset~()とか)をクラス内で定義してやる事で、インスタンスの変数の取得、変更を可能にする。
House.Javaclass House{ private Stirng name; //ここでは必要な変数が用意されている。 private Person owner; //別のクラスPersonのインスタンスをownerという変数に格納できる。 House(String name, ...) { //インスタンス初期設定 } //ここから関数が書かれていく。 public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。 } public getOwner() { return this.owner; //このインスタンスのowner情報を返す。情報を返すだけなので単体では何も表示されない } }クラスの継承(親クラス、子クラス)
extendを使って親クラスを継承した子クラスを作ることができる。
また子クラス内でインスタンスに対する変数の初期値や初期関数の設定を変更できる。
クラスの継承はいくらでもできるのでクラスのクラスのクラスの...ができる。お作法
・クラス毎にファイルは分割して読みやすくするのが基本。
・継承とは異なるかもしれないがimportをすると外部のライブラリーやパッケージを読み込める。抽象クラス
様々な子クラスで必ず必要だけれど、クラスによって処理内容が異なる関数がある場合。
↓
親クラスで抽象メソッド(あえて何も処理が書かれていない関数)を定義する。
すると子クラスでは必ずoverrideしてその関数定義をしなければいけない。
また抽象メソッドを持つクラスは抽象クラスと呼ばれ、そのクラス自身のインスタンスは作成できない。多重継承
javaでは禁止されているが、子クラスが複数の親クラスを継承することを多重継承と呼ぶ
クラス間での交わり
Aクラス内で別のBクラスのインスタンスを変数として定義することができる。例えばHouseクラス内で、所有者を示す変数ownerとしてPersonクラスのインスタンスを紐づけることができる。
House.Javaclass House{ private Stirng name; //ここでは必要な変数が用意されている。 private Person owner; //別のクラスPersonのインスタンスをownerという変数に格納できる。 House(String name, ...) { //()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる... } //ここから関数が書かれていく。 public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。 } public getOwner() { return this.owner; //このインスタンスのowner情報を返す。情報を返すだけなので単体では何も表示されない } }Person.Javaclass Person { private Stirng name; //ここでは必要な変数が用意されている。 Person(String name, ...) { //()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる... } //ここから関数が書かれていく。 public void detailData() { //ここでインスタンスの詳細な情報を表示するようにする } }Main.Javahouse1 = New House("Aハウス", ...); person1 = New Person("山本", ...); //引数を受け取って、それぞれのクラスのインスタンスを作成 house1.setOwner(person1); //これでhouse1のownerがperson1の値と同じになる。 house1.getOwner().detailData(); //これでhouse1のownerの詳細な情報が得られる。 //getOwnerでownerの情報をreturnする(単体では何も表示されない)。その後にPersonで定義した関数を使うことで、ownerの情報を知れる。変数にインスタンスを入れるということ
newでインスタンスを作る。
引数の値を受け取って、メモリにそのインスタンスのための領域をを確保して、そこにインスタンスの情報を置いておく。(情報は二進数で書かれる)
↓
そいつを変数に代入する。
インスタンスのための変数を作ると、その変数のための領域がメモリ内で確保される。その領域にはインスタンスの領域がどこにあるかという情報だけを残す。多態性
Aクラスを継承しているBクラスのインスタンスは、Aクラスのインスタンスとしても扱えるしBクラスのインスタンスとしても扱えるぞって感じかな?。
例えば同じ関数処理を別のクラスのインスタンスに実行する場合、それぞれのクラス毎に関数を設定してしまいがち。しかしその異なったクラスが共通の親クラスを継承している場合、その親クラスに対する関数処理を書くだけで良くなる。これにより、その親クラスを継承する子クラスが増えても関数処理を書かなくて良くなる。
やりがちなコード
House.Javaclass House { . . . public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。 } }Villa.Javaclass Villa { //villaは別荘という意味 . . . public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。 } }Person.Javaclass Person { . . . public void buy(House house) { //引数としてHouseクラスのインスタンスである変数houseを受け取れば house.setOwner(this); //変数houseのownerには、このインスタンスがなる。 } public void buy(Villa villa) { //引数としてVillaクラスのインスタンスである変数villaを受け取れば villa.setOwner(this); //変数villaのownerには、このインスタンスがなる。 } }多態性を活かしたコード
(Houseクラスとvillaクラスの共通の親クラスはBuildingクラス)
Person.Javaclass Person { . . . public void buy(Building building) { //引数としてBuildingクラスのインスタンスである変数buildingを受け取れば building.setOwner(this); //変数buildingのownerには、このインスタンスがなる。 //親クラスに対する関数定義をしてやると子クラスに対する定義が不要になる } }参考にさせていただいた参考文献
- 投稿日:2020-03-16T08:33:03+09:00
【初心者向け】今度こそわかるDI〜DIの基礎とSpringにおけるDI〜
はじめに
DIとはDependency Injectionの略であり、日本語では「依存性の注入」と訳されることが多いです。
新卒だったころにSpringに触れた私は、初めてこの概念を聞いた時に頭に浮かんだのは、はてなマークでした。DIの概念やDIコンテナ、Springにおけるアノテーションとの結びつきがピンとこなかったのです。
今回は初心者だったころの自分にあてて、DIの概念およびSpringにおけるDIについて自分なりに整理しつつ、簡単にまとめていきたいと思います。
依存性(Dependency)とは
依存(性)とは何かということはソースコードで示したほうがわかりやすいと思います。
Main.javapublic class Main { public int hoge() { var sub = new Sub(); return sub.calculate(); } }Mainクラスのhogeメソッド内で、Subクラスのインスタンスを生成し、利用しています。
このようなMainクラスがあったとき、「MainクラスはSubクラスに依存している」と表現します。依存に関しては比較的わかりやすいと思います。
注入(Injection)とは
先程の例では、Subクラスのインスタンスを内部で生成していましたが、インスタンスを外部で生成し、それを用いるクラスに渡してあげることを注入と呼びます。
ここでは、コンストラクタを用いた例(コンストラクタインジェクション)を示します。
Main.javapublic class Main { private Sub sub; public int hoge() { return sub.calculate(); } // コンストラクタインジェクション public Main(Sub sub) { this.sub = sub; } }先程はhogeメソッド内でSubクラスのインスタンスを生成していましたが、今回の例では、インスタンスをコンストラクタから受け取るようになっています。
このようにすることで、Mainクラスを実際に用いる際は、
var sub = new Sub(); var main = new Main(sub);といった形で、SubクラスのインスタンスをMainクラスの外から渡す形になります。
これによるメリットは後述します。
DIコンテナとは
コンテナと聞くと現代ではDockerを連想するかもしれませんが、DIコンテナにおけるコンテナはDockerなどの文脈で用いられるコンテナと異なります。
誤解を恐れず一言で書くとすると、DIコンテナは「注入するインスタンスを管理してくれる入れ物」の役割をしてくれます。
先程のようにDIコンテナを用いずに、Mainクラスを用いようとすると、用いるたびにSubクラスのインスタンスが必要になります。
DIコンテナを用いると、このインスタンスを管理してくれるので、いちいちインスタンスを生成する必要がなくなります。
SpringにおけるDIコンテナ
Springではアノテーションでクラスを指定することで、そのクラスのインスタンスをDIコンテナで管理する対象として指定することができます。
具体的には以下のようなアノテーションです。
@Controller
@Service
@Repository
@Bean
ちなみに、DIコンテナで管理されているインスタンスはApplicationContextクラスのgetBeanメソッドを用いることで、取得することができます。
また、SpringではDIコンテナがインスタンスをいつまで管理するかという、スコープを設定することができます。
設定できるスコープは下記のとおりで、設定する際はScopeアノテーションを用います。
- singleton
- prototype
- request
- session
- global session
Springのデフォルトはsingletonになっていますが、設定を変更することにより、例えばsession単位で、コンテナに管理されているインスタンスを破棄し、再設定するといったことが可能になります。
初心者がやりがちな注意点としては、singletonにもかかわらず、クラスに変化しうるフィールドを持たせてしまうことです。以下に例を示します。
hogeService@Service public class hogeService { private String result; public int fuga(String suffix) { result = "test" + suffix; return result; } }hogeServiceにはServiceアノテーションが付与されており、特にスコープを設定していないので、スコープがsingletonになります。
そのため、resultフィールドを別のセッションで変更してしまう危険性があり、スレッドセーフでなくなってしまいます。
下記のようにクラスフィールドではなく、変数にしてあげることで、スレッドセーフな実装になります。
hogeService@Service public class hogeService { public int fuga(String suffix) { String result = "test" + suffix; return result; } }SpringにおけるDI
Springでは、Autowiredアノテーションを用いることで、DIすることができます。
インジェクションの方法には先程のコンストラクタインジェクションも含め、下記の3つがあります。
- フィールドインジェクション
- セッターインジェクション
- コンストラクタインジェクション
それぞれ具体的には、このようになります。
Main.javapublic class Main { // フィールドインジェクション @Autowired private Sub sub; public int hoge() { return sub.calculate(); } }Main.javapublic class Main { private Sub sub; public int hoge() { return sub.calculate(); } // セッターインジェクション @Autowired public setSub(Sub sub) { this.sub = sub; } }Main.javapublic class Main { private Sub sub; public int hoge() { return sub.calculate(); } // コンストラクタインジェクション @Autowired public Main(Sub sub) { this.sub = sub; } }Autowiredアノテーションを付与することで、フィールド、メソッドおよびコンストラクタの引数にDIコンテナからインスタンスを注入してくれます。
ちなみに自分がインジェクションする際は、こちらの記事にあるように、ライブラリのLombokのRequiredArgsConstructorアノテーションを用いて書いています。
DIのメリット
よく言われるメリットとして、単体テストが書きやすくなることが挙げられますが、
これはそのとおりだと私も思います。冒頭の例をもう一度示します。
Main.javapublic class Main { public int hoge() { var sub = new Sub(); return sub.calculate(); } }仮にこのSubクラスのcalculateメソッドが、DBにアクセスする必要があったとすると、Mainクラスの単体テストは非常に難しくなります。
しかし、DIによって注入するインスタンスをモックにすることで、テストすることができます。
ちなみに私はモックを用いる際はMockitoを用いています。まとめ
最後に、改めてDIおよびDIコンテナについて一言でまとめると下記のようになります。
DI:あるクラスに対して依存しているクラスのインスタンスを外部から渡してあげること
DIコンテナ:注入するインスタンスを管理してくれる入れ物これらを用いることで、テストが書きやすくなったり、スコープの管理がしやすくなったりというメリットがあります。
内容は以上です。本記事が少しでもお役に立てば幸いです。
最後までお読み頂き、ありがとうございました。参考文献
公式ドキュメント
Core Technologies
- 投稿日:2020-03-16T07:56:01+09:00
デプロイ失敗
エラー内容
[INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.360 s [INFO] Finished at: 2020-03-15T14:42:24Z [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project sf5-lounge: Compilation failure: Compilation failure: [ERROR] /tmp/build_4af038e236977e9ba2fca9b5a329339f/src/main/java/dev/GetTweet.java:[129,43] diamond operator is not supported in -source 1.5 [ERROR] (use -source 7 or higher to enable diamond operator) [ERROR] /tmp/build_4af038e236977e9ba2fca9b5a329339f/src/main/java/dev/GetTweetTest.java:[111,44] diamond operator is not supported in -source 1.5 [ERROR] (use -source 7 or higher to enable diamond operator)原因
pom.xmlに記載不備
対処
pom.xmlに以下追加
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin>
- 投稿日:2020-03-16T07:46:20+09:00
twitter4j使用ソースをデプロイしたら失敗した
■twitter4jをデプロイしたら失敗した
■エラー
[INFO] ------------------------------------------------------------- [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[9,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[10,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[11,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[12,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[13,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[14,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[15,22] package twitter4j.conf does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[16,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[17,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[18,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[19,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[20,17] package twitter4j does not exist [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[21,17] package twitter4j does not exist [ERROR]/tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[22,22] package twitter4j.conf does not exist■原因
Mavenが参照できてなかった
■対処
pom.xmlに以下を追加した。
<dependency> <groupId>org.twitter4j</groupId> <artifactId>twitter4j-stream</artifactId> <version>4.0.6</version> </dependency>■参考
- 投稿日:2020-03-16T02:55:40+09:00
java の int と Integer と BigInteger の違いや float や double を理解する
先日プログラミングコンテストの過去問を haskell でやった時に、べき乗をなんやかんやするロジックを書いた時に最終結果が
12340000
で欲しかったのに12340000.0
になってしまって通りませんでした。恥ずかしい。恥ずかしいけど、聞くは一時の恥聞かぬは一生の恥の解説とも言うし、知らないことは素直に学んで修めればなかったことにできるんです。できんの?
よくわからんけど、要するに曖昧なまま使ってた
int
みたいなものをきっちりまとめるぜって、そんな話。かるーく背景
普段は java / DDD で契約管理のシステムを作ってるんだけど、扱う数字なんてたかだか数万の整数くらいなんだよね。ちょっとした円だとか契約の数を数えたりだとか、その程度。
なんと月の請求に日割りがないしね。驚きだね。DDD の value object とかのおかげで、生の
int
とかを触ることもあんまりないしね。なのでこの記事を書く直前の例えば java の理解度はだいたいこんな感じ。
「
byte
? よくわからんけど怖い」
「long
? 長そ〜」
「BigInteger
? でかそ〜」
「float
? ふわふわ〜」
「double
? 何が倍なの〜?とうおるるるるるるるる」ってくらいの理解度。これ大マジ。
(double のイタリア語が doppio ってことだけは知ってたんだよォォォ)
なのでそれくらいの人がまとめたんだよってことだけ了承してね!
haskell 版もよろしく
この記事の確認言語には java を使っています。
もともと haskell で確認してたのですが、いくつかの言語を確認した方がより理解が進むかと思って java でも試してみました。
python でも少し確認したりしたのですが、記事は本懐の haskell と仕事で使ってる java にしようかと思います。
というわけで、こっちもよろしく。→ haskell の Int と Integer の違いや Float や Double や Rational を理解する
まずは概要
プログラミングに入る前に、数学の話です。
数学といっても代数とか圏論とかの怖いやつはでてこないです。僕も怖いので。
だいたいが中学校くらいまでの話。まずはこれを見てください。
ざっくり説明します。
実数と虚数
普段目にする数はだいたい
実数
です。対して
虚数
は便利なので発明された数ですが、現実には存在しません。
代表的なのが√-1
もしくはそれをi
と表現したものですね。わかりやすいですね。僕はよくわかりません。
虚数
は二乗して 0 未満の実数になる数で、実数
はそれ以外と定義されます。ここを細かく考える気はないので、「だいたい
実数
」くらいで大丈夫です。有理数と無理数
実数
の分類は真剣に考えます。
実数
は有理数
と無理数
に大別されますが、有理数
は整数の比で表現できる数です。対してそれ以外の整数の比で表現できない数を
無理数
と言います。整数と有限小数と循環小数
いずれも
有理数
です。
整数
は説明するまでもありませんね。
3
は3/1
と表現できるので有理数
です。有限小数は
0.5
の様な終わりのある小数
です。
1/2
の様に整数
の比で表現できます。対して
0.333...
や0.142857142857142857...
の様に同じ数字の繰り返しが無限に続く小数
を循環小数
と言います。
これも1/3
や1/7
の様に整数
の比で表現できます。負の整数と正の整数とゼロと自然数
整数
は一番馴染みがあるのであまり問題ないと思いますが、一応。
負の整数
は-1
や-5
のことで、-5/1
の形で表現できます。
0
は0/1
ですね。
正の整数
についても同様です。また、
正の整数
を自然数
とも言います。(0
を含めるかは本記事では問いません)小数と分数
少数
と分数
についての補足です。
分数
は数の比で表現される数であり、一見有理数
と同じな気がします。
が、有理数
は整数の比なので、分数の方が広い概念です。例えば
1/√2
なんてのもありです。これは整数の比ではないので無理数
です。
(だいたい 0.7 なので二乗するとだいたい0.5
で0
より大きいので虚数
ではないですね。)また、例えば
無限小数
というものがありますが、先ほどの図で言うと、有理数
の循環小数
も無理数
も無限小数
です。
循環しているかしていないかの違いですね。押さえておきたい英語
以上の要点を押さえつつ、我々プログラマは英単語も知らないと困るので、ざっくり整理しておきます。
(と言っても絵には英語も入ってますが。)
実数
はreal number
で虚数
はimaginary number
です、イメージつきやすいですね。あまり馴染みはないですが、
有理数
はrational number
です。コピペで書いてると稀にぶち当たります。
ratio
が比率
という意味なので変数名で使ったことがある人もいるのではないでしょうか。また、絵にはないですが
小数
はdecimal
で分数
はfraction
です。プログラミングの世界へ ( java )
さっそく java でサンプルコードを見たいところですが、人間の世界とコンピュータの世界では大きく違うことがあります。
それは「メモリが有限」ということです。
どこにそれが関係するかと言うと、例えば「すげーでけー数」と「無限小数」です。
固定長整数
例えば java の
int
は 32bit 固定の整数
です。コンピュータのメモリには限界があるので、数値を 32 の
0|1
の範囲に限定して表現します。多倍長整数
対して
多倍長整数
は扱う数に応じて動的にメモリを確保する数値の表現方法です。理論上は無限の数を扱うことができます。(もちろんコンピュータのメモリの許す限りですが。)
固定長整数と多倍長整数
みんな大好きオーバーフローはこの
固定長整数
が引き起こします。たとえば java の
byte
は 8bit 固定の整数
です。
頭の 1bit を正負の符号に、残りを値の表現に使います。
0000|0000
から1
ずつ増加を始め、0111|1111
から1000|0000
になるところでオーバーフローし、
1111|1111
から1|0000|0000
になるところで 9bit 目が範囲外になり0000|0000
として扱われます。
(見やすくするために 4 桁ごとに|
を入れています。)対して
BigInteger
は多倍長整数
です。
こいつは桁あふれが起きそうになると、動的にメモリを確保するのでオーバーフローしません。(符号や値の保持については実装方法によるので、上図はイメージです。)
固定長整数
はメモリ効率や性能に優れ、多倍長整数
は精度に優れます。
これらは適材適所です。浮動小数点
整数
と同じく小数
においても同様の考え方があります。
浮動小数点
とは数値の表現方法の一つで、固定長
の仮数部
と指数部
を持つ表現方法です。ざっくり
仮数部
は値で指数部
は桁を表していると考えれば大丈夫。例えば二進数の
0.00000101
は101 * 2^-8
の様に表されます。ただこれだと
10.1 * 2^-7
とかでも表現できちゃうので、IEEE754
と言う規格で仮数部
は1.x
にすると決まってます。なので1.01 * 2^-6
です。
1.01e-6
なんて書いたりもします。コード書いていてたまに出る
e
入ってるやつはこれだね。怖かったけど克服したぞ。
仮数部
と指数部
によって小数点を打つ位置が変わってくるので浮動小数点
と言うのかな。
一方で対になる単語は固定小数点
で、例えば整数
がこれに含まれます。java で確認
前置きが長くなりました。ここからはガシガシ java で確認していきます。
type 説明 byte, Byte 8bit 固定長整数 short, Short 16bit 固定長整数 int, Integer 32bit 固定長整数 long, Long 64bit 固定長整数 float, Float 単精度浮動小数点 ( 32bit ) double, Double 倍精度浮動小数点 ( 64bit ) BigInteger 多倍長整数 BigDeciaml 多倍長小数 以下のコードは
System.out.println
に相当するものは省略し、その行のコメントがその結果とします。byte, short, int, long
たくさんあるけど恐れることはありません。
こいつらは全部
固定長整数
で、違いは表現できる精度しかありません。Byte.MAX_VALUE; // 127 Short.MAX_VALUE; // 32767 Integer.MAX_VALUE; // 2147483647 Long.MAX_VALUE; // 9223372036854775807例えば
Integer
の上限値に+1
すると、オーバーフローします。Integer.MAX_VALUE + 1; // -2147483648相互変換
また当然ですが、精度の低い方から大きい方へのキャストは問題ありませんが、逆は正しく行えません。
short s = 20000; (int) s; // 20000int i = 40000; (short) i; // -25536ところで int と Integer の違い
もともとの趣旨とは離れるのですが、案外面白いのでせっかくと言うことで。
java の
int
はプリミティブ型で、Integer
はクラス型と言います。主な違いはすんごいざっくり言うと「
int
はnull
が許容されない」のと、「int
はList<T>
とかのT
になれない」くらいです。
精度とかについてはint
とInteger
に違いはありません。これ大事。また java にはコンパイラがよしなに相互変換してくれる仕組みがあるので、大体の場合はあんまりどちらかを気にしなくても大丈夫です。
相互変換、の前にメモリの話
普段あんまり考えることはないかもしれませんが、スタック領域とヒープ領域について超ざっくり説明します。
例えばこの様なコードを書いた場合。
(int
とInteger
の変数を区別しやすくするため、本記事では変数名の先頭に大文字を使います。)Integer Ia = new Integer(1);この場合、メモリはこんな感じになってます。
new
をするとスタック領域のIa
という変数に何かが入ります。
なんとなくIa
にはインスタンス自体が入ってる気がしますが、入ってるのは矢印だけです。恐ろしい言い方をするとポインタです。作られたインスタンスはヒープ領域に入っています。
対してプリミティブ型の
int
はスタック領域にそのまま確保されます。Integer Ia = new Integer(1); Integer Ib = new Integer(1); int ia = 1; int ib = 1;なのでこんなコードを書いた場合の絵は下のようになります。
同一性と同値性
「java で比較に
==
を使うんじゃあねぇ」と怖い人に怒られたことがある人はいっぱいいると思いますが、せっかくなのでなんでなのか見てみましょう。クラス型における
同一性
は同じインスタンスかを、同値性
は同じ値かを比較することです。
前者は==
で、後者はequals
によって行われます。また同値性は実装に依存します。
(例えば DDD の entity の比較では identity の一致のみで同値とみなす場合もあります。)プリミティブ型の
==
はシンプルに値を比較します。なので
Ia == Ib
は宛先の違う矢印なので false です。Ia.equals(Ib)
は宛先の値が同じなので true です。例えるなら「A さんも B さんも 500 円玉を持っていて、物理的には違う硬貨だけど価値は同じ」と言った感じです。
auto boxing と auto unboxing
スタック領域とヒープ領域、比較について理解したところで、相互変換についてです。
int
->Integer
を boxing , 逆を unboxing と言います。
ラッパークラスの箱に入れるイメージかな。以下のコードが実行できるのは auto boxing | auto unboxing によるものです。
Integer Ia = new Integer(1); int ia = Ia; // unboxingint ib = 1; Integer Ib = ib; // boxing内部的にはスタック領域に値を持ってきたり、ヒープ領域にインスタンスを作って参照を得たりしています。
(実際には元の値は消えませんが、イメージしやすいので薄くしています。)余談 落とし穴
さて、以下のコードは
true
とfalse
どちらになるでしょうか。int ia = 1; int ib = 1; Integer Ia = ia; Integer Ib = ib; Ia == Ib; // true or false ?auto boxing によって
new
されるのでIa
とIb
の矢印は違うはずです。上の絵でもそうなってます。が、これ
true
になります。どうやら auto boxing は
Integer#valueOf
で、auto unboxing はInteger#intValue
によって実現される様です。Integer Ia = Integer.valueOf(ia); Integer Ib = Integer.valueOf(ib);で、肝心の
Integer#valueOf
ですが、こんな実装になっています。public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }どうやらよく使う
-128
~127
はキャッシュされているみたいですね。なので上のコード例だとnew
されないです。こんなコードだとちゃんと
false
になります、理解は間違ってなかった様で安心だ。int ia = 1000; int ib = 1000; Integer Ia = ia; Integer Ib = ib; Ia == Ib; // falseあ、ちなみに内部では auto unboxing に
Integer#intValue
を使うと言うことは、Ia
がnull
の場合に auto unboxing をするとNullPointerException
がでますよ。int と Integer の違いまとめ
- 精度は同じ
- 比較はちょっと気をつけろよ
- 相互変換は便利だけど完全に置き換えてくれるわけではないから気をつけろよ
ってことですね。
本記事においては以後
int
とInteger
やfloat
とFloat
は精度の違いがないため、特に断りなくサンプルコードで都合が良い方を用います。float, double
整数
は押さえましたね。次は小数
です。
double
の何が倍
なんだって思ってましたが、勉強すれば明瞭ですね。
float
は 32bit を使って、double
は 64bit を使って値を表現するということでした。だから倍精度。コンピュータのメモリが有限である以上
無限小数
を完全に表現することは不可能なので、誤差が出る前提で扱わなければなりません。例えば十進数の
0.01
は二進数だと有限で表現することができません。
有限で表現できない以上どこかで諦めなければいけず、それを繰り返せば誤差が大きくなるのはなんとなく感覚で理解できますね。で、どんな誤差が出るか、です。試してみましょう。
float f = 0; for (int i = 0; i < 100; i++) { f += 0.01f; } double d = 0; for (int i = 0; i < 100; i++) { d += 0.01d; } f; // 0.99999934 d; // 1.0000000000000007
double
の方が1.0
に近いですね。相互変換
float
とdouble
の変換も、short
とint
と同様に精度の高い方から低い方へ変換すると壊れます。f; // 0.99999934 d; // 1.0000000000000007 (double) f; // 0.9999993443489075 (float) d; // 1.0
double
からfloat
にした場合は欠けてしまっていますね。またそもそも有限なので、単純に以下の様な値で誤差が出ます。
10d / 3d; // 3.3333333333333335 1.00000001f; // 1.0BigInteger, BigDecimal
お待たせしました、
多倍長
の奴らです。こいつらは桁に応じて動的にメモリを確保するので、オーバーフローしないし誤差も出ません。なんかすごい。
さっそく試してみましょう。
BigDecimal
小数
のBigDecimal
から試してみます。初っ端から気前よく巨大な整数
を扱ってみましょう。BigDecimal bd = new BigDecimal(Long.MAX_VALUE); bd; // 9223372036854775807 bd.add(new BigDecimal(1)); // 9223372036854775808
long
の上限に加算してもオーバーフローしてません。
もっと思い切りよく足しても全然大丈夫。bd.add(bd); // 18446744073709551614
小数
も加算できる。bd.add(new BigDecimal(0.5)); // 9223372036854775807.5本命?の
小数
の誤差はどうでしょうか。BigDecimal bd = BigDecimal.ZERO; BigDecimal x = new BigDecimal(0.01); for (int i = 0; i < 100; i++) { bd = bd.add(x); } bd; // 1.00000000000000002081668171172168513294309377670288085937500
double
の1.0000000000000007
より精度が良いですね。(toString
出来ているのはすげぇ頑張ってるからです。)
double
で誤差が出た10d / 3d
はどうでしょうか。BigDecimal bd10 = new BigDecimal(10); BigDecimal bd3 = new BigDecimal(3); bd10.divide(bd3); // ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
terminating
って単語は冒頭のベン図っぽいもので見ましたね、有限小数じゃあないって怒られてます。誤差のでる値は誤差のあるまま持たせてくれないみたいですね。
切り捨てたり切り上げたりを明示しないとだめみたいです。bd10.divide(bd3, RoundingMode.FLOOR) // 3 bd10.divide(bd3, RoundingMode.CEILING) // 4BigInteger
こいつは簡単です。
小数
の扱えないBigDecimal
です。BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE); bi; // 9223372036854775807 bi.add(bi); // 18446744073709551614
BigInteger
には0.5
みたいな小数
を渡せる生成メソッドがないので、BigDecimal
と比べると「これだけ」です。もう大丈夫。怖くない。
余談 破壊/非破壊
ところでなんとなく java の感覚だと
add
すると破壊する気がしませんか?
List#add
とかそうじゃん。けど、
add
するたびにメモリを確保し直す可能性があることを理解していると、非破壊で毎度違うインスタンスを作ってるって考えやすいよね。
(実装方法によるので immutable の場合もあるけど mutable の場合もあるらしい。)まとめ
ながーい記事になったけど、やってみて感じた java における数値表現の要点は3つだけだ!
byte
,short
,int
,long
の違いは精度だけ、それぞれ限界があるぜfloat
,double
も違いは精度だけ、小数
は有限のメモリでは表現できないので誤差が前提なんだぜBigInteger
とBigDecimal
は(メモリがある限り)限界がない整数
と小数
だぜこれだけだ!
int
とInteger
の違いは数値表現ってより java のお勉強としてがんばるんだ!いやーそれにしても勉強になった。普段どれだけ適当にやってきたかを痛感した。
そしてこれを理解したらどうするかと言うと、やっぱドメインロジックとは切り離したいので value object を作って隠蔽するわけだ!
きっちり理解したので普段の業務(ドメイン実装)ではやっぱり使わないわけだ!なんというパラドクス!