MENU

【Spring Boot】Spring JPAともっと仲良くしたい【JPQL】

こんにちは、くまごろーです。
今、家計簿アプリの出入金管理の部分を作成しているんですが、これがめちゃくちゃややこしい・・・
特にDB関係がわけわからなさ過ぎて、Spring JPAのことを嫌いになりかけたんですが、JPQLの使い方が少しずつわかってきて楽しくなってきました。
現時点でわかったことをまとめます。
 
 
 
1 自動実装されるメソッドには限界がある・・・?
 
Spring JPAには、Repositoryインターフェースに、ルールに則った命名のメソッドを宣言すれば、クエリを自動的に生成してくれます。
(参考:【Spring Data JPA】自動実装されるメソッドの命名ルール - Qiita
今日のお昼までは、「これを駆使すればSQL使わなくても行けるんじゃね?」と思ってました。
でも、少し複雑なことをやろうとするともう厳しい。
 
<MoneyRecoreRepository.java

public interface MoneyRecordRepository extends JpaRepository<MoneyRecord, Long>{
  //ユーザー名で検索したり
  public List<MoneyRecord> findByUsername(String username);
  //レコードを削除したり
  public void deleteByRecordId(Long recordId);
  //ユーザー名で検索した上で日付順に並び変えたり
  public List<MoneyRecord> findByUsernameOrderByRecordDate(String username);
  //特定の期間のレコードだけを検索したり
  public List<MoneyRecord> findByRecordDateBetweenOrderByRecordDateAsc(LocalDate start, LocalDate end);	
}

 
これくらいの条件なら自動実装のメソッドでもいけましたが、例えば「ユーザー名で検索した記録を月毎に分けて、それを更に収入と支出に分けて月毎の合計金額を算出する」とかになると、ちょっと無理そうでした。(無理ですよね?もし可能だったら教えてほしい)
 
で、今まで避け続けていたJPQLに挑戦することにしました。
 
 
 
2 JPQLを使ってみる

JPQLとは、Java Persistence Query Language の略で、SQLのクエリと似たクエリ文を実行することでDBを操作する、JPAで使用できるクエリ言語らしい。
ここでは、作成途中のポートフォリオにJPQLを導入するに当たって追加した部分を載せていきます。
 
(1)DAOインターフェースを用意する
既に出入金用のRepositoryインターフェースを用意していましたが、自動実装のメソッドはそのまま使用したいので、別インターフェース・別クラスを用意することにしました。

<MoneyRecordDao.java

@Repository
public interface MoneyRecordDao <T> extends Serializable {
  //出入金をユーザーIDで検索
  public List<T> findByUsername(String username);
  //出入金をユーザーIDと期間で検索
  public List<T> find(String fstr, String a);
}

 
(2)DAOクラスを実装
(1)で作成したインターフェースを実装する。

<MoneyRecordDaoImpl.java

@Repository
public class MoneyRecordDaoImpl implements MoneyRecordDao<MoneyRecord> {
  private static final long serialVersionUID = 1L;
  private EntityManager em;
  public MoneyRecordDaoImpl() {
    super();
  }
  public MoneyRecordDaoImpl(EntityManager manager) {
    this();
    em = manager;
  }

  @SuppressWarnings("unchecked")
  @Override
  public List<MoneyRecord> findByUsername(String username){
    String qstr = "from MoneyRecord where username = :fstr";
    Query query = em.createQuery(qstr).setParameter("fstr", username);
    List<MoneyRecord> list = query.getResultList();
    em.close();
    return list;
  }
	
  @SuppressWarnings("unchecked")
  @Override
  public List<MoneyRecord> find(String fstr, String a){
    List<MoneyRecord> list = null;
    String qstr = "from MoneyRecord where username = ?1 and record_date like ?2 and category_id not like ?3";
    Query query = em.createQuery(qstr).setParameter(1, fstr)
                               .setParameter(2, a + "%")
			       .setParameter(3, 20 + "%");
    list = query.getResultList();
		
    return list;
  }
}

ポイントとしてはこんな感じ。 
 
〇 クエリを作成する

 public List<MoneyRecord> findByUsername(String username){
    //"from MoneyRecord"は、SQLの"select * from MoneyRecord"に相当する
    //":fstr"・・・":〇〇"という形式で書くと、パラメータ用の変数として使える
    String qstr = "from MoneyRecord where username = :fstr";
    //Queryクラスに、EntityManagerのcreateQueryメソッドでQueryを代入
    //その際に、setParameterメソッドで変数に値を設定
    Query query = em.createQuery(qstr).setParameter("fstr", username);

 
〇 結果を取得

//Queryクラスのgetメソッドで取得(結果の型に応じていくつかgetメソッドがあります)
List<MoneyRecord> list = query.getResultList();

 
〇 複数のパラメータを設定する場合
パラメータは、複数設定することもできる。

//"?1"というように、"?"の後に数字を指定する
 String qstr = "from MoneyRecord where username = ?1 and 
        record_date like ?2 and category_id not like ?3";
//setParameterメソッドでは、第一引数に番号を指定する
    Query query = em.createQuery(qstr).setParameter(1, fstr)
                            . setParameter(2, a + "%")
			    .setParameter(3, 20 + "%");

 
〇 @SuppressWarningsアノテーション
コンパイル時の警告を抑制する。"unchecked"を引数に指定することで、getResultListの戻り値のListが適正かチェックしないようにしている。
 

DAOクラスに実装したメソッドは、自動実装のものと同じように使えます。
今日はここまでしかできませんでした。ここからバリエーションをうまく膨らましていきたい。データの検索、集計はJavaでもできなくないけど、なるべく全部JPQLでできるようになりたいなあ。
 
 
 
3 参考
 
www.amazon.co.jp
hacknote.jp