20210308のJavaに関する記事は13件です。

Java Scannerを使う

Scannerについてまとめてみた

経緯

progate JavaⅢ の道場コースを進めている際にScannerというライブラリの存在を知った。
入力した値が意図せず改行されてしまったため、疑問に思いScannerについて調べてみたので、ついでに学んだことも含めてまとめます。

結論

改行に関してはこの理解で解決
⬇︎
System.out.print と System.out.println の違い
- System.out.println は最後に改行を付与
- System.out.println は先頭には改行を付与しない

初期化

Scanner scanner = Scanner(System.in);

Scannerではこの初期化したものを代入した変数を使ってメソッドを呼び出す。
scanner.next()とするとコンソールに入力された文字列を受け取ることができる。

Scanner クラスの主なメソッド

next() 文字列を取り出す
nextInt() 整数値を取り出す
nextDouble() 実数値を取り出す
nextLine() 次の改行文字の前までを文字列として取り出し、改行文字をスキップする
hasNext() 次の入力があるときは値 true を、ないときは false を返す
close() スキャナを閉じる(ファイルから入力するとき使う)

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

javaでファイル更新監視アプリを作成する

目的

javaでアプリを作りたいと思った。
とりあえず人からこんなものを作ったらと言われて作ってみた作品になります。
ということで今回作ったのは監視対象ファイルをCSVから読み込んでファイルサイズが変わったら
デスクトップのポップアップで通知してくれるデスクトップアプリです。

環境構築

windows8
Pleiades All in One photon

ソース

gitthubにて
https://github.com/mimicman/FileSurveillance/tree/main/src/jp/main

ソースの説明

MainUpdateCheker.java

MenuItemクラスでデスクトップのメニューに追加します。
メニューには開始、停止、再開などアプリを操作するメニューを追加しています。
Timerクラスで定期的に処理を実行しています。

CsvLoad.java

CSVを読み込んでCSVに記載されている監視対象ファイルを読み込みます。

Run.java

監視対象ファイルのサイズが変更されていないかを確認する処理を実施しています。

UpdateChekerFrame.java

監視対象ファイルを可視化するため、フレームにファイル名等を表示しています。

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

Spring+Thymeleafでのページング実装例(ページ番号を省略させるもの)

1 はじめに

Spring+Thymeleafでのページング処理方法を調べてみると、下記のようなページングの実装例は簡単に見つかる。
oldPaging.png
このページングの実装は、下記の記事を参考にさせていただいた。
Spring Boot + Thymeleafでページング機能を実装する

しかし、この実装の内容だとページ数が増えた場合に横にページ番号を増やし続けてしまう。
(自分でカスタマイズしろ!というお話なのだが・・・)

そこで、次のようなページ番号が増えた場合には「...」で省略するページングを実装してみたので共有する。
(JavaScriptを使用してのページングを実装する方法もあるそうなのだが、今回はThymeleafのみで実装した。)
newPaging1.png

2 実装例

Thymeleafのコーディング例はこちら

pagination.html
<ul class="center padding-l-0">
        <li th:class="inline">
            <span th:if="${page.first}">&lt;</span>
            <a th:if="${not page.first}" th:href="@{${url}(page=${page.number-1})}">&lt;</a>
        </li>
        <!--/* 全ページ数が9以下の時は、全ページ番号を表示 */-->
        <li th:if="${page.totalPages <= 9}" th:each='i : ${#numbers.sequence(0, page.totalPages-1)}' th:class="inline">
            <span th:if='${i}==${page.number}' th:text='${i+1}'></span>
            <a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
                <span th:text='${i+1}'></span>
            </a>
        </li>
        <!--/* 全ページが9以上の時 */-->
        <!--/* 1~5ページの時は後ろに「...」をつける */-->
        <li th:if="${page.totalPages > 9 && page.number < 5}" th:each='i : ${#numbers.sequence(0, 8)}' th:class="inline">
            <span th:if='${i}==${page.number}' th:text='${i+1}'></span>
            <a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
                <span th:text='${i+1}'></span>
            </a>
        </li>
        <li th:if="${page.totalPages > 9 && page.number < 5}" class="inline">
            <span th:text="..."></span>
        </li>
        <!--/* (6~TotalPage数-5)ページの時は前後に「...」をつける */-->
        <li th:if="${page.totalPages > 9 && 4 < page.number && page.number < page.totalPages - 5}" class="inline">
            <span th:text="..."></span>
        </li>
        <li th:if="${page.totalPages > 9 && 4 < page.number && page.number < page.totalPages - 5}" th:each='i : ${#numbers.sequence(page.number-4, page.number+4)}' th:class="inline">
            <span th:if='${i}==${page.number}' th:text='${i+1}'></span>
            <a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
                <span th:text='${i+1}'></span>
            </a>
        </li>
        <li th:if="${page.totalPages > 9 && 4 < page.number && page.number < page.totalPages - 5}" class="inline">
            <span th:text="..."></span>
        </li>
        <!--/* (TotalPage数-5)以上ページの時は前方に「...」をつける */-->
        <li th:if="${page.totalPages > 9 && page.number >= page.totalPages-5}" class="inline">
            <span th:text="..."></span>
        </li>
        <li th:if="${page.totalPages > 9 && page.number >= page.totalPages-5}" th:each='i : ${#numbers.sequence(page.totalPages-9, page.totalPages-1)}' th:class="inline">
            <span th:if='${i}==${page.number}' th:text='${i+1}'></span>
            <a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
                <span th:text='${i+1}'></span>
            </a>
        </li>
        <li th:class="inline">
            <span th:if="${page.last}">&gt;</span>
            <a th:if="${not page.last}" th:href="@{${url}(page=${page.number+1})}">&gt;</a>
        </li>
    </ul>

