質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.36%

  • Java

    15088questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

SpringBootで特定の年月のデータを一覧表示したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 86

Ayaka1005

score 1

実行環境

Java :  version "1.8.0_172"
SpringBoot : spring-boot-gradle-plugin:2.0.5
MySQL :  Ver 8.0.12 for osx10.13 on x86_64 (Homebrew)

実現したいこと

特定の年月のデータをデータベースから取得し、表示させたいです。
RepositoryクラスでQueryを使ってデータを取得しようとしたのですが、下記の実装だとList型になっていないせいか、エラーになってアプリが実行できません。

Repository

@Repository
public interface IncomeOutgoRepository extends JpaRepository<IncomeOutgo, Integer> {
    @Query("select * from income_outgo  where DATE_FORMAT(date,'%Y-%m') = :month")
    List<IncomeOutgo> findByMonth(@Param("month") String month);
}

エラーメッセージは以下になります。

 : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'incomeOutgoController': Unsatisfied dependency expressed through field 'incomeOutgoService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'incomeOutgoService': Unsatisfied dependency expressed through field 'incomeOutgoRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'incomeOutgoRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.Collection income_outgo.IncomeOutgoRepository.findByMonth(java.lang.String)!

最適な実装方針ありましたら、ご教示頂けますと幸いです。
よろしくお願いいたします。

Serviceクラス、Controllerクラスの実装は以下の通りです。

Service

@Service
public class IncomeOutgoService {
    @Autowired
    private IncomeOutgoRepository incomeOutgoRepository;

    public List<IncomeOutgo> findByMonth(String month){
        return incomeOutgoRepository.findByMonth(month);
    }
}


Controller

@Controller
@RequestMapping("/")
public class IncomeOutgoController {
    @Autowired
    private IncomeOutgoService incomeOutgoService;

    @GetMapping("month")
    public String month(Model model){
     
     //今日の日付 
     Date today = new Date();

        // 年月の文字列(例:2018-12)
        SimpleDateFormat thisMonthPathFormat = new SimpleDateFormat("yyyy-MM");
        String thisMonthPath = thisMonthPathFormat.format(today);

        // 月別一覧表示
        List<IncomeOutgo> monthList = incomeOutgoService.findByMonth(thisMonthPath);
        model.addAttribute("monthList", monthList);
        return "/month";
    }
}

テーブルの中身はこちら

+----+-------------+------+------------+-----------+-------+
| id | category_id | cost | date       | memo      | type  |
+----+-------------+------+------------+-----------+-------+
|  1 |           1 | 1000 | 2018-12-07 | ランチ    | outgo |
|  2 |           1 |  500 | 2018-11-05 | お菓子    | outgo |
+----+-------------+------+------------+-----------+-------+
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • rubytomato

    2018/12/08 20:15

    差し支えなければ、Java, Spring Bootのバージョン、使用されているDBの種類とそのバージョンを質問文に明記してください。

    キャンセル

  • Ayaka1005

    2018/12/08 20:27

    java version "1.8.0_172"

    キャンセル

回答 1

checkベストアンサー

+1

IncomeOutgoRepositoryの@Queryにnative queryが記述されていることが原因のように思います。
下記に引用したJavaDocの通り、QueryアノテーションにはJPQLという(一見普通のsql文のようにみえますが)、独自の問い合わせ言語で記述する必要があります。

Defines the JPA query to be executed when the annotated method is called.

解決方法は2つあります。

1つ目は、native queryを使えるようにする方法です。
下記のようにQueryアノテーションにnativeQuery = trueを付けるだけで済むと思います。

@Query("select * from income_outgo where DATE_FORMAT(date,'%Y-%m') = :month", nativeQuery = true)
List<IncomeOutgo> findByMonth(@Param("month") String month);

2つ目は、JPQLに書き直す方法です。
JPQLで書き直すと次のようになります。DATE_FORMAT関数はMySQL固有の関数なのでJPQLからは利用できません。(正確には使えないこともないのですが面倒くさくなるのでこの例では使いませんでした)また、FROM句に指定するのはテーブル名ではなくJPAのエンティティ名になります。

@Query("SELECT iogo FROM IncomeOutgo iogo WHERE iogo.date >= :date1 AND iogo.date < :date2")
List<IncomeOutgo> findByMonth(@Param("date1") Date date1, @Param("date2") Date date2);

リポジトリが変わるので当然呼び出し元のサービスも修正が必要です。月初日の求め方はざっくりしたものなので参考程度にみてください。

public List<IncomeOutgo> findByMonth(Date today){
    Calendar date1 = createFirstDay(today);
    Calendar date2 = createFirstDay(today);
    date2.add(Calendar.MONTH, 1);

    return incomeOutgoRepository.findByMonth(date1, date2);
}

private Calendar createFirstDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.clear(Calendar.HOUR_OF_DAY);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);
    return cal;
}

コントローラー

@GetMapping("month")
public String month(Model model){

    //今日の日付
    Date today = new Date();

    // 月別一覧表示
    List<IncomeOutgo> monthList = incomeOutgoService.findByMonth(today);
    model.addAttribute("monthList", monthList);
    return "/month";
}

補足

冒頭に書いた"native query"ですが、これはデータベースがそのまま解釈できるいわゆる普通のsql文のことです。
データベースがサポートしている関数(DATE_FORMAT)なども使えます。
ただし、JPAで問い合わせを行うときはsql文ではなくJPQL (Java Persistence Query Language)という専用の問い合わせ言語を利用します。
JPQLの使い方、書き方についてはこの質問の範疇を超えるので別途お調べください。

native queryを使えるようにするのが手間がかからず簡単なように見えますが、2点ほど問題があるように思います。
1) 私の経験上ではJPAを使う場合、native queryはあまり使われないように思います。
上司の方かチームリーダーにnative queryを使ってもよいかご相談頂いた方がいいかもしれません。
2) このnative queryではパフォーマンス上の問題が発生するかもしれません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/11 12:57 編集

    丁寧なご回答ありがとうございます。
    解決策1, 2どちらの方法でも取得できること確認いたしました。

    解決策2に関しては、Serviceクラスのdate1, date2をDate型に変換する処理を追加することで実行できました。

    ```
    public List<IncomeOutgo> findByMonth(Date today){
    Calendar firstDay = createFirstDay(today);
    Date date1 = firstDay.getTime();
    // 1ヵ月プラスしてからDate型に変換
    firstDay.add(Calendar.MONTH, 1);
    Date date2 = firstDay.getTime();
    return incomeOutgoRepository.findByMonth(date1, date2);
    }
    ```

    > JPAで問い合わせを行うときはsql文ではなくJPQL (Java Persistence Query Language)という専用の問い合わせ言語を利用します。

    JavaやSpringBootに不慣れだったこともあり、JPQLについて認知しておりませんでした..。
    大変参考になりました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.36%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Java

    15088questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。