Spring DATA JPAでデータ検索
GWも終わり、夏が近づいてまいりましたね。 今回は、Spring Data JPAを使ってデータ検索してみました。 ちょこっとだけハマったところもあったので、個人的なメモとして残しておきます。
開発環境構築
Spring Bootの開発環境ですが、私の場合はマイEclipseにプラグインを入れました。 参考サイト
テーブル作成
Spring DATA JPAとSpring Bootを使用して、検索を行ってみます。 とりあえず検索用のデータがないといけないので、サンプルデータを作成します。 商品テーブルを作成してデータを適当に入れておきます。
CREATE TABLE goods_mst ( goods_id varchar(10) NOT NULL, goods_name varchar(256) NOT NULL, price decimal(9,0) NOT NULL, PRIMARY KEY (goods_id) );
※DBはMySQLを使用しました。
プロジェクト作成
Eclipseで新規Spring Starter Projectを作成します。 依存関係は次のものを選択しました。 私の環境だと、プロジェクト作成時にJREシステム・ライブラリーがダブっていて、エラーが発生してました。 build.gradleの4行目を削除したところ、ダブらなくなりました。
eclipse { classpath { containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER') containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8' } }
DB接続するため、src/main/resources以下のapplication.propertiesにDB設定を記述します。
spring.datasource.url=jdbc:mysql://localhost/databasename?useSSL=true spring.datasource.username=username spring.datasource.password=password spring.datasource.driverClassName=com.mysql.jdbc.Driver
propertiesファイルより、ymlファイルの方が個人的には見やすい感じがして好きなので、私はapplication.ymlに変更しました
spring: datasource: url: jdbc:mysql://localhost/databasename?useSSL=true username: username password: password driverClassName: com.mysql.jdbc.Driver
実装
ますは、検索処理を動かすための画面を作成します。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>商品検索</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <form method="post" name="form1"> <table> <tr><td>商品ID : <input type="text" class="form-control" id="goods_id" name="goodsId" th:value="${goodsId}"/></td></tr> <tr><td>商品名 : <input type="text" class="form-control" id="goods_name" name="goodsName" th:value="${goodsName}"/></td></tr> <tr> <td>価格帯 : <input type="text" class="form-control" id="price_from" name="priceFrom" th:value="${priceFrom}"/> ~ <input type="text" class="form-control" id="price_to" name="priceTo" th:value="${priceTo}"/></td> </tr> <tr><td><input type="submit" value="検索"/></td></tr> </table> </form> <div th:if="${resultSize > 0}"><label th:text="${resultSize}"></label>件</div> <table border="1" th:if="${resultSize > 0}"> <tr> <td>商品ID</td> <td>商品名</td> <td>価格</td> </tr> <tr th:each="data : ${result}"> <td th:text="${data.goodsId}"/> <td th:text="${data.goodsName}"/> <td th:text="${data.price}"/> </tr> </table> </body> </html>
Entityの中身は以下のようになります。
package jp.classmethod.entity; import java.math.BigDecimal; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @Entity @Table(name="goods_mst") public class Goods { @Id @Column(name="goods_id") @Getter @Setter private String goodsId; @Column(name="goods_name") @Getter @Setter private String goodsName; @NotNull @Getter @Setter private BigDecimal price; }
あとは、JPARepositoryを継承したinterfaceを作成すれば、これを使うだけで最低限の検索ができちゃいます。 interfaceを作るだけで検索できるのが簡単で素敵ですね。 ジェネリクスの第一引数には、Entity、第二引数にはEntityでIdアノテーションを付けたプロパティの型(主キーの型)を指定してあげます。
package jp.classmethod.repository; import jp.classmethod.entity.Goods; import org.springframework.data.jpa.repository.JpaRepository; public interface GoodsRepository extends JpaRepository<Goods, String> { }
画面を動かすための、ControllerとServiceを作成します。
package jp.classmethod.service; import java.math.BigDecimal; import java.util.List; import jp.classmethod.entity.Goods; import jp.classmethod.repository.GoodsRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class GoodsService { @Autowired GoodsRepository repository; public List<Goods> search(String goodsId, String goodsName, BigDecimal priceFrom, BigDecimal priceTo) { List<Goods> result = repository.findAll(); return result; } }
package jp.classmethod.controller; import java.math.BigDecimal; import java.util.List; import jp.classmethod.entity.Goods; import jp.classmethod.service.GoodsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @ComponentScan @Controller @RequestMapping("/goods") public class GoodsController { private static final String VIEW = "/goods"; @Autowired public GoodsService service; @RequestMapping(method = RequestMethod.GET) public String index() { return VIEW; } @RequestMapping(method = RequestMethod.POST) public ModelAndView login(ModelAndView mav , @RequestParam("goodsId") String goodsId, @RequestParam("goodsName") String goodsName , @RequestParam("priceFrom") BigDecimal priceFrom, @RequestParam("priceTo") BigDecimal priceTo) { mav.setViewName(VIEW); mav.addObject("goodsId", goodsId); mav.addObject("goodsName", goodsName); mav.addObject("priceFrom", priceFrom); mav.addObject("priceTo", priceTo); List<Goods> result = service.search(goodsId, goodsName, priceFrom, priceTo); mav.addObject("result", result); mav.addObject("resultSize", result.size()); return mav; } }
ここまで出来れば、アプリケーションを起動して、http://localhost:8080/goodsにアクセス、検索処理が動かせるはずです。
とはいえこのままだと、画面上の検索条件が活きていないので、検索条件に合わせて検索ができるようにします。 色々と実装方法がありますが、画面に入力された条件により、クエリを動的に生成してみましょう。
自分で新たなRepositoryインターフェースを作成して、その実装クラスも作ります。
package jp.classmethod.repository; import java.math.BigDecimal; import java.util.List; import jp.classmethod.entity.Goods; public interface GoodsRepositoryCustom { public List<Goods> search(String goodsId, String goodsName, BigDecimal priceFrom, BigDecimal priceTo); }
package jp.classmethod.repository.impl; import java.math.BigDecimal; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; import jp.classmethod.entity.Goods; import jp.classmethod.repository.GoodsRepositoryCustom; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class GoodsRepositoryCustomImpl implements GoodsRepositoryCustom { @Autowired EntityManager manager; @SuppressWarnings("unchecked") @Override public List<Goods> search(String goodsId, String goodsName, BigDecimal priceFrom, BigDecimal priceTo) { StringBuilder sql = new StringBuilder(); sql.append("SELECT g From Goods g WHERE "); boolean andFlg = false; boolean goodsIdFlg = false; boolean priceFromFlg = false; boolean priceToFlg = false; if (!"".equals(goodsId) && goodsId != null) { sql.append(" g.goodsId LIKE :goodsId "); goodsIdFlg = true; andFlg = true; } boolean goodsNameFlg = false; if (!"".equals(goodsName) && goodsName != null) { if (andFlg) sql.append(" AND "); sql.append("g.goodsName LIKE :goodsName "); goodsNameFlg = true; andFlg = true; } if (priceFrom != null) { if (andFlg) sql.append(" AND "); sql.append("g.price >= :priceFrom "); priceFromFlg = true; andFlg = true; } if (priceTo != null) { if (andFlg) sql.append(" AND "); sql.append("g.price <= :priceTo "); priceToFlg = true; andFlg = true; } Query query = manager.createQuery(sql.toString()); if (goodsIdFlg) query.setParameter("goodsId", "%" + goodsId + "%"); if (goodsNameFlg) query.setParameter("goodsName", "%" + goodsName + "%"); if (priceFromFlg) query.setParameter("priceFrom", priceFrom); if (priceToFlg) query.setParameter("priceTo", priceTo); return query.getResultList(); } }
個人的ハマりポイントなのですが、最初はこんな感じで書いてうまくいくと思ったのですが、駄目でした。
sql.append(" g.goodsId LIKE % :goodsId % ");
if (goodsNameFlg) query.setParameter("goodsName", goodsName);
あいまい検索する際の"%"はパラメータをセットするときにつけるように、気を付けるようにします。
あとは、新しく作ったRepositoryを使えるようにServiceの修正をします。 検索条件がすべて未入力だった場合は全件検索を行うようにしてあります。
package jp.classmethod.service; import java.math.BigDecimal; import java.util.List; import jp.classmethod.entity.Goods; import jp.classmethod.repository.GoodsRepository; import jp.classmethod.repository.GoodsRepositoryCustom; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class GoodsService { @Autowired GoodsRepository repository; @Autowired GoodsRepositoryCustom repositoryCustom; public List<Goods> search(String goodsId, String goodsName, BigDecimal priceFrom, BigDecimal priceTo) { List<Goods> result; if ("".equals(goodsId) && "".equals(goodsName) && priceFrom == null && priceTo == null) { result = repository.findAll(); } else { result = repositoryCustom.search(goodsId, goodsName, priceFrom, priceTo); } return result; } }
検索条件に合わせて検索ができることを確認します。
終わりに
今回は単純なテーブルの検索を行いました。 次は複数テーブルを結合したデータ取得や、主キーが複数あるテーブルなど使って試してみたいと思います。