コード中のpageオブジェクトに検索結果が格納されている。
(pageオブジェクトに検索結果を渡す処理(ControllerやServiceなど)のコーディング例は参考情報として文末に記載しておく)

条件分岐について

全ページ数(page.totalPages)や現在閲覧しているページ番号(page.number)などを使用して、下記4パターンにif条件で分岐させている。
ここでは説明のために、全ページ数をN、現在閲覧しているページ番号をxと表記することにする。

パターン1(どのページ番号も省略しない)

oldPaging.png
このパターンはN<=9の時に発生する。

パターン2(後方に...がつく)

newPaging1.png
このパターンはN>9 かつ 1<=x<=5の時に発生する。

パターン3(前方、後方に...がつく)

スクリーンショット 2021-03-08 19.07.19.png
このパターンはN>9 かつ 6<=x<=N-5の時に発生する。

パターン4(前方に...がつく)

スクリーンショット 2021-03-08 19.07.38.png
このパターンはN>9 かつ N-5<xの時に発生する。


上記のような分岐になっている。
ここでは上記のような条件分岐をさせているために、ページ番号として表示されるのは最大9件までとなっており、現在閲覧しているページから最大前後4ページまでが表示されるようになっている。
(つまり、8ページ目を閲覧している時には4~12のページ番号が表示される)
なので、表示させるページ数を増やしたい場合は上記の条件分岐に手を加えていただければ実装可能となっている。

また、このような分岐以外にも条件分岐をさせている。
例えば、現在見ているページ(page.number)のページ番号を表示する時にはaタグではなくspanタグで表記するような条件を記載している。
また、同様に次ページ前ページボタン(<,>)についても現在見ているページ(page.number)が最初のページ(page.firstがtrue)もしくは最後のページ(page.lastがtrue)ならaタグではなくてspanタグで表記するような条件としている。

参考情報(Controller、Service、Repositoryの実装例)

Controller

ItemSearchController.java
@Controller
public class ItemSearchController {

    // アイテム検索サービス
    @Autowired
    private ItemSearchService itemSearchService;

    @RequestMapping(value = "/")
    public ModelAndView searchAllItem(ModelAndView modelAndView, Pageable pageable) {
        // 全アイテムの検索
        Page<Item> itemListPage = itemSearchService.searchAllItem(pageable);

        // htmlに値を渡す
        modelAndView.addObject("page", itemListPage);
        modelAndView.addObject("itemList", itemListPage.getContent());
        modelAndView.addObject("url", "/");

        // 遷移先:アイテム検索ページ
        modelAndView.setViewName("item_search");

        return modelAndView;
    }
}

Service

ItemSearchService.java
@Service
public class ItemSearchService {

    // アイテムリポジトリ
    @Autowired
    private ItemRepository itemRepository;

    /**
     * 全アイテム検索メソッド
     * @return 全アイテムリスト
     */
    public Page<Team> searchAllItem(Pageable pageable) {
        // 全チームを検索する
        return itemRepository.findAllItem(pageable);
    }
}

Repository

ItemRepository.java
@Repository
public interface ItemRepository extends JpaRepository<Item, Integer> {

    // アイテムを検索する
    @Query(value = "SELECT * FROM item", nativeQuery = true)
    public Page<Item> findAllItem(Pageable pageable);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング初心者が基礎基本を地道に身に着ける日記(Java_ABC189編)

※本稿はAtCoderProblemsのBeginnerContest,A~C問題をJavaを用いて地道に解いていく人間の観察日記です

目次

  • まえがき
  • AtCoder Beginner Contest 189に挑戦
    • A問題
    • B問題
    • C問題
  • 感想
  • お世話になったサイト(参考に使用)

まえがき

 前回の記事(ABC190)
~前回のあらすじ~
 前回は、bit全探索の条件分けの書き方を全く知らずに時間を浪費したe2vee。今回は1時間で解けるのか!?頑張れ~~!e2vee~~!!

AtCoder Begginer Contest 189に挑戦

A問題

189A.java
import java.util.*;

public class Main{
  public static void main(String args[]){

    Scanner sc = new Scanner(System.in);
    //区切りが「""」(一文字ずつ!)なので
    //ちなみに読み取るものが(String型)文字列じゃないと使えないヨ!
    //多分そうだったハズ..(;・∀・)
    sc.useDelimiter("");
    //読み取り
    String c1 = sc.next();
    String c2 = sc.next();
    String c3 = sc.next();

    //答え
    String ans = "Lost";

    //判定
    if(c1.equals(c2) && c2.equals(c3)){
      ans = "Won";
    }

    //出力
    System.out.println(ans);
  }
}

 .useDelimiter()を知った自分に死角など無い。次の料理を持ってきたまへ。

B問題

189B.java
import java.util.*;

public class Main{
  public static void main(String args[]){

    Scanner sc = new Scanner(System.in);

    //まずは飲んだ杯数を数える
    int n = sc.nextInt();

    //浮動小数点だと環境によっては違う解を出力する
    //なので、int型に揃えるために、xとpに100をかけてどうにかする
    int x = sc.nextInt();
    int alcr = 0;
    int ans = -1;

    for(int i=0; i<n; i++){
      int v = sc.nextInt();
      int p = sc.nextInt();

      alcr = alcr + v*p;

      if(100*x<alcr){
        ans = i+1;
        break;
      }
    }


    /*!浮動小数点数の演算は一般に誤差を含むため、ときたま間違う!

    //酔っぱらうライン
    //ちなみに自分はほろよい1缶で酔います(隙自語)
    double x = sc.nextDouble();

    //摂取したアルコール
    double alcr = 0;

    //解
    int ans = -1;

    for(int i=0; i<n; i++){
      //次はv:量とp:アルコール度数を調べる
      double v = sc.nextDouble();
      double p = sc.nextDouble();
      //摂取したアルコール
      alcr = alcr + v*(p/100);

      //確認用
      //System.out.println("x = "+x);
      //System.out.println("alcr = "+alcr);
      //System.out.println("ans = "+ans);

      //判定
      if(x<alcr){
        ans = i+1;
        break;
      }
    }

    */

    //出力
    System.out.println(ans);
  }
}

 いや絶対に合ってるのに~~!!!なんでぇ!?と最初は思っていました。

 浮動小数点数の演算は一般に誤差を含む

 あ~~^なんか聞いたことある~~~~^

 出来る限り少数の計算を避けるべきという、基礎を失念していたがために、30分以上も唸っていました。ノォオオオオ!

C問題

189C.java
import java.util.*;

public class Main{
  public static void main(String args[]){

    Scanner sc = new Scanner(System.in);

    //皿の枚数
    int n = sc.nextInt();

    //各皿に置かれてるみかんの個数
    //みかんの配列
    int[] orange = new int[n];
    for(int i=0; i<n; i++){
      orange[i] = sc.nextInt();
    }

    //また全探索..(;・∀・)
    //l~r間を順に大きくしながらxを更新する

    //答え
    int ans = 0;

    //探索開始!
    for(int l=0; l<n; l++){

      //まず" l "皿目の?を食べる(予定・基準
      int x = orange[l];

      //その後、" l+1~ "皿の?をどんどん食べる。
      for(int r=l; r<n; r++){
        //l皿とr皿に乗っている?の数を比較
        //小さい方に基準を合わせる
        //※小さい方の?の数はxに、その後1つずつorange[r]で比較されていく
        x = Math.min(orange[r], x);
        //その後、現在のansとx個を(r-l" +1 ")皿分の?を食べた数を比較
        ans = Math.max(ans, x*(r-l+1));
        //
      }
    }

    //出力
    System.out.println(ans);
  }
}

 おきまりのC問題。ぶっちゃけ今までのC問題の中で一番早く解けたけました。

 最初は「l~r間の最小の数をその都度数えて、?の数を数えて、最大を求めて~」という殆ど同じだけども、計算回数が多くて時間をより多くかけてしまうプログラムを書いちゃってました。

 求める値を上書きしていくやり方に慣れていない感満載で、ちょっと不安が残りましたね..。

感想

 今回は今までのABCと比べて簡単(?)でした! ただ、失念していたことも見つかり、まだまだだなぁと..。

 次も一時間で終われるように頑張ります!(今回は1時間くらい)

お世話になったサイト

 実例を用いて分かりやすく解説してくれています!今度から特に"割り算"するときは気を付けます..。

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

JAVA Convert short[] to byte[]

Method 1:

byte [] ShortToByte_ByteBuffer_Method(short [] input)
{
  int index;
  int iterations = input.length;

  ByteBuffer bb = ByteBuffer.allocate(input.length * 2);

  for(index = 0; index != iterations; ++index)
  {
    bb.putShort(input[index]);    
  }

  return bb.array();       
}

Method 2:

byte [] ShortToByte_Twiddle_Method(short [] input)
{
  int short_index, byte_index;
  int iterations = input.length;

  byte [] buffer = new byte[input.length * 2];

  short_index = byte_index = 0;

  for(/*NOP*/; short_index != iterations; /*NOP*/)
  {
    buffer[byte_index]     = (byte) (input[short_index] & 0x00FF); 
    buffer[byte_index + 1] = (byte) ((input[short_index] & 0xFF00) >> 8);

    ++short_index; byte_index += 2;
  }

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

tomcat8は始動に失敗しました

原因

1.アノテーションに/がなかった

@WebServlet(/LoginServlet);

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

Java の SimpleDateFormat は「Jun」でも「June」でも Parse してくれる

SimpleDateFormatで「MMM」を指定すると、月の書き方が短縮系でもそうでなくてもParseしてくれる。

コード

public static void main(String[] args) throws Exception {

    {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
        System.err.println(sdf.parse("01-June-2020"));// OK
    }
    {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
        System.err.println(sdf.parse("01-Jun-2020"));// OK
    }
    {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
        System.err.println(sdf.parse("01-September-2020"));// OK
    }
    {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
        System.err.println(sdf.parse("01-Sep-2020")); // OK
    }
    {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
        System.err.println(sdf.parse("01-Sept-2020")); // NG
    }

}


結果

Mon Jun 01 00:00:00 JST 2020
Mon Jun 01 00:00:00 JST 2020
Tue Sep 01 00:00:00 JST 2020
Tue Sep 01 00:00:00 JST 2020
Exception in thread "main" java.text.ParseException: Unparseable date: "01-Sept-2020"
    at java.text.DateFormat.parse(DateFormat.java:366)
    at hello.date.HelloSimpleDateFormat2.main(HelloSimpleDateFormat2.java:28)

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

SpringBootのExecutable JARでMyBatisのtypeAliasが解決できない件。

何についての話か

SpringBoot+MyBatisを利用したプロジェクトをEclipseやIntelliJで開発時に動かすと動くのに、実行可能JAR作ってコマンドラインで実行しようとすると

Exceptionログ(抜粋)
Caused by: org.apache.ibatis.type.TypeException: Could not resolve type alias 'hogehoge'.  Cause: java.lang.ClassNotFoundException: Cannot find class: hogehoge
    at org.apache.ibatis.type.TypeAliasRegistry.resolveAlias(TypeAliasRegistry.java:120)
    at org.apache.ibatis.builder.BaseBuilder.resolveAlias(BaseBuilder.java:149)
    at org.apache.ibatis.builder.BaseBuilder.resolveClass(BaseBuilder.java:116)

とかなることありませんか??

本記事はその解決策になります。

環境

  • SpringBoot 2.4.3
  • MyBatis 3.5.6
  • mybatis-spring-boot-starter 2.1.4
  • 開発環境IntelliJ IDEA Community(ごめんなさい) 2020.3.2
  • Kotlin 1.x(現時点で最新)
  • Java 11

では始めます。

原因

JARファイルにまとめてしまうとJARファイル内にあるファイルたちにアクセスするためには素で書くと

resource
   getClass().getResource("classpath:hogehoge")

などとしてアクセスする必要があります。
MyBatisでは仮想ファイルシステム(VFS)を経由することでJARファイルの中にアクセスするようになっているようです。

このVFSはJARごとに用意されているのですが、デフォルトではSpringBoot pluginで生成するJAR用のVFSがセットされていません
※用意はされている。

このため、IDE上で実行する=実FS上の実ファイルにアクセスできる、が、JARを実行する=SpringBoot仮想FS上のクラスファイルにアクセスできない(見つからない)、となってClassNotFoundExceptionが起きます。

解決策

SpringBoot+MyBatisでは、MyBatisの設定をbeanで行えます。
最近Kotlin覚えたのでKotlinで書きます。

MyBatisConfiguratorSample.kt
import org.apache.ibatis.session.AutoMappingBehavior
import org.apache.ibatis.session.SqlSessionFactory
import org.mybatis.spring.SqlSessionFactoryBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.transaction.TransactionManager
import javax.sql.DataSource

@Configuration
class MyBatisConfig {

    @Bean
    fun transactionManager(dataSource: DataSource): TransactionManager =
        DataSourceTransactionManager().apply {
            this.dataSource = dataSource
        }

    @Bean
    fun sqlSessionFactory(dataSource: DataSource): SqlSessionFactory {
        val config = org.apache.ibatis.session.Configuration().apply {
            isMapUnderscoreToCamelCase = true
            autoMappingBehavior = AutoMappingBehavior.FULL
            typeAliasRegistry.registerAliases("package.to.entities")
        }

        val sessionFactory = SqlSessionFactoryBean().apply {
            setDataSource(dataSource)
            setConfiguration(config)

            val resolver = PathMatchingResourcePatternResolver()
            setMapperLocations(* resolver.getResources("classpath:/sql/**/*.xml"))
        }

        return sessionFactory.getObject()!!
    }

}

テンプレです。

WARにパッケージングしたり、IDE上で動かしたり、build/binディレクトリごと持っていくなら上で問題ないですが、JARで実行するよ!となると、

MyBatisConfigSample修正版.kt
import org.apache.ibatis.session.AutoMappingBehavior
import org.apache.ibatis.session.SqlSessionFactory
import org.mybatis.spring.SqlSessionFactoryBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.transaction.TransactionManager
import javax.sql.DataSource
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS

@Configuration
class MyBatisConfig {

    @Bean
    fun transactionManager(dataSource: DataSource): TransactionManager =
        DataSourceTransactionManager().apply {
            this.dataSource = dataSource
        }

    @Bean
    fun sqlSessionFactory(dataSource: DataSource): SqlSessionFactory {
        val config = org.apache.ibatis.session.Configuration().apply {
            isMapUnderscoreToCamelCase = true
            autoMappingBehavior = AutoMappingBehavior.FULL
            typeAliasRegistry.registerAliases("package.to.entities")
        }

        val sessionFactory = SqlSessionFactoryBean().apply {
            vfs = SpringBootVFS::class.java // <- これが要る!!
            setDataSource(dataSource)
            setConfiguration(config)

            val resolver = PathMatchingResourcePatternResolver()
            setMapperLocations(* resolver.getResources("classpath:/sql/**/*.xml"))
        }

        return sessionFactory.getObject()!!
    }

}

とすると動くよ!っていう記事がたくさんあるのですが 私は動かなかったです。
以前はこれだけで動いた気がするのだけどなぁ。

ちなみに、上のコードでは、SqlSessionFactoryBeanでclasspath以下のSQLのXMLファイルを参照しようとしているときにVFSを経由しているので、
SqlSessonFactoryにVFSはSpringBootVFSだよ!って教えてあげている1行です。

解決策(最終版)

ソース追ってたら、どうやらTypeAliasRegistry#registerAliasesでパッケージの展開ができてない模様。
ん?これはもしや・・・と思い、もうちょっとソース追ってみたところ、

org.apache.ibatis.io.ResolverUtil.java
  public ResolverUtil<T> find(Test test, String packageName) {
    String path = getPackagePath(packageName);

    try {
      List<String> children = VFS.getInstance().list(path); // <- ここ!!
      for (String child : children) {
        if (child.endsWith(".class")) {
          addIfMatching(test, child);
        }
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }

うおい!!
5行目でVFSクラス直接参照しているのですね、、、こっちは。。

ということで、ソースコードをこんな感じに修正。

MyBatisConfigSample最終版.kt
import org.apache.ibatis.session.AutoMappingBehavior
import org.apache.ibatis.session.SqlSessionFactory
import org.mybatis.spring.SqlSessionFactoryBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.transaction.TransactionManager
import javax.sql.DataSource
import org.apache.ibatis.io.VFS
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS

@Configuration
class MyBatisConfig {

    @Bean
    fun transactionManager(dataSource: DataSource): TransactionManager =
        DataSourceTransactionManager().apply {
            this.dataSource = dataSource
        }

    @Bean
    fun sqlSessionFactory(dataSource: DataSource): SqlSessionFactory {
        val config = org.apache.ibatis.session.Configuration().apply {
            isMapUnderscoreToCamelCase = true
            autoMappingBehavior = AutoMappingBehavior.FULL

            VFS.addImplClass(SpringBoot::class.java) // <= これも要る!!
            typeAliasRegistry.registerAliases("package.to.entities")
        }

        val sessionFactory = SqlSessionFactoryBean().apply {
            vfs = SpringBootVFS::class.java // <- これが要る!!
            setDataSource(dataSource)
            setConfiguration(config)

            val resolver = PathMatchingResourcePatternResolver()
            setMapperLocations(* resolver.getResources("classpath:/sql/**/*.xml"))
        }

        return sessionFactory.getObject()!!
    }

}

てな感じで、SqlSessionFactoryだけじゃなく、TypeAliasRegistryから呼ばれるResolverUtilにもVFSを教えてあげる必要がありました。

これで、IDE上でも実行可能JARででも両方とも動きました!

お悩みの方々、ご参考になれば幸いです。

おまけにJavaのコード

上のコードはKotlinのapplyとか使ってますが、Javaで書くとこんな感じです。

MyBatisConfigSampleJava.java
package com.axiohelix.minpaku.diplas.config;

import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
public class MyBatisConfig {

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);

        return transactionManager;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setVfs(SpringBootVFS.class);

        org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
        config.setMapUnderscoreToCamelCase(true);
        config.setAutoMappingBehavior(AutoMappingBehavior.FULL);
        VFS.addImplClass(SpringBootVFS.class);
        config.getTypeAliasRegistry().registerAliases("package.to.entities");
        sessionFactory.setConfiguration(config);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath:/sql/**/*.xml"));

        return sessionFactory.getObject();
    }

}

つぶやき

ClassNotFoundExceptionの対応策一生懸命探してたんですがなかなかなくてソースを見る羽目になりました。
ただ、ちょっと趣深いなーと思ったのが、同様のエラーが発生している人に対して、

registerAliasesでパッケージ指定せずに、1つずつクラス指定すればいいんだよ

つまり...
typeAliasRegistry.registerAlias(packages.to.entity.Entity1::class.java)
typeAliasRegistry.registerAlias(packages.to.entity.Entity2::class.java)
typeAliasRegistry.registerAlias(packages.to.entity.Entity3::class.java)
typeAliasRegistry.registerAlias(packages.to.entity.Entity4::class.java)

とすればエラー出ないよ!

という回答でした。
趣深い。

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

【Java】SpringBoot勉強してみた

はじめに

こんにちは。
少し前にはなりますが、とある企業のセミナーに参加した際に、SpringBootというフレームワークを使っていると知り、気になったので勉強してみました。
復習がてらに、備忘録として記事に残しておこうと思います:sunny:

今回は概念的な内容になります。

Spring Frameworkってなんぞや

SpringBootを紹介する前に、SpringFrameworkについて説明しておきます:runner_tone1:

◆ SpringFrameworkについて

  • Javaの世界では老舗とも言えるフレームワーク
  • DI(依存性注入)を実現するためのもの
  • DIをベースに様々な機能を実装して「 統合フレームワーク 」に成長

◆ Spring Frameworkの「DI」とは?

  • Dependency Injection(依存性注入)
  • オブジェクト間の依存関係を、オブジェクト内のコードに記述せず、
    実行時に外部から呼び出す手法

DIについて.png

◆ 多数のフレームワークから構成

SpringFrameworkは多数のフレームワークの集合体です。中でも有名なものとして、SpringMVC、SpringRoo、SpringBootなどがあります。

  • SpringMVC:Model-View-Controllerアーキテクチャーによる開発

MVC.png

  • SpringRoo:Spring MVCを補完するライブラリとして登場
    • コードの自動生成機能ライブラリ
    • 簡単なコマンドを実行するだけで、アプリケーションの枠組みが自動的にできあがるらしい(Railsみたいなイメージ)
  • SpringBoot

◆ SpringBoot誕生

先ほど説明したSpringMVCやSpringRooはどちらも便利そうで良さげに思えますが、問題点もいくつかあります。SpringMVCは、多くのSpring系のライブラリを正確に組み合わせる必要があり、環境構築も大変。SpringRooもある程度は自動化できましたが、やっぱりまだまだ大変、、

そこで登場したのがSpringBootです!:dizzy:
SpringBootは、最良のWebアプリケーション開発を素早く構築するスターターキット的なライブラリです。Spring Boot = 「Spring MVCの完成形」と言えます :tada:

【SpringBootの特徴】

  • 専用のスターターライブラリを設定ファイルに記述するだけで、 一通りのライブラリの読み込み、環境構築が可能
  • データベースアクセスなどの機能をほとんど自動化

Spring Bootのメリット

Javaの世界でも「Railsライク」とも言われる、効率的で高速開発ができるフレームワークがたくさん出来てきました。 それでも、「 Spring Boot 」を選ぶ理由は何なのでしょうか?

ここではSpringBootのメリットを5つ紹介します:raised_hands_tone2:

① 面倒なXML設定ファイルの記述がない

従来のJava EE開発はXMLによる設定ファイルだらけで、入力が面倒であったり、エラーの特定が困難である等、問題も多々ありました。

SpringBootでは 以下のような事でXML設定ファイルの記述を少なくしています。

  • Auto-configuration: 各種設定を必要最低限の記述のみで自動設定
  • 不足する部分はJavaConfigを追加
  • アノテーションによる開発

② アノテーション(注釈)記述による コーディング量の削減

アノテーションとは、"@ Xxx" の形式でコンパイラや実行環境にJava の言語機能としては表現できない事や補足などを伝えるものです。
例:@ before(メソッド実行前に呼び出す)

アノテーションは大きく3つの種類に分けることが出来ます。

マーカーアノテーション:何らかの用途でマークする
単一アノテーション:データーを持つ事ができる
フルアノテーション:複数のデータを持つ事ができる

③ サーバー内蔵方式

一般的なものは、アプリケーションとは別にApache ,Tomcat等のサーバーを準備し、 開発したアプリを追加して動かします。
これに対して、SPringBootにはJavaサーバーが内蔵されており、 直接サーバーを起動して実行します。

④ DIをベースとする一貫した実装

DIをベースとしているため、スッキリしていて分かりやすいのもSpringBootの特徴です:writing_hand_tone2:

  • 数多くのライブラリがあっても、基本的な設計思想は一貫している
  • 新しいライブラリが追加されても覚え直す必要なし
  • テスト時のインスタンスの差し替え可能

⑤ AOP( アスペクト指向プログラミング )

AOPとは、プログラミングを別の視点、側面から考えていこうよ!っていうプログラミング手法です。

オブジェクト指向の時、オブジェクト単位に処理を上手く分解できない...どうしよう...
複数のオブジェクトで同じ操作が散在してしまう.....えらいこっちゃ...
こんな悩みを抱える事があります ( この状態を「横断的関心事」って言うらしいです)

そんな時はAOP!!
アスペクトというモジュールに分離します。AOPによって、オブジェクト指向ではカバーできない部分をカバーする事が出来ます :fist_tone2:

⑥ STS ( Spring Tool Suite)

一般的なフレームワーク「本体は提供するから、あとはそれぞれで自由に使ってくれ」 っていうスタンスですが、Spring Bootはフレームワーク本体に加えて専用開発ツール(STS)も提供しています。

【STSの特徴】

  • ベースは「 Eclipse 」で、Spring Framework利用のためのプラグインが追加されている
  • 必要な処理が自動化され、コードの作成のみに注力できる
  • ここまで環境整備を行っているフレームワークはSpring Framework以外にはほぼ無い(らしい)

:warning: Spring Bootは「 十分条件 」ではない

フレームワークが提供するのは「プレゼンテーション層」の部分です。MVCにおける、View(画面)、Controller(全体の制御)の部分のみを提供します。つまり、ただ「仕組みがある」というだけで、別の技術も必要になってくるのです...

例えば、データベース関連ではSpring Data JPAやSpring Data MongoDBが必要ですし、画面実装にはテンプレートエンジン( Thymeleaf )というモノも使われます。

おわりに

ここまで、SpringBootについて紹介してきました。まとめます :writing_hand_tone2:

  • SpringBootでは、主にSpring Frameworkのフレームワークを組み合わせて開発
  • 複雑な設定処理の記述が必要ない
  • SpringBootを使えば、従来よりも簡単に素早く Webアプリケーションを開発できる

最後まで読んでいただきありがとうございます :bow_tone1:
間違っている点などございましたら、ご指摘いただけると嬉しいです!

参考文献

今回は主に「Spring Boot2 プログラミング入門」を参考にまとめさせていただきました。初心者にも分かりやすく説明してくれているので、興味のある方は是非:bow_tone1:

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

JavaからMySQLのデータベースにアクセルする時に発生する例外の対処法

JavaでMySQLのデータベースにアクセスしようとしたら拒否されて例外が発生してアクセスできなかったので、色々と調べていました。

ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

どうやらrootユーザが設定されていなくてこのようなエラーが発生することがあるようです。

このエラーが出た時はまずMySQLを停止してから、オプション付きで起動することによって解決しました。

1、まずは一旦停止します。

# service mysqld stop

2、次にMySQLを–skip-grant-tablesオプション付きで起動します。
これはMySQLの権限システムを使用しないで起動するためのものだそう。

# mysqld_safe --skip-grant-tables &

僕の場合はこれで解決しました。

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

React+springbootお試し実装参考サイトリンク

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

【Java】メソッド情報から引数名を取得する方法

概要

Javaのクラス情報(java.lang.Class)に含まれるメソッド情報(java.lang.reflect.Method)からは、メソッドで定義した引数の名前が取得できます。
今回はその引数名の取得方法を紹介します。

条件など

  • こちらのStackOverflowの記事にある通り、Java8以降が前提となります。Java8以降ではMethodからParameterクラスが取得可能なので、ParameterクラスのgetName()を使用することで引数名が取得できます。
  • Javaのコンパイルオプションで、classファイルにパラメータ情報を含める必要があります。この設定を行わないと引数名を取得してもarg0、arg1のような形になります。コンパイルオプションにどういう指定をすれば良いか、リフレクションで引数名を取得するときはjavacに-parametersオプションをつけるにて紹介されています。なお、Intellijでの設定方法はこちらのStackOverflowの記事を参照ください。

実装サンプル

大した内容ではないのですが、MethodクラスからParameterを取得し、それを出力する実装サンプルものせておきます。

public static void outputMethodParamName(Method m) {
    Arrays.stream(m.getParameters()).forEach(p -> {
        System.out.println(p.getName());
    });
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gradle(Spring boot)で作ったWeb APIをHerokuにデプロイしてMySQL接続してダンプをリストアするまで

理由

制作中のAndroidアプリでWeb APIをたたくときにいちいち起動するのが面倒くさい
せっかくなので作ったWeb APIをデプロイしてみたい
初歩的なところでつまずいてしまったので同じ過ちを繰り返さないための備忘録です

ゴール

  • Gradle(Spring boot)で作ったWeb APIをherokuにデプロイする
  • MySQL接続をする
  • 自作したAndoroidアプリからたたける

herokuとは

インフラストラクチャ管理が不要で、アプリケーションの開発から実行、運用までのすべてをクラウドで完結できるクラウドベースの PaaS(サービスとしてのプラットフォーム)
(公式より)
https://jp.heroku.com/home

やったこと

環境

  • macOS Big Sur 11.2.1
  • Spring boot 2.4.3
  • MySQL 5.7

0. 事前準備

  • MySQL接続するWeb APIをGradleで作る
  • 作ったアプリケーションをgit管理している
  • herokuアカウントを持っている
  • インポートしたいダンプを用意している

1. herokuとアプリの準備

1.1 heroku CLI(Command Line Interface)をインストールする

↑からインストーラをダウンロードしてもいいですし、Homebrewが入っている人はターミナルで↓のコマンドを叩いてもいいです。

terminal
$ brew install heroku/brew/heroku

ちょっぴり時間がかかるのでコーヒーでも飲んで待ちましょう。

1.2 ターミナルでherokuにログインする

terminal
$ heroku login
heroku: Press any key to open up the browser to login or q to exit
(略)
Logged in as {メールアドレス}

最後にLogged in as {メールアドレス}で自分のherokuアカウントに登録しているメールアドレスが表示されたら成功です。

2. アプリをデプロイする

2.1 ターミナルでアプリの場所に移動する

初心者のつまずき1: gitで1つのリポジトリにGradleプロジェクトとDockerとAndroidアプリをまとめちゃってるんだけどこの場合はどうすれば?

新たにリポジトリを作ってGradleプロジェクト単体だけ移動しました。
適解かはわからないけど、スターターガイドのサンプルプロジェクトはbuild.gradleがroot階層にある状態になっていたのでそれに倣いました。

そして新しく作ったリポジトリに移動します。

terminal
$ cd path/to/repository

2.2 herokuでアプリを作成する

terminal
$ heroku create {アプリ名}
Creating *** ...done
https://***.herokuapp.com/ | https://git.heroku.com/***

{アプリ名}は省略可能)

2.3 コードをデプロイする

terminal
$ git push heroku master

できませんでした。

初心者のつまずき2: heroku側とのJDKバージョンのミスマッチ
terminal
remote:        > Task :compileJava FAILED
remote:
remote:        FAILURE: Build failed with an exception.
remote:
remote:        * What went wrong:
remote:        Execution failed for task ':compileJava'.
remote:        > Could not target platform: 'Java SE 11' using tool chain: 'JDK 8 (1.8)'.

Java 8でコンパイルしようとしている。。。
公式のサンプルプロジェクトに参考になりそうなファイルがあったので真似してみました。

system.properties
java.runtime.version=11

このsystem.propertiesというファイルを新たに作って親階層に置きます。コミットを忘れずに。
そして再度プッシュ。

成功!!

terminal
...
remote:
remote:        BUILD SUCCESSFUL in 54s
remote:        4 actionable tasks: 4 executed
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote:        Done: 92.3M
remote: -----> Launching...
remote:        Released v3
remote:        https://***.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.

2.4 起動しているか確認してみる

terminal
$ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free

もしくは

terminal
$ heroku open

でアプリ名で生成されたURLにあるアプリが開きます。が、まだデータベース接続の設定をしていないのでエラー画面が表示されます。

3. MySQLと接続する

DB接続をします。herokuはデフォルトではpostgresQLらしいですが今回はMySQLを使います。

ClearDB MySQLJawsDB MySQLというアドオンがあり、どちらも5MBまでは無料で使えます。

ただ、注意が必要なのがClearDB MySQLはバージョンがデフォルトで5.6で、かつ無料プランでは変えられないそうなので、それ以外を使っている人はJawsDBを使うしかなさそうです。ちなみにJawsDB MySQLのデフォルトは5.7とのこと。(2021年3月時点)

私は5.7を使っていたのでJawsDB MySQLを使用します。

3.1 DB作成

terminal
$ heroku addons:create jawsdb:kitefin --name=[your DB name] --version=5.7

--name=[your DB name]オプションでデータベースの名前を任意に変えられるらしい。

重要
それよりも大事なのが--version=5.7オプション。後述しますが、最初これをつけなかったせいでダンプのインポートに失敗しました。というか知らずにClearDB MySQLを入れていたので全然だめだった。
もしダンプをインポートしようと考えている場合は、エクスポートしたMySQLのバージョンと同じものを設定すべし。

3.2 接続に必要な情報を取得

一旦、configを見てみます。

terminal
$ heroku config
JAWSDB_URL: mysql://******

いましがた取得したCLEARDB_DATABASE_URLをよく見ると以下のようなフォーマットになっています。

mysql://[username]:[password]@[host]:[port]/[database name]

ただ今回はGradle用にJDBC_DATABASE_URLを使用します。が、あとでダンプをインポートするときに使うのでメモはしておきましょう。
公式の説明には

Java、Scala、Clojure、Gradle 用の公式 Heroku buildpack では、dyno の起動時に JDBC_DATABASE_URL​ 環境変数の作成を試みます。

とあるので、特に何もしなくても変数を作ってくれるみたいです。
試しに以下のコマンドで表示してみます。

terminal
$ heroku run echo \$JDBC_DATABASE_URL
jdbc:mysql://*******/*******?password=*******&reconnect=true&user=*******

ばっちり!
今回のGradleアプリケーションはapplication.ymlファイルでデータベースを設定していたので、それぞれ値を以下のように修正します。

application.yml
spring:
  datasource:
    url: ${JDBC_DATABASE_URL}
    username: ${JDBC_DATABASE_USERNAME}
    password: ${JDBC_DATABASE_PASSWORD}

3.3 確認

再度$ heroku openしてみます。
ルート直下に表示用のビューを用意している場合はそれが表示されるはずです。
私は用意していないので404 エラーですが。とはいえ問題なくデータベース接続はできていそうです。

3.4 ダンプをインポートする

いつものコマンドです。各パラメータには3.2 でメモした値を使います。

terminal
$ mysql -u[username] -p[password] [database name] --host=[host] < [path/to/your/dump]
初心者のつまずき3: MySQLのバージョンが違うのでダンプインポートに失敗する

(結局、根本原因は2択から絞れませんでした。)

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version

最初にClearDBでDBを作るときにバージョンを指定しなかったらこんなエラーがでました。
???と思ってバージョンを確認してみることに。
 
 

terminal
$ mysql -u[username] -p[password] [database name] --host=[host]
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 205064
Server version: 5.6.50-log MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

バージョンが5.6だった。というわけで一旦このDBは削除してバージョン指定して再度作り直しましたが、バージョン指定したはずなのにやっぱり5.6。どうやら無料プランでは変えられないらしい?なのでJawsDBに変えました。

 

初心者のつまずき4: 短時間に接続を繰り返しすぎて上限超える

うまくいかないなあと何度もダンプインポートのコマンドをたたきすぎたせいか、やりすぎって怒られてしまいました。1時間くらい放置してから再度試すことにします。

java.sql.SQLSyntaxErrorException: User '********' has exceeded the 'max_user_connections' resource (current value: 10)

 

初心者のつまずき5: MySQLのバージョン合わせてもダンプインポートに失敗する

1時間後、無事接続できるようにはなりましたが、なぜかまだ同じエラーが出ます。バージョン合わせたはずなのに。

ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'mysqldump: [Warning] Using a password on the command line interface can be insec' at line 1

ダンプファイルを開いて1行目を確認すると、エラー文言にも出ている警告文mysqldump: ~ be insecが書いてあるだけでした。
つまりインポートする処理には影響なし。
そして、ちょっとしたバージョンの差でもちょっとした書き方の違いでエラーになることがあるということを見かけたので、思い切ってエラーだと言われている1行目を削除することにしました。

するとうまくいきました!!!

結局バージョンの違いのせいだったのか1行目の警告文のせいだったのか、はたまたその両方だったのかは今となってはわかりませんが、似たような事象に陥っている人がいたら試してみてください。

4 APIをたたいてDBのデータを取得してみる

ようやくWebAPIとDBの接続が完了したのでAPIをたたいてみて動作確認をします。

成功!!!

このあとAndroidアプリでもちゃんとデータが取ってこれていることを確認できました。
めでたしめでたし。

おまけ

ちなみにお手軽にGET、POST、PUT、DELETEなどなどのAPIを叩くことのできるこのアプリケーション。めっちゃ便利です。
Advanced REST client - Chrome ウェブストア

参考

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