20211203のJavaに関する記事は8件です。

EclipseをMacにインストールする手順

今回はJava開発環境のEclipseをMacにインストールする手順を教えます✨ まずは下記のサイトからインストーラを入手します。   https://mergedoc.osdn.jp/ 「Eclipse 2021 最新版」をクリック? 容量があるのであればMac版のJavaで「Full Edition」をダウンロードしましょう。 ダウンロードができたらファイル実行し、表示された画面に従って「Eclipse_2021-12」を「Applications」フォルダにコピーします。 先ほど「Applications」フォルダに移動した「Eclipse」を起動してみましょう。 このようなエラーが表示された場合は、  システム環境設定 > セキュリティとプライバシー > このまま開く これでアプリを開くことができました 続きは別の記事で紹介しようと思います
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EclipseをMacにインストールする方法

今回はJava開発環境のEclipseをMacにインストールする手順を教えます✨ まずは下記のサイトからインストーラを入手します。   https://mergedoc.osdn.jp/ 「Eclipse 2021 最新版」をクリック? 容量があるのであればMac版のJavaで「Full Edition」をダウンロードしましょう。 ダウンロードができたらファイル実行し、表示された画面に従って「Eclipse_2021-12」を「Applications」フォルダにコピーします。 先ほど「Applications」フォルダに移動した「Eclipse」を起動してみましょう。 このようなエラーが表示された場合は、  システム環境設定 > セキュリティとプライバシー > このまま開く これでアプリを開くことができました 続きは別の記事で紹介しようと思います
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Caffeineでお手軽にローディングキャッシュ

まえがき ・Caffeine Cache をもう一段深堀り ・通常版は 【Java】Caffeineでお手軽にローカルキャッシュをご参考下さい。 ・ローディングキャッシュ(LoadingCache)とは…   ・リクエストの際は既存のキャッシュにあるものを返し、裏側で新しいデータを読み込み格納しておくキャッシュの事 長所 ・重い処理の影響をリクエストの際に受けない ・初回を起動時に読み込んでおけば、あとは良しなにしてくれる 短所 ・リクエストを返すと古い(現状の値ではない)値が取得される   ・↑これを対処するにはScheduledExecutorServiceとかで定期的にロードするとかでしょうか   ・定期的にロードするとリソース使うので一長一短な気がします   ・バッチ等でテーブルにデータを入れるとか、起点となる処理が実行された際に裏で読み込むとか、色々と方法はありそうです 環境 spring framework java 8 使い方 ・ここではランキングのキャッシュを想定 宣言 private static @NonNull LoadingCache<String, Ranking> rankingCache = null; 初期化 ・通常のキャッシュと異なるのは build の際にキャッシュローダーを渡すこと   ・ローダーを起動することでキャッシュのリフレッシュをしています   ・refreshAfterWrite を指定することで書き込み後の時間経過後にリフレッシュするようになります   ・expireAfterWrite も別に指定することが可能 @PostConstruct public void init() { rankingCache = Caffeine.newBuilder() .maximumSize(64) .refreshAfterWrite(10, TimeUnit.MINUTES) .build(rankingCacheLoader()); } 読み出し関数 ・引数は String: param1, String: startDate, String: endDate を想定(この引数からキーを作ります) public Ranking getCachedRankingDto(String param1, String startDate, String endDate) { Ranking result = new Ranking(); if (param1 == null || startDate == null || endDate == null) return result; final String[] params = { param1, startDate, endDate }; final String cacheKey = String.join("-", params); try { result = rankingCache.get(cacheKey, key -> { try { return getRankingDto(param1, startDate, endDate); } catch (Exception e) { throw new RuntimeException(e); } }); } catch (Exception e) { rankingCache.invalidate(cacheKey); StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); logger.error(sw.toString()); } return result; } キャッシュローダー ・読み出し関数とは逆に、キーを分割してパラメータにしています public CacheLoader<String, Ranking> rankingCacheLoader() { CacheLoader<String, Ranking> loader = new CacheLoader<String, Ranking>() { @Override public @Nullable Ranking load(@NonNull String key) throws Exception { final String[] params = key.split("-"); if (params.length != 3) { return null; } return getRankingDto(params[0], params[1], params[2]); } }; return loader; } 実読み込み関数(キャッシュしたい重い処理) public Ranking getRankingDto(String param1, String startDate, String endDate) { if (param1 == null || startDate == null || endDate == null) { return null; } // データ取得など重い処理 Ranking dto = ... return dto; } 手軽に良きキャッシュライフ(?)が手に入ります。 以上、お疲れさまでした!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Micronaut 3を使ってみる

What's? TIS Advent Calendar 2021に参加ということで。 2年ほど前にMicronaut 1.0で遊んでいたことがあるのですが、気づくと3.0を超えていたので、この機会に久しぶりに触ってみようかなと。 Micronautとは、フルスタックのマイクロサービスやサーバーレスに向くと謳われているフレームワークです。 環境 今回の環境はこちらです。 $ java --version openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04) OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-91-generic", arch: "amd64", family: "unix" CLIのインストールとプロジェクトの作成 まずは、MicronautのCLIをインストールします。 SDKMANを使って行います。 $ sdk install micronaut 現在のMicronautのバージョンは、3.2.0です。 $ mn --version Micronaut Version: 3.2.0 そのまま続けて、プロジェクトを作成します。ビルドツールは、Mavenにしました。 $ mn create-app hello-micronaut3 --build maven プロジェクト内に移動。 $ cd hello-micronaut3 こんな感じの構成になっています。 $ tree . ├── README.md ├── micronaut-cli.yml ├── mvnw ├── mvnw.bat ├── pom.xml └── src ├── main │   ├── java │   │   └── hello │   │   └── micronaut3 │   │   └── Application.java │   └── resources │   ├── application.yml │   └── logback.xml └── test └── java └── hello └── micronaut3 └── HelloMicronaut3Test.java 10 directories, 9 files 含まれているJavaソースコードと src/main/java/hello/micronaut3/Application.java package hello.micronaut3; import io.micronaut.runtime.Micronaut; public class Application { public static void main(String[] args) { Micronaut.run(Application.class, args); } } 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>hello.micronaut3</groupId> <artifactId>hello-micronaut3</artifactId> <version>0.1</version> <packaging>${packaging}</packaging> <parent> <groupId>io.micronaut</groupId> <artifactId>micronaut-parent</artifactId> <version>3.2.0</version> </parent> <properties> <packaging>jar</packaging> <jdk.version>11</jdk.version> <release.version>11</release.version> <micronaut.version>3.2.0</micronaut.version> <exec.mainClass>hello.micronaut3.Application</exec.mainClass> <micronaut.runtime>netty</micronaut.runtime> </properties> <repositories> <repository> <id>central</id> <url>https://repo.maven.apache.org/maven2</url> </repository> </repositories> <dependencies> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-inject</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-validation</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.micronaut.test</groupId> <artifactId>micronaut-test-junit5</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-http-client</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-http-server-netty</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-runtime</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>io.micronaut.build</groupId> <artifactId>micronaut-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <!-- Uncomment to enable incremental compilation --> <!-- <useIncrementalCompilation>false</useIncrementalCompilation> --> <annotationProcessorPaths combine.children="append"> <path> <groupId>io.micronaut</groupId> <artifactId>micronaut-http-validation</artifactId> <version>${micronaut.version}</version> </path> </annotationProcessorPaths> <compilerArgs> <arg>-Amicronaut.processing.group=hello.micronaut3</arg> <arg>-Amicronaut.processing.module=hello-micronaut3</arg> </compilerArgs> </configuration> </plugin> </plugins> </build> </project> アプリケーションを書いてみる こちらを見ながら、簡単なアプリケーションを作ってみましょう。 Creating a Server Application DIも使っておきます。 Inversion of Control Controllerの作成。 src/main/java/hello/micronaut3/HelloController.java package hello.micronaut3; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import jakarta.inject.Inject; @Controller("/hello") public class HelloController { @Inject MessageService messageService; @Get(produces = MediaType.TEXT_PLAIN) public String index() { return messageService.get(); } } Beanの作成。 src/main/java/hello/micronaut3/MessageService.java package hello.micronaut3; import jakarta.inject.Singleton; @Singleton public class MessageService { public String get() { return "Hello Micronaut 3!!"; } } Micronaut Maven Pluginで起動。 $ mvn mn:run こんな感じのバナーが表示されます。 __ __ _ _ | \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ | |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| | | | | | (__| | | (_) | | | | (_| | |_| | |_ |_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| Micronaut (v3.2.0) 17:13:26.550 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 974ms. Server Running: http://localhost:8080 確認。 $ curl localhost:8080/hello Hello Micronaut 3!! OKですね。 次は、パッケージングしてJARファイルを作成しましょう。 $ mvn package 起動。 $ java -jar target/hello-micronaut3-0.1.jar __ __ _ _ | \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ | |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| | | | | | (__| | | (_) | | | | (_| | |_| | |_ |_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| Micronaut (v3.2.0) 17:14:18.344 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 957ms. Server Running: http://localhost:8080 っていうか、起動速いですね。 Startup completed in 957ms. 結果は同じなので、割愛。 ネイティブイメージを作成する 最後に、このアプリケーションをネイティブイメージにしたいと思います。 Microservices as GraalVM native images GraalVMをインストールせずにDockerを使ってビルドする方法もあるようですが、今回はGraalVMをインストールしてビルドすることにします。 GraalVMもSDKMANでインストール。native-imageツールもインストールしておきます。 $ sdk install java 21.3.0.r11-grl $ sdk install java 21.3.0.r11-grl $ gu install native-image GraalVMとnative-imageツールがインストールされました。 $ java --version openjdk 11.0.13 2021-10-19 OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05) OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05, mixed mode, sharing) $ native-image --version GraalVM 21.3.0 Java 11 CE (Java Version 11.0.13+7-jvmci-21.3-b05) -Dpackaging=native-imageを指定して、パッケージング。 $ mvn package -Dpackaging=native-image だいぶ時間がかかりますが、なんとか完了します。 [hello-micronaut3:208224] classlist: 4,339.55 ms, 0.96 GB [hello-micronaut3:208224] (cap): 802.08 ms, 0.96 GB [hello-micronaut3:208224] setup: 3,730.72 ms, 0.96 GB Warning: class initialization of class io.micronaut.http.server.$CoroutineHelper$Definition failed with exception java.lang.NoClassDefFoundError: kotlin/coroutines/CoroutineContext. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.micronaut.http.server.$CoroutineHelper$Definition to explicitly request delayed initialization of this class. [hello-micronaut3:208224] (clinit): 2,268.13 ms, 3.58 GB [hello-micronaut3:208224] (typeflow): 15,282.40 ms, 3.58 GB [hello-micronaut3:208224] (objects): 140,670.17 ms, 3.58 GB [hello-micronaut3:208224] (features): 25,422.79 ms, 3.58 GB [hello-micronaut3:208224] analysis: 192,424.77 ms, 3.58 GB [hello-micronaut3:208224] universe: 20,954.42 ms, 3.58 GB [hello-micronaut3:208224] (parse): 5,223.66 ms, 3.61 GB [hello-micronaut3:208224] (inline): 18,816.18 ms, 3.48 GB [hello-micronaut3:208224] (compile): 99,596.81 ms, 3.71 GB [hello-micronaut3:208224] compile: 141,904.16 ms, 3.71 GB [hello-micronaut3:208224] image: 15,093.41 ms, 3.71 GB [hello-micronaut3:208224] write: 3,054.96 ms, 3.71 GB [hello-micronaut3:208224] [total]: 382,040.97 ms, 3.71 GB # Printing build artifacts to: /path/to/target/hello-micronaut3.build_artifacts.txt [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 06:27 min [INFO] Finished at: 2021-12-03T17:21:50+09:00 [INFO] ------------------------------------------------------------------------ ネイティブイメージができあがりました。 $ file target/hello-micronaut3 target/hello-micronaut3: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ceca748796f73559894e78c43bb90f0de66038a1, for GNU/Linux 3.2.0, with debug_info, not stripped 起動。 $ ./target/hello-micronaut3 __ __ _ _ | \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ | |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| | | | | | (__| | | (_) | | | | (_| | |_| | |_ |_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| Micronaut (v3.2.0) 17:22:42.238 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 63ms. Server Running: http://localhost:8080 起動速度がめちゃくちゃ速くなりました。 Startup completed in 63ms. 動作確認。 $ curl localhost:8080/hello Hello Micronaut 3!! 補足 GraalVMをインストールせずとも、Dockerを使うとネイティブイメージを作れるようです。 $ mvn package -Dpackaging=docker-native -Pgraalvm この場合、GraalVMのアーティファクトをダウンロードしてきます。 [INFO] ---------------------------[ docker-native ]---------------------------- Downloading from central: https://repo.maven.apache.org/maven2/org/graalvm/sdk/graal-sdk/21.3.0/graal-sdk-21.3.0.pom Downloaded from central: https://repo.maven.apache.org/maven2/org/graalvm/sdk/graal-sdk/21.3.0/graal-sdk-21.3.0.pom (1.4 kB at 340 B/s) Downloading from central: https://repo.maven.apache.org/maven2/org/graalvm/nativeimage/svm/21.3.0/svm-21.3.0.pom Downloaded from central: https://repo.maven.apache.org/maven2/org/graalvm/nativeimage/svm/21.3.0/svm-21.3.0.pom (1.9 kB at 8.3 kB/s) ... こんなディレクトリツリーになります。 $ tree $HOME/.m2/repository/org/graalvm -d $HOME/.m2/repository/org/graalvm ├── buildtools │ ├── junit-platform-native │ │ └── 0.9.7.1 │ ├── native-maven-plugin │ │ └── 0.9.7.1 │ └── utils │ └── 0.9.7.1 ├── compiler │ └── compiler │ └── 21.3.0 ├── nativeimage │ ├── objectfile │ │ └── 21.3.0 │ ├── pointsto │ │ └── 21.3.0 │ └── svm │ └── 21.3.0 ├── sdk │ └── graal-sdk │ └── 21.3.0 └── truffle └── truffle-api └── 21.3.0 23 directories が、手元の環境の事情で、Dockerイメージを使ってビルドする最中に失敗してしまうので…。 回避方法はまた別途探そうかなと思います。 こんなところで?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Themeleaf初心者向け】formにおけるth:fieldの挙動まとめ

はじめに Spring+Thymeleafで開発を行っていて、 formの作成の際、 <input type="radio" th:value="○" th:text="○" th:field="○"> のように、複数Thymeleafタグを使っています。 これって全部書く意味があるのか?必要ないものはないのか? と思ったので、自分で検証してみた結果を以下にまとめます。 概要 inputのtypeによって、th:fieldの挙動が違うので、記載すべきものが違います。 text,number,range th:fieldは、「id」「name」「value」の役割を果たします。 th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先されます。 textarea th:fieldは、「id」「name」「text」の役割を果たします。 th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先されます。 radio,checkbox,select th:fieldは「id」「name」の役割を果たします。 つまり、他2パターンのように「value」や「text」を補うことはできません。 th:valueとth:textは別途指定する必要があります。 前提 ・コントローラークラス コード説明は後述します。 ThFieldTestController.java public class ThFieldTestController { // import文省略 @ModelAttribute private ThFieldTestForm setUpForm() { ThFieldTestForm thFieldTestForm = new ThFieldTestForm(); thFieldTestForm.setGender("2"); return thFieldTestForm; } @RequestMapping("") public String index(Model model) { Map<Integer, String> genderMap = new LinkedHashMap<>(); genderMap.put(1, "woman"); genderMap.put(2, "man"); model.addAttribute("genderMap", genderMap); model.addAttribute("gender", "1"); // ${"gender"}で1を呼べるように return "th-field-test"; } setUpForm()メソッドでは、 thFieldTestForm.setGender("2"); でフォームのgenderの初期値に2を渡しています。 index()メソッドでは、リクエストスコープにデータを格納し、 <○ th:each="gender:${genderMap}" th:○="${gender.key}"> <th:○="${gender}"> のどちらでも値を呼べるようにしています。 この時、マップでないgenderの値は1を渡しています。 ・フォームクラス ThFieldTestForm.java public class ThFieldTestForm { private String gender; public void setGender(String gender) { this.gender = gender; } public String getGender() { return gender; } } 詳細 textの場合 th-field-test.html <div class="text"> <h4>text</h4> リクエストスコープのgender:1<br> formの初期値:2<br> 【text】th:field="*{gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:field="*{gender}"><br> <button>送信</button> </form> 【text】th:field="*{gender}" th:value="${gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:field="*{gender}" th:value="${gender}"><br> <button>送信</button> </form>   th:value="${gender}"が優先されるなら1<br> th:field="*{gender}"が優先されるなら2<br> →th:fieldの入力値保存のほうが強く働いている<br> 【text】th:value="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:value="${gender}"><br> <button>送信</button> </form> 【text】th:text="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:text="${gender}"><br> <button>送信</button> </form><br> </div> ブラウザだと・・・ コンソールで確認すると・・・ →th:fieldがあると、id,name,valueが入力されている。 (th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。) (numberとrangeも同じ挙動のため割愛します。) textareaの場合 th-field-test.html   <div class="textarea"> <h4>textarea</h4> リクエストスコープのgender:1<br> formの初期値:2<br> 【textarea】th:field="*{gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:field="*{gender}"></textarea><br> <button>送信</button> </form> 【textarea】th:id="gender" th:name="gender" th:text="${gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:id="gender" th:name="gender" th:text="${gender}"></textarea><br> <button>送信</button> </form> 【textarea】th:field="*{gender}" th:value="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:field="*{gender}" th:value="${gender}"></textarea> <button>送信</button> </form> 【textarea】th:value="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:value="${gender}"></textarea> <button>送信</button> </form> 【textarea】th:text="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:text="${gender}"></textarea> <button>送信</button> </form><br> 【textarea】th:field="*{gender}" th:text="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:field="*{gender}" th:text="${gender}"></textarea> <button>送信</button> </form> th:text="${gender}"が優先されるなら1<br> th:field="*{gender}"が優先されるなら2<br> </div> ブラウザだと・・・ コンソールだと・・・ →th:fieldがあると、id,nameが入力されており、textを指定したときと同じ挙動をしている。 (th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。) radioの場合 th-field-test.html <div class="radio"> <h4>radio</h4> リクエストスコープのgender:1<br> formの初期値:2<br> 【radio】th:field="*{gender}" <br> → エラー(org.attoparser.ParseException: Attribute "value" is required in "input(radio)" tags)<br><br> <!-- <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> --> <!-- <span th:each="gender:${genderMap}"> --> <!-- <input type="radio" th:field="*{gender}"><br> --> <!-- </span> --> <!-- <button>送信</button> --> <!-- </form><br> --> 【radio】th:field="*{gender}" th:value="${gender.key}<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:field="*{gender}" th:value="${gender.key}"><br> </span> <button>送信</button> </form><br> 【radio】th:value="${gender.key}<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:value="${gender.key}"><br> </span> <button>送信</button> </form><br> 【radio】th:value="${gender.key} th:text="${gender.value}"<br> → 送れるけどデータとしてgenderのデータとして受け取られない<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:value="${gender.key}" th:text="${gender.value}"><br> </span> <button>送信</button> </form><br> 【radio】th:field="*{gender}" th:text="${gender.value}"<br> → エラー(org.attoparser.ParseException: Attribute "value" is required in "input(radio)" tags)<br><br> <!-- <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> --> <!-- <span th:each="gender:${genderMap}"> --> <!-- <input type="radio" th:field="*{gender}" th:text="${gender.value}"><br> --> <!-- </span> --> <!-- <button>送信</button> --> <!-- </form><br> --> 【radio】th:field="*{gender}" th:value="${gender.key} th:text="${gender.value}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:field="*{gender}" th:value="${gender.key}" th:text="${gender.value}"><br> </span> <button>送信</button> </form><br> </div> ブラウザだと・・・ コンソールだと・・・ →th:fieldがあると、id,nameが入力されていますが、 別途valueを指定しないとエラーになる。また、textを指定しないとボタンの横に文字が現れません。 th:valueとth:textは別途指定する必要があります。 (checkboxとselectも同じ挙動のため割愛します。) まとめ th:fieldは以下図のように挙動していました。 ・検証に使用したコードは以下に格納しておりますので、よろしければこちらからご確認ください。 https://github.com/MasayukiFurumoto-rks/th-field-test 参考 以下記事を参考にさせていただきました。 ありがとうございました。 ・Thymeleafのth:fieldの意味とSpring Frameworkの値の受け渡しの仕組み (Qiita) ・th:field と th:object によるフォームバインディング機能(inputタグ・基本入力系編) (Hatena Blog) 所感 最後までご覧下さりありがとうございます。 今回、初めてQiita記事を投稿させていただきました。 マークダウン記法もままならないところからのスタートでしたが、アウトプットしていく中で理解が深まることもあり、ぜひまた投稿しようと思います。 もし訂正・加筆すべき箇所がございましたら、コメントにてご教示いただけますと幸いです。 何卒よろしくお願いいたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC 229 参加してみました。(C問題まで)

初めまして。 茶色コーダーを目指して毎週コンテストに参加しています。 まだまだ先は長いですが、解けた問題を投稿していきたいと思います。 使用言語はJavaです。 A問題 方針  "#"(黒マス)が3つ以上あれば、黒マスをどこに配置しても条件を満たします。 逆に黒マスが2つのみであれば、配置される位置によって判定が変わる。 具体的には黒マスが縦に並んでいるか、横に並んでいる場合は条件を満たす。 そうでない場合は条件を満たさないとします。 コード 標準入力と出力に関するコードは省略しています。 main.java import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.NoSuchElementException; public class Main { public static void main(String[] args) { FastScanner fs = new FastScanner(); PrintWriter out = new PrintWriter(System.out); String s1 = fs.next(); String s2 = fs.next(); int black = 0; for(int i = 0;i < 2;i++) { if(s1.charAt(i) == '#') { black++; } if(s2.charAt(i) == '#') { black++; } } if(black >= 3) { out.println("Yes"); }else if(s1.charAt(0) == '#' && s1.charAt(1) == '#') { out.println("Yes"); }else if(s1.charAt(0) == '#' && s2.charAt(0) == '#') { out.println("Yes"); }else if(s1.charAt(1) == '#' && s2.charAt(1) == '#') { out.println("Yes"); }else if(s2.charAt(0) == '#' && s2.charAt(1) == '#') { out.println("Yes"); }else { out.println("No"); } out.flush(); fs.close(); } } 分岐が複雑になってしまったので、もう少しスッキリさせるべきだったのが反省点です… B問題 https://atcoder.jp/contests/abc229/tasks/abc229_b 標準入力と出力に関するコードは省略しています。 方針  整数A,Bは共に1 ≦ A,B ≦ 10^18で、long型でも受け取ることができないので文字列で受け取ります。文字列として受け取ったA,Bに関して末尾から(一の位に相当)一桁ずつ探索して和が10以上であればHardとして、そうでなければEasyとする。 コード Main.java import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.NoSuchElementException; public class Main { public static void main(String[] args) { FastScanner fs = new FastScanner(); PrintWriter out = new PrintWriter(System.out); String aString = fs.next(); String bString = fs.next(); boolean flag = false; for(int i = 1;i <= Math.min(aString.length(), bString.length());i++) { int intA = Character.getNumericValue(aString.charAt(aString.length() - i)); int intB = Character.getNumericValue(bString.charAt(bString.length() - i)); int sum = intA + intB; if(sum >= 10) { flag = true; break; } } if(flag) { out.println("Hard"); }else { out.println("Easy"); } out.flush(); fs.close(); } } C問題 https://atcoder.jp/contests/abc229/tasks/abc229_c 標準入力と出力に関するコードは省略しています。 方針 「可能なピザの美味しさの最大値を求める」ということなので、1gあたりの美味しさ(Ai)が大きい方から順番にWgに到達するまで、足し込んでいけば良いです。 コード Main.java import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Arrays; import java.util.Comparator; import java.util.NoSuchElementException; public class Main { public static void main(String[] args) { FastScanner fs = new FastScanner(); PrintWriter out = new PrintWriter(System.out); int n = fs.nextInt(); long w = fs.nextLong(); long[][] cheese = new long[n][2]; for(int i = 0;i < n;i++) { //美味しさ cheese[i][0] = fs.nextLong(); //重さ cheese[i][1] = fs.nextLong(); } //チーズの美味しさを昇順ソート Sort2DArrayBasedOnColumnNumber(cheese, 1); long total = 0; for(int i = n - 1;i >= 0;i--) { if(w >= cheese[i][1]) { total += cheese[i][0] * cheese[i][1]; w -= cheese[i][1]; }else { total += cheese[i][0] * w; break; } } out.println(total); out.flush(); fs.close(); } public static void Sort2DArrayBasedOnColumnNumber (long[][] array, final int columnNumber){ Arrays.sort(array, new Comparator<long[]>() { @Override public int compare(long[] first, long[] second) { if(first[columnNumber-1] > second[columnNumber-1]) return 1; else if(first[columnNumber - 1] == second[columnNumber - 1]) return 0; else return -1; } }); } } 結果 3完でした。D問題は分からず… 以上となります。 ありがとうございました。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コマンドでcheckstyleをかける

上からcheckstyleのjarをダウンロードする 循環複雑度をシュッとかけるためのxml <?xml version="1.0"?> <!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd"> <module name = "Checker"> <module name="TreeWalker"> <module name="CyclomaticComplexity"> <property name="max" value="7"/> </module> </module> </module> おもむろに実行してみる java -jar <PATH>/checkstyle-9.0.1-all.jar -c <上のXML>.xml -o checkstyle_result.txt **/*.java
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ分析基盤・DataLakeを作成するツールとしてのEmbulk

この記事はエイチーム引越し侍 / エイチームコネクトの社員による、Ateam Hikkoshi samurai Inc.× Ateam Connect Inc. Advent Calendar 2021 3日目の記事です。 はじめに 今日は、最近のお仕事で触ることになる可能性が高いEmbulkについて書きます。 触ることになるとあるようにEmbulkを使い始めるソフトウェアエンジニアによる記事です。説明に至らない点があったり間違っていたりするかもしれません。その際は編集リクエストなりコメントなりでご指摘いただけたら幸いです。 何に使うかですが、データ分析基盤、DataLake作成におけるETL処理のために使います。ちなみにETLはExtract、Transform、Loadの略でデータを抽出、変換・加工、ロード(Output)する処理のことです。 ETL処理ツールとしては過去Pentaho、Talendを少し触った経験があります。 RubyistなのでFluentdからのEmbulkは知っていたのですが、実際に触るのは初めてです。Embulk、勝手に中身Rubyかと思ってました。(メインがJavaで一部Rubyなんですね) Embulkとは Embulk(エンバルク)はbulk data loader です。はい、bulk load なのでデータを大量に且つ一括で登録するツールです。並列・分散実行も可能となっています。 Embulkの優れた点はプラグイン機構を導入していることによる対応フォーマット、対応データソースの多さです。 MySQLやBigQueryはもちろん、CSVといった物理ファイル、salesforceといったPaaSにも対応しています。 プラグイン一覧はこちら↓ 導入方法 導入方法については上記記載の公式URLにあるので割愛します。 実際に運用する際はFargateなりECSでDockerイメージを使うことになるかと思います。 使い方 公式にembulk exampleというサンプル用コマンドが用意されているのでそれを叩いてみます。 $ embulk example ./try1 2021-12-03 02:39:19.977 +0900: Embulk v0.9.23 Creating ./try1 directory... Creating ./try1/ Creating ./try1/csv/ Creating ./try1/csv/sample_01.csv.gz Creating ./try1/seed.yml Run following subcommands to try embulk: 1. embulk guess ./try1/seed.yml -o config.yml 2. embulk preview config.yml 3. embulk run config.yml exampleコマンドを実行すると上記の通り~/try1というディレクトリが作成されその配下にサンプル用ファイルが格納されます。 ディレクトリ内の構成は以下でした。 $ tree try1/ try1/ ├── csv │   └── sample_01.csv.gz └── seed.yml そして次にguessコマンドを実行するのですが、これがEmbulkの特徴の1つです。まずは実行してみます。 $ embulk guess ./try1/seed.yml -o config.yml 2021-12-03 02:39:36.109 +0900: Embulk v0.9.23 2021-12-03 02:39:37.255 +0900 [WARN] (main): DEPRECATION: JRuby org.jruby.embed.ScriptingContainer is directly injected. 2021-12-03 02:39:40.522 +0900 [INFO] (main): Gem's home and path are set by default: "/Users/h-1390/.embulk/lib/gems" 2021-12-03 02:39:41.612 +0900 [INFO] (main): Started Embulk v0.9.23 2021-12-03 02:39:41.932 +0900 [INFO] (0001:guess): Listing local files at directory '/Users/h-1390/./try1/csv' filtering filename by prefix 'sample_' 2021-12-03 02:39:41.934 +0900 [INFO] (0001:guess): "follow_symlinks" is set false. Note that symbolic links to directories are skipped. 2021-12-03 02:39:41.935 +0900 [INFO] (0001:guess): Loading files [/Users/h-1390/./try1/csv/sample_01.csv.gz] 2021-12-03 02:39:41.946 +0900 [INFO] (0001:guess): Try to read 32,768 bytes from input source 2021-12-03 02:39:42.026 +0900 [INFO] (0001:guess): Loaded plugin embulk (0.9.23) 2021-12-03 02:39:42.063 +0900 [INFO] (0001:guess): Loaded plugin embulk (0.9.23) 2021-12-03 02:39:42.109 +0900 [INFO] (0001:guess): Loaded plugin embulk (0.9.23) 2021-12-03 02:39:42.133 +0900 [INFO] (0001:guess): Loaded plugin embulk (0.9.23) in: type: file path_prefix: /Users/h-1390/./try1/csv/sample_ decoders: - {type: gzip} parser: charset: UTF-8 newline: LF type: csv delimiter: ',' quote: '"' escape: '"' null_string: 'NULL' trim_if_not_quoted: false skip_header_lines: 1 allow_extra_columns: false allow_optional_columns: false columns: - {name: id, type: long} - {name: account, type: long} - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'} - {name: purchase, type: timestamp, format: '%Y%m%d'} - {name: comment, type: string} out: {type: stdout} Created 'config.yml' file. 上記にあるようにconfig.ymlファイルが出力されました。引数として指定してあるseed.ymlは以下です。 seed.yml in: type: file path_prefix: '/Users/h-1390/./try1/csv/sample_' out: type: stdout in:配下に記載されているのがInputとして使用するデータ定義、out:配下に記載するのがOutputするデータ定義です。 上記の場合はInputは実ファイルで任意のディレクトリにプレフィックス指定された形で任意の数のファイルがある、とわかります。(pathでCSVと推測できますが)ファイル形式は指定されていません。 上記内容を引数として出力guessコマンドを実行して出力されたのが以下です。 config.yml in: type: file path_prefix: /Users/h-1390/./try1/csv/sample_ decoders: - {type: gzip} parser: charset: UTF-8 newline: LF type: csv delimiter: ',' quote: '"' escape: '"' null_string: 'NULL' trim_if_not_quoted: false skip_header_lines: 1 allow_extra_columns: false allow_optional_columns: false columns: - {name: id, type: long} - {name: account, type: long} - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'} - {name: purchase, type: timestamp, format: '%Y%m%d'} - {name: comment, type: string} out: {type: stdout} 出力されたファイルには、引数として与えた元ファイル内に記載されたCSVの構造・ヘッダー情報が出力されています。 つまり、指定されたファイル(郡)から形式、カラム数カラム名、型を推測し、引数として渡したymlへ追記した形で定義ファイルを作成してくれるのです。 こういう、「形式が決まってて頑張れば自動化できるけど手でやってもそんなにかからない(から結局自動化しないで毎回手でやっちゃうような)作業」を自動化してくれるのは非常にありがたく、感謝しかありません。 どうやって形式や型を推測しているのだろう…と気になり、csvという名称が含まれるディレクトリパス(これは推測材料にしてないと思う)や拡張子(これは判断材料にしているかも)を別名にしてコマンドを叩き直したりしましたが、ちゃんとCSVと推測されてました。おそらくDecorder,Parserを動かして成功するかどうかとかで判断しているんだと思います。 この出力された定義ファイルを引数としてbulk loadを実行します。定義ファイルはYml形式です。Yml形式だと何が嬉しいのか。 はい。Git管理ができますね。ここソフトウェアエンジニアとしては嬉しいポイントです。定義ファイルがバイナリだったりすると管理がなかなか大変だったりするので。 さて、previewコマンドではどのような出力(Output)になるのかを確認できます。 $ embulk preview config.yml 2021-12-03 02:42:49.234 +0900: Embulk v0.9.23 2021-12-03 02:42:50.382 +0900 [WARN] (main): DEPRECATION: JRuby org.jruby.embed.ScriptingContainer is directly injected. 2021-12-03 02:42:53.580 +0900 [INFO] (main): Gem's home and path are set by default: "/Users/h-1390/.embulk/lib/gems" 2021-12-03 02:42:54.539 +0900 [INFO] (main): Started Embulk v0.9.23 2021-12-03 02:42:54.694 +0900 [INFO] (0001:preview): Listing local files at directory '/Users/h-1390/./try1/csv' filtering filename by prefix 'sample_' 2021-12-03 02:42:54.695 +0900 [INFO] (0001:preview): "follow_symlinks" is set false. Note that symbolic links to directories are skipped. 2021-12-03 02:42:54.697 +0900 [INFO] (0001:preview): Loading files [/Users/h-1390/./try1/csv/sample_01.csv.gz] 2021-12-03 02:42:54.704 +0900 [INFO] (0001:preview): Try to read 32,768 bytes from input source +---------+--------------+-------------------------+-------------------------+----------------------------+ | id:long | account:long | time:timestamp | purchase:timestamp | comment:string | +---------+--------------+-------------------------+-------------------------+----------------------------+ | 1 | 32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | embulk | | 2 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby | | 3 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin | | 4 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | | +---------+--------------+-------------------------+-------------------------+----------------------------+ そして実行はrunです。サンプルではout: {type: stdout}なので標準出力にそのまま出力されます。 $ embulk run config.yml 2021-12-03 02:43:48.603 +0900: Embulk v0.9.23 2021-12-03 02:43:49.935 +0900 [WARN] (main): DEPRECATION: JRuby org.jruby.embed.ScriptingContainer is directly injected. 2021-12-03 02:43:53.109 +0900 [INFO] (main): Gem's home and path are set by default: "/Users/h-1390/.embulk/lib/gems" 2021-12-03 02:43:54.129 +0900 [INFO] (main): Started Embulk v0.9.23 2021-12-03 02:43:54.251 +0900 [INFO] (0001:transaction): Listing local files at directory '/Users/h-1390/./try1/csv' filtering filename by prefix 'sample_' 2021-12-03 02:43:54.253 +0900 [INFO] (0001:transaction): "follow_symlinks" is set false. Note that symbolic links to directories are skipped. 2021-12-03 02:43:54.254 +0900 [INFO] (0001:transaction): Loading files [/Users/h-1390/./try1/csv/sample_01.csv.gz] 2021-12-03 02:43:54.306 +0900 [INFO] (0001:transaction): Using local thread executor with max_threads=8 / output tasks 4 = input tasks 1 * 4 2021-12-03 02:43:54.318 +0900 [INFO] (0001:transaction): {done: 0 / 1, running: 0} 1,32864,2015-01-27 19:23:49,20150127,embulk 2,14824,2015-01-27 19:01:23,20150127,embulk jruby 3,27559,2015-01-28 02:20:02,20150128,Embulk "csv" parser plugin 4,11270,2015-01-29 11:54:36,20150129, 2021-12-03 02:43:54.426 +0900 [INFO] (0001:transaction): {done: 1 / 1, running: 0} 2021-12-03 02:43:54.433 +0900 [INFO] (main): Committed. 2021-12-03 02:43:54.433 +0900 [INFO] (main): Next config diff: {"in":{"last_path":"/Users/h-1390/./try1/csv/sample_01.csv.gz"},"out":{}} サンプルではシンプルなデータ出力でしたが、プラグインを導入することでデータの加工(ETL)も可能です。また、MySQL等SQLが使えるデータソースの場合はSQLでin:を記述できるため、SQLでデータセットのJOIN・加工しそれをOutput先に登録も可能です。 実際の運用時には最初に一度今あるデータをすべて移行して、その後は差分移行という形になると思いますが、もちろんそういった差分更新にも対応しています。 SQLで記述できる場合はシンプルにupdate_atの条件指定をしてN日前にしたり前回実行時のタイムスタンプを参照するようにしたり、実現方法は複数あります。 Embulkまとめ bulk data loader プラグイン方式で様々なデータソースに対応している 定義ファイル(Yml)を用いてI/Oを定義する 定義ファイルはInputから推測して生成可能 定義ファイルがYmlなのでバージョン管理(Git)が容易 おまけ: なぜEmbulkなのか データ分析基盤・DataLakeの構築の手段は複数存在し、サービスもいくつかあります。その中でなぜEmbulkなのか、について。 データ分析基盤を構築する上での採用事例が複数存在する点、プラグインによって独自のデータセットがあったとしても柔軟に対応できる、等複数ありますが、社内に有識者が複数いる点というのも強いです。 すでに運用実績がある場合、外部に情報として出てこない深い話や情報があるため、導入の障壁が下がります。 身近に気軽に質問できる有識者がいる(今はリモートメインなのでビデオ越しですが)という点は大きいです。 話はそれますが、逆に社内に導入事例が無くても、誰かにその技術知見やモチベーションがある場合その人が旗振り役となって導入するという流れももちろんあります。 今、我々が実現したいことを最も簡単に実現できる方法は何か。それは保守性があるか・マネージドな状態にできるか等をそれなりに考えた結果の技術選定です。 次回 Ateam Hikkoshi samurai Inc.× Ateam Connect Inc. Advent Calendar 2021 3日目の記事は以上です。 読んでくださった方、ありがとうございます。参考になれば幸いです。 明日は @yhorikawa です。ご期待ください!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む