Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
SpringBootプログラミング入門_読書メモ

1.Spring開発のセットアップ

  • DIをベースとする一貫した実装
    • Springのライブラリ群は、SpringFrameworkの中心となっているDIをベースに設計されている
    • 新しいライブラリを追加するたびにその設計を一から覚え直す、ということはない
  • 幅広い利用範囲
    • SpringFrameWorkはWeb開発専用ではない
    • ここで覚える昨日のいくつかは、他の分野でも応用できる
  • Bootによる生成機能
    • ごく簡単なコマンドでアプリケーションの基本的な骨格を作れる
    • 非常に短いコードでWebアプリケーションの汎用的な処理を実装できる
  • 開発元が開発環境をセットで用意している
    • SpringToolSuite(STS)がEclipseベースで使えて便利
    • Pivotal to Server Developer Edition(tc Server)も同梱されている

2.Groovyによる超簡単アプリケーション開発

SpringBootを使った開発

SpringBootによる開発の特徴は以下の通り

  • 従来の「XMLによる設定ファイルだらけ」の開発から、「アノテーションによる、設定ファイルを使わない」開発へシフトする
  • コードをほとんど書かずに処理を実装する

SpringBootを利用するアプリケーションは、Groovy系とJava系に分かれる

  • Groovy・・・本格的な開発の前にプロトタイプをサッと作る場合に使う
  • Java・・・SpringBootの一般的なアプリケーション形態

コマンドラインでSpringBootを動かしたい場合は、「SpringBootCLI」が便利

Thymeleafを利用する

SpringBootでは、Thymeleafと呼ばれるテンプレートライブラリがよく使われる
Thymeleafの特徴は以下の通り

  • タグの中に「th:」という特殊な属性を用意する
  • 「${}」といった特殊な記号を使って値をはめ込む

こうすることで、HTMLのタグ構造に影響を与えずに内容を記述できるので便利

3.JavaによるSpringBoot開発の基本

SpringBootとSpringStarterプロジェクト

SpringBootはJavaサーバが内臓されているため、サーバへのデプロイが不要
サーバ内臓方式だとファイルサイズは肥大化するが、クラウドサービスとの相性が良い

Spring Strater Projectのpom.xml

STSでSpringStarterプロジェクトを作成すると、pomが自動生成される
parentの定義は以下の通り

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.3.0.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

dependenciesの定義は以下の通り

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>      
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

buildおよびpluginsの定義は以下の通り

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

RestControllerを利用する

STSでSpringStarterプロジェクトをつくると、デフォルトでMyBootApplication.javaが作成される

@SpringBootApplication
public class MyBootAppApplication {

  public static void main(String[] args) {
    SpringApplication.run(MyBootAppApplication.class, args);
  }
}

@SpringBootApplicationをつけると、SpringBootの起動時に呼び出されるクラスになる
アノテーションをつけたクラスをSpringApplication.runすると、アプリが起動する

MVCアーキテクチャについておさらい

  • Model・・・アプリケーションで使うデータを管理する
  • View・・・画面の表示を扱う
  • Controller・・・全体の処理の制御を行う

続いて、コントローラクラスを作成する

@RestController
public class HelloController {
  @RequestMapping("/")
  public String index(){
    return "Hello Spring-Boot World!";
  }
}

@RestControllerをつけることで、REST用のコントローラとして振る舞う
コントローラのメソッドに@RequestMappingをつけることで、ルーティングが設定できる
@RequestMappingををつけたメソッドは、「リクエストハンドラ」と呼ばれる
サンプルの場合、http://localhost:8080/にアクセスすると、indexメソッドが呼ばれる

RequestMapping("/{num}")
public String index(@PathVariable int num){
  int res = 0;
  for(int i = 1; i<= num; i++){
    res += 1;
  }
  return "total:" + res;
}

パス変数「{}」を使うことで、パス部分に渡された値を変数として取り出せる
指定したパスは、「@PathVariable」で引数として渡せる
RestControllerクラスは、returnされたインスタンスの内容をJSON形式に変換して出力する

ControllerによるWebページ作成

Controllerを使う

通常のWebページを作成する場合は、@Controllerを使用する
画面表示にはThymeleafを使いたいので、pomファイルに以下の依存関係を追加する

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

コントローラは下記の通り修正する

@Controller
public class HelloController {
  @RequestMapping("/")
  public String index(){
    return "index";
  }
}

Webページのテンプレートファイルをresource/templatesに作成する
Controllerクラスのリクエストハンドラでは、テンプレート名をreturnすると、その名前でテンプレートを検索してレンダリングする
このように動作させるには、

  • spring-boot-starter-thymeleafの依存関係が追加されている
  • @Controllerアノテーションを使用している

の二つの前提が必要

テンプレートに値を表示する

テンプレート側

<body>
  <h1>Hello page</h1>
  <p class="msg" th:text="${msg}"></p>
</body>

コントローラ側

@Controller
public class HelloController {
  @RequestMapping("/")
  public String index(Model model){
    model.addAttribute("msg", "メッセージ");
    return "index";
  }
}

Modelは、Webページで利用するデータを管理するためのクラス
model#addAttributeすることで、値をテンプレート側に渡せる

  • Model・・・テンプレートで利用するデータを管理する
  • ModelAndView・・・テンプレートで利用するデータと、ビューに関する情報を管理する

ModelAndViewを使って書き換えるとこんな感じ

@Controller
public class HelloController {
  @RequestMapping("/")
  public String index(ModelAndView mav){
    mav.addAttribute("msg", "メッセージ");
    mav.setViewName("index");
    return mav;
  }
}

フォームを利用する

テンプレートにフォームを追加する

<body>
  <h1>Hello page</h1>
  <p th:text="${msg}">please wait...</p>
  <form method="post" action="/">
    <input type="text" name="text1" th:value="${value}" />
    <input type="submit" value="Click" />
  </form>
</body>

GETとPOSTを受け取れるよう、コントローラを修正する

@Controller
public class HelloController {

  @RequestMapping(value="/", method=RequestMethod.GET)
  public ModelAndView index(ModelAndView mav){
    mav.setViewName("index");
    mav.addObject("msg", "お名前を書いて送信してください。");
    return mav;
  }

  @RequestMapping(value="/", method=RequestMethod.POST)
  public ModelAndView send(@RequestParam("text1")String str,
                           ModelAndView mav){
    mav.addObject("msg", "こんにちは、" + str + "さん!");
    mav.addObject("value", str);
    mav.setViewName("index");
    return mav;
  }
  
}

@RequestMappingにmethodを指定することで、GETとPOSTを指定できる
@RequestParamを使うことで、フォームから値を受け取れる

リダイレクトについて

  • フォワード・・・サーバ内部で別のページを読み込み表示する
    • アクセスするアドレスはそのまま
    • 表示内容だけは別ページ
  • リダイレクト・・・クライアント側に送られたあとで別のページに移動させる
    • アクセスするアドレスは遷移先のもの

    • 表示内容も遷移先のもの

      @RequestMapping("/other") public String other(){ return "redirect:/" }

      @RequestMapping("/other") public String home(){ return "forward:/" }

4.テンプレートエンジンを使いこなす

Thymeleafをマスターする

ThymeleafではOGNL式を書ける

ユーティリティオブジェクト

Thymeleafではよく使われるクラスを「#名前」という定数で呼び出せるようにしている
これをユーティリティオブジェクトと呼ぶ

  • strings
  • numbers
  • bools
  • dates
  • objects
  • arrays
  • lists

パラメータへのアクセス

クエリパラメータへは「param」変数を通してアクセスできる
例えば、${params.id}とすれば、id=hogeの形式で送られてきた値が受け取れれる

メッセージ式

プロジェクトにあらかじめ用意して置いたプロパティファイルから値を取り出し、表示する仕組み
main/resourcesフォルダに「message.properties」を用意することで、メッセージを取り出せる
プロパティファイルの例

content.title=message sample page.
content.message=this is sample message from propeties.

上記の通り用意したら、あとはテンプレートから#{ 値 }で呼び出せる

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <p th:text="#{content.message}"></p>
</body>

国際化する場合は、messages_ja.propertiesなどのファイルを用意する(通常のJavaのローカライズと同じルール)

リンク式とhref

リンクを指定する場合は、@{ アドレス }の書式で用意する
他の変数などと組み合わせてリンクのアドレスを用意したい場合に便利

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <p><a th:href="@{'/home/' + ${param.id[0]}}">link</a></p>
</body>

選択オブジェクトへの変数式

オブジェクトを指定し、選択されたオブジェクトから値を取り出すための専用の変数式が用意されている
${}の代わりに、*{}を使うことで実現可能

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <p th:text="${msg}">message.</p>
  <table th:object="${object}">
    <tr><th>ID</th><td th:text="*{id}"></td></tr>
    <tr><th>NAME</th><td th:text="*{name}"></td></tr>
    <tr><th>MAIL</th><td th:text="*{value}"></td></tr>
  </table>
</body>

リテラル置換

テキストの前後を「|」で囲むと、変数式を直接書き込んで文字列結合ができて便利

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <div th:object="${object}">
    <p th:text="|my name id *{name}. mail address is *{value}.|"></p>
  </div>
</body>

HTMLコードの出力

Thymeleafでは、変数式でテキストを出力する際、HTMLタグをエスケープする
th:utextを使うことで、エスケープを解除できる

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <p th:utext="${msg}">message.</p>
</body>

構文・インライン・レイアウト

条件式いろいろ

Thymeleafでは、条件式として以下の構文が使える

  • 三項演算子( 条件文 ? 値1 : 値2 )
  • th:if
  • th:unless
  • th:switch
  • th:each

プリプロセッシングについて

式の一部を事前に評価させ、その結果を元に変数式を実行する、ということができる(プリプロセッシング)

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <p th:text="|expressiom[ ${check} ]|"></p>
  <table th:object="${data.get(__${check}__)}">
    <tr><th>ID</th><td th:text="*{id}"></td></tr>
    <tr><th>NAME</th><td th:text="*{name}"></td></tr>
    <tr><th>MAIL</th><td th:text="*{value}"></td></tr>
  </table>
</body>

上記のように記載すると、まず${check}が評価され、それを受けて${data.get()}を評価するような処理が書ける

インライン処理について

HTMLタグとタグの間に直接Thymeleafの変数式を書き込むやり方
th:inlineをタグに記載するやり方と、変数式の前後に[[と]]をつけるやり方がある
テキストの場合は以下のように記載する

<body>
  <h1 th:text="#{content.title}">Hello page</h1>
  <table th:inline="text">
    <tr>
      <th>ID</th>
      <th>NAME</th>
      <th>MAIL</th>
    </tr>
    <tr th:each="obj : ${data}">
      <td>[[${obj.id}]]</td>
      <td>[[${obj.name}]]</td>
      <td>[[${obj.value}]]</td>
    </tr>
  </table>
<body>

インライン処理はテキストだけでなく、JavaScriptに対しても書ける
変数式を埋め込む場合は、[[]]をコメントアウトする形で記述する

<head>
  <script th:inline="javascript">
    function action(){
      var val = document.getElementById("text1").value;
      var res = parseInt(val * ((100 + /*[[${tax}]]*/) / 100))
      document.getElementById("msg").innerHTML = "include tax: " + res;
    }
  </script>
</head>

テンプレートフラグメント

複数のファイルを組み合わせてページを構成する場合、テンプレートフラグメントが使える
共通部分を別ファイルに切り出して使いたい場合などに便利
部品として用意するテンプレート側には、th:fragmentを指定する
テンプレートを組み込む側には、th:includeを指定する

その他のテンプレートエンジン

SpringBootでは、JSPの利用は推奨されていない

  • 実行可能JAR形式では動作せず、WARファイルとしてデプロイしなければいけないなど、制約があるため
  • すでにJSP自体がサーバサイドJavaで使われなくなりつつあるため

SpringBootでは、JSPをあえて使うこともできる
application.propertiesの設定値を書き換えることで対応可能

spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp

SpringBootで普通のプログラムを作る

spring-boot-starter-webを使うと、WEBアプリケーションとして動作する

  • pomファイルにspring-boot-starter-を指定する
  • 実行クラスにCommandLineRunnerインタフェースを実装する
    • 処理の実態はrunメソッドに記述する

      public class App implements CommandLineRunner { public static void main(String[]args ) { SpringApplication.run(App.class, args); }

      @Override public void run(String.. args){ // do something. } }

5.モデルとデータベース

SpringBootStarterDataJPAは、以下のライブラリ・フレームワークを統合し、データベースアクセスに関する機能を提供する

  • JavaTransactionAPI・・・JavaEEにトランザクション処理を提供する
  • SpringORM・・・SpringFrameworkに用意されているORMフレームワーク
  • SpringAspects/SpringAOP・・・アスペクト志向プログラミングの機能を提供する

以下のdependencyを追加することで使用できる

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

JPAを利用する場合、データベースのデータとなる部分はエンティティと呼ばれるクラスとして定義する
クラス定義の前に@Entityアノテーションを付けることで、エンティティとして振る舞う

@Getter
@Setter
@Entity
@Table(name = "mydata")
public class MyData {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column
  private long id;

  @Column(length = 50, nullable = false)
  private String name;

  @Column(length = 200, nullable = true)
  private String mail;

  @Column(nullable = true)
  private Integer age;

  @Column(nullable = true)
  private String memo;
  
}
  • @Entity・・・指定したクラスをエンティティにする
  • @Table・・・テーブル名を定義する
    • 省略した場合はクラス名がそのままテーブル名として扱われる
  • @Id・・・- 指定したフィールドをプライマリキーにする
  • @GeneratedValue・・・指定したフィールドの値を自動生成する
  • @Column・・・フィールドに割り当てられるカラムを指定する
    • 名称を省略した場合は、フィールド名がそのままカラム名として扱われる

データベースアクセスのための基本的な手段は「リポジトリ」が提供する
リポジトリは、汎用的なデータベースアクセスの処理を、自動的に生成して実装する
そのため、ほとんどコードを書くことなくデータベースアクセスが行えるようになる

@Repository
public interface MyDataRepository extends JpaRepository<MyData, Long> {

}

@Repositoryアノテーションを付与し、JpaRepositoryを継承することでリポジトリクラスになる
あとは、@Autowiredでインジェクションすることで利用可能

@Controller
public class HelloController {

  @Autowired
  MyDataRepository repository;

  @RequestMapping("/")
  public ModelAndView index(ModelAndView mav) {
    mav.setViewName("index");
    mav.addObject("msg", this is sample content.);
    Iterable<MyData> list = repository.findAll();
    mav.addObject("data", list);
    return mav;
  }
  
}

エンティティのCRUD

フォームでデータを保存する

フォームからデータを保存する実装のサンプル

<body>
  <table>
  <form method="post" action="/" th:object="${formModel}">
    <tr>
      <td><label for="name>名前</label></td>
      <td><input type="text" name="name" th:value="*{name}" /></td>
    </tr>
    <tr>
      <td><label for="name>年齢</label></td>
      <td><input type="text" name="age" th:value="*{age}" /></td>
    </tr>
    <tr>
      <td><label for="name>メール</label></td>
      <td><input type="text" name="mail" th:value="*{mail}" /></td>
    </tr>
    <tr>
      <td><label for="name>メモ</label></td>
      <td><textarea name="memo" th:value="*{memo}" cols="20" rows="5"></textarea></td>
    </tr>
  </form>
  </table>
  <hr/>
  <table>
    <tr>
      <th>ID</th>
      <th>名前</th>
    </tr>
    <tr th:each="obj : ${datalist}">
      <td>${obj.id}</td>
      <td>${obj.name}</td>
    </tr>
  <table>
</body>

コントローラクラスのサンプルは以下のような感じ

@Controller
public class HelloController {
  @Autowired
  MyDataRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  public ModelAndView index(@ModelAttribute("formModel") MyData mydata,
      ModelAndView mav) {
    mav.setViewName("index");
    mav.addObject("msg", "this is sample content.");
    Iterable<MyData> list = repository.findAll();
    mav.addObject("datalist", list);
  }

  @RequestMapping(value = "/", method = RequestMethod.POST)
  @Transactional(readonly = false)
  public ModelAndView form(@ModelAttribute("formModel") MyData mydata,
      ModelAndView mav){
    repository.saveAndFlush(mydata);
    return new ModelAndView("redirect:/");        
  }
}

「@ModelAttribute」アノテーションを付与することで、エンティティクラスのインスタンスを自動的に用意できる
データの永続化はJpaRepository#saveAndFlushで行える
トランザクション制御は@Transactionalアノテーションで行える
これをメソッドに付けることで、メソッド内で実行されるデータベースアクセスが一括して実行されるようになる
デフォルトでは、readOnlyのため、書き込みを行いたい場合はfalseに設定する

データの更新

IDでエンティティを検索して取り出す処理をリポジトリに追加する

@Repository
public interface MyDataRepository extends JpaRepository<MyData, Long> {
  public MyData findById(Long name):
}

リクエストハンドラをコントローラクラスに追加する
テンプレートのサンプルは省略(idをhiddenに持たせる点に注意)

@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public ModelAndView edit(@ModelAttribure MyData mydata,
    @PathVariable int id,
    ModelAndView mav) {
  mav.setViewName("edit");
  mav.addObject("title", "edit mydata.");
  MyData data = repository.findById((long)id);
  mav.addObject("formModel", data);
  return mav;
}

@RequestMapping(value = "/edit", method = RequestMethod.POST)
@Transactional(readOnly = false)
public ModelAndView update(@ModelAttribure MyData mydata,
    ModelAndView mav){
  repository.saveAndFlush(mydata);
  return new ModelAndView("redirect:/");
}

データの更新をする際にもJpaRepository#saveAndFlushを使う

データの削除

JpaRepository#deleteで削除が行える

@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public ModelAndView delete(@PathVariable int id,
    ModelAndView mav) {
  mav.setViewName("delete");
  mav.addObject("title", "delete mydata.");
  MyData data = repository.findById((long)id);
  mav.addObject("formModel", data);
  return mav;
}

@RequestMapping(value = "/delete", method = RequestMethod.POST)
@Transactional(readOnly = false)
public ModelAndView remove(@RequestParam long id,
    ModelAndView mav){
  repository.delete(id);
  return new ModelAndView("redirect:/");
}

リポジトリのメソッド自動生成について

JpaRepositoryには辞書によるコードの自動生成機能が組み込まれている
(例:findの命名がされたらエンティティを検索する、byIdの命名がされたらidをキーに検索する)
メソッド生成時のポイントは以下の通り

  • メソッド名はキャメルケースで書く
  • メソッド名の単語の並びに注意する
    • find[ByHOGE][そのほかの検索条件][OrderBy など]
  • 引数の型が一致しているか確認する

単純な検索は自動生成メソッドで、複雑な検索はDAOを定義して実装する

エンティティのバリデーション

@RequestMapping(value = "/", RequestMethod.POST)
@Tramsactional(readOnly = false)
public ModelAndView form(
    @ModelAttribute("formModel") @Validated MyData mydata,
    BindingResult result,
    ModelAndView mav){
  ModelAndView res = null;
  if(!result.hasErrors()) {
    repository.saveAndFlush(mydata);
    res = new ModelAndView("redirect:/");
  } else {
    mov.setViewName("index");
    mov.addObject("msg", "sorry, error is occured...");
    Iterable<MyData> list = repository.findAll();
    mov.addObject("datalist", list);
    res = mov;
  }
  return res;
}

@Vilidatedアノテーションを付けると、そのエンティティに対してバリデーションが有効になる
バリデーションの結果はBindingResultに格納される
エラーが発生しているかどうかは、BindingResult#hasErrorsで調べられる
テンプレート側の実装サンプルは以下の通り

<li th:each="error : ${#fields.detailedErrors}" class="err" th:text="${error.message}">

エラーメッセージは、#fieldsオブジェクトに格納される
発生したエラー情報は、detailedErrorsメソッドから取り出せる
エラーメッセージ自体は${error.message}から取り出せる

<input type="text" name="name" th:value="*{name}" th:errorclass="err" />

inputタグにth:errorclassを指定することで、エラー発生時に適用するクラスを指定できる
エラーが発生したテキストボックスの色を変えたい場合などに使う

バリデーションを行うためのアノテーション

javax.validationパッケージのライブラリ

  • @Null/@NotNull
  • @Min/@Max
  • @DecimalMin/@DecimalMax
  • @Digits
  • @Future/@Past
  • @Size
  • @Pattern

Hibernate Validatorによるアノテーション

  • @NotEmpty
  • @Length
  • @Range
  • @Email
  • @CreditCardNumber
  • @EAN

エラーメッセージについて

バリデーションで表示されるメッセージは、基本的に英語
message属性をバリデータに指定するか、プロパティファイルを用意することで変更できる
resourceフォルダに「ValidationMessages.properties」という名前でテキストファイルを作成することで対応できる

オリジナルのバリデータを作成する

バリデータを自作するには、

  • アノテーションクラス
  • バリデータクラス

の二つが必要となる
アノテーションは、@interfaceの後に名前を記述して作成する

@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
public @interface Phone {
  String message() default "Please input a phone number.";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() dedault {};
}

バリデータクラスはConstraintValidatorインタフェースを実装して作成する

public class PhoneValidator implements ConstraintValidator<Phone, String> {

  @Override
  public void initialize(Phone phone){
  }

  @Override
  public boolean isValid(String input, ConstraintValidatorContext cxt){
    if(input == null){
      return false;
    }
    return input.matches("[0-9()-]*");
  }

}

6.データベースアクセスを掘り下げる

EntityManagerによるデータベースアクセス

EntityManagerは、エンティティを利用するために必要な機能を提供する

@Repository
public class MyDataDaoImpl implements MyDataDao<MyData> {
  private static final long serialVersionUID = 1L;

  private EntityManager entityManager;

  public MyDataDaoImpl(){
    super();
  }

  public MyDataDaoImpl(EntityManager manager;){
    entityManager = manager;
  }

  @Override
  public List<MyData> getAll(){
    Query query = entityManager.createQuery("from MyData");
    List<MyData> list = query.getResultList();
    entityManager.close();
    return list;
  }
}

JPAには、JPQLと呼ばれるクエリ言語が搭載されている
JPQLによるクエリは、Queryクラスのインスタンスに格納される
作成されたQueryは、getResultListメソッドを通じて、実行結果を取り出せる

public class HelloController {
  @Autowired
  MyDataRepository repository;

  @PersistenceContext
  EntityManager entityManager;

  MyDataDaoImpl dao;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  public ModelAndView index() {
    mav.setViewName("index");
    mav.addObject("msg", "MyDataのサンプルです。");
    Iterable<MyData> list = dao.getAll();
    mav.addObject("datalist", list);
    return mav;
  }
}

@PersistenceContextアノテーションは、EntityManagerのbeanを取得してフィールドに設定する
EntityManagerは、SpringBootの場合、起動時に自動敵機にBeanとしてインスタンスが登録されている
これを@PersistenceContextによりフィールドに割り当てている
@PersistenceContextを使ったBeanのバインドは、1クラスにつき1インスタンスまでしか置けないので注意

JPQLを活用する

JPQLは、SQLのクエリと似たクエリ文を実行することで、データベースを操作するための簡易言語
これを利用することでデータベースを柔軟に操作できる

クエリアノテーション

クエリをあらかじめ用意しておくことのできる機能
@NamedQueryで名前付きクエリを作成することで、クエリを切り離して管理しやすくなる

@NamedQuery(
  name = "findWithName",
  query = "from MyData where name like :fname"
)

インタフェース側に上記のように定義しておくと、

@Override
public List<MyData> find(String fstr) {
  List<MyData> list = null;
  Long id = 0L;
  try{
    fid = Long.parseLong(fstr);
  } catch(NumberFormatException e) {
    e.printStackTrace();
  }
  Query query = entityManager
      .createNamedQuery("findWithName")
      .setParameter("fname", "%" + fstr + "%");
  list = query.getResultList();
  return list;
}

このように、名前だけでクエリを呼び出せて便利

リポジトリと@Query

@Queryをリポジトリのメソッドに付けることで、メソッド呼び出し時にクエリを呼び出せる

@Repository
public interface MyDataRepository extends JpaRepository<MyData, Long> {
  @Query("SELECT d FROM MyData d ORDER BY d.name")
  List<MyData> findAllOrderByName();
}

Criteria APIによる検索

JPAのCriteria APIを使うことで、よりJavaらしいデータベースアクセスが行えるようになる
主に使うのは下記の三つのクラス

  • CriteriaBuilder・・・クエリ生成を管理するクラス

  • CriteriaQuery・・・クエリ実行のためのクラス

  • Root・・・操作されるエンティティのルートとなるクラス

    @Override public List getAll(){ List list = null;

     CriteriaBuilder builder = entityManager.getCriteriaBuilder();
     CriteriaQuery<MyData> query = builder.createQuery(MyData.class);
     Root<MyData> root = query.from(MyData.class);
     query.select(root);
    
     list = (List<MYData>)entityManager
         .createQuery(query)
         .getResultList();
     return list;
    

    }

CriteriaBuilderメソッドいろいろ

  • where
    • equal
    • notEqual
    • greaterThan
    • greaterThanOrEqual
    • lessThan
    • lessThanOrEqualTo
    • between
    • isNull
    • isNotNull
    • isEmpty
    • isNotEmpty
    • like
    • and
    • or
    • not
  • orderBy
    • asc
    • desc

ページング

エンティティの取得位置と取得個数を指定したい場合には、Queryインスタンス内のメソッドを利用する

@Override
public List<MyData> getAll(){
  int offset = 1;
  int limit = 2;
  List<MyData> list = null;

  CriteriaBuilder list = entityManager.getCriteriaBuilder();
  CriteriaQuery<MyData> query = builder.createQuery(MyData.class);
  Root<MyData> root = query.from(MyData.class);
  query.select(root);

  list = (List<MyData>)entityManager
      .createQuery(query)
      .setFirstResult(offset)
      .setMaxResults(limit);
      .getResultList();
  return list;
}

エンティティの連携

エンティティ間の連携を表現する際には「アソシエーション」機能を使う
Javaのクラスにプロパティとして別のエンティティを持たせ、アノテーションを付与することで実装できる
アノテーションは以下の四種類が用意されている

  • OneToOne・・・1対1で対応する連携を表す
  • OneToMany・・・1対多で対応する連携を表す
  • ManyToOne・・・多対1で対応する連携を表す
  • ManyToMany・・・多対多で対応する連携を表す

関連付け元のエンティティ

@Entity
@Table(name = "msgdata")
@Getter
@Setter
public class MsgData {
  @Id
  @Column
  @NotNull
  private long id;

  @Column
  private String title;

  @Column(nullable = false)
  @NotEmpty
  private String message;

  @ManyToOne
  private MyData mydata;
}

関連付け先のエンティティ

@Entity
@Table(name = "mydata")
@Getter
@Setter
public class MyData {
  @OneToMany(cascade = CascadeType.ALL)
  @Column(nullable = true)
  private List<MsgData> msgdatas;
}

あとは、それぞれに対応するDAOを作成して、テンプレートを用意する

<body>
  <table>
    <tr>
      <th>ID</th>
      <th>名前</th>
      <th>タイトル</th>
    </tr>
    <tr th:each="obj : ${datalist}">
      <td th:text="${obj.id}"></td>
      <td th:text="${obj.mydata.name}"></td>
      <td th:text="${obj.title}"></td>
    </tr>
  </table>
</body>

リポジトリのsaveでは、保存をする際、他のエンティティに関連付けられたフィールドがある場合は、送信された情報を元にインスタンスを取得して自動的に設定する
そのため、関連付いたレコードの登録処理をプログラマが実装する必要はなくなる

7.SpringBootを更に活用する

サービスとコンポーネント

ビジネスロジックの中で、アプリケーションから利用できるようにコンポーネント化された部分は、一般に「サービス層」と呼ぶ
@Serviceアノテーションを付与することで、クラスをサービスとして登録できる

@Service
public class MyDataService {
  @PersistenceContext
  private EntityManager entityManager;

  public List<MyData> getAll(){
    return (List<MyData>) entityManager
        .createQuery("from MyData")
        .getResultList();
  }      
}

作成したサービスは@Autowiredで呼び出せる

@Controller
public class HelloController {
  @Autowired
  MyDataRepository repository;

  @Autowired
  private MyDataService service;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  public ModelAndView index(ModelAndView mav) {
    mav.setViewNmae("index");
    mav.addObject("title", "Find Page");
    List<MyData> list = service.getAll();
    mav.addObject("datalist", list);
    return mav;
  }
}

XMLでデータを取得するには

Jackson DataFormat XMLを使うのが便利

<dependency>
  <groupId>com.faterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

ライブラリを追加し、XMLで結果を取得したいエンティティに@XmlRootElementアノテーションを追加する
すると、RestControllerにアクセスしてreturnされたオブジェクトは、XML形式のテキストに変わる

コンポーネントとBean

コンポーネントは、アプリケーション内に自動生成されるBean
作成されるBeanのインスタンスは、@Autowiredでバインドし、利用できるようになる
@Componentを付与することで、クラスをコンポーネントとして登録できる

覚えておきたいその他の機能

設定クラスとBeanの利用

SpringBootでは、初期設定をXMLではなくクラスに持たせている

@Configuration
public class MyBootAppConfig {
  @Bean
  MyDataBean myDataBean(){
    return new MyDataBean();
  }
}

@Configrationアノテーションを付与することで、設定クラスとして扱われる
設定クラス内に@Beanをつけて、クラスのインスタンスを返すメソッドを用意すると、Beanとして登録できる(@Autowiredで呼び出せる)

ページネーション

ページごとに分けてデータを扱う機能をページネーションと呼ぶ

private static final int PAGE_SIZE = 3;

public Page<MyData> getMyDataInPage(Integer pageNumber) {
  PageRequest pageRequest = new PageRequest(pageNumber - 1, PAGE_SIZE);
  return repository.findAll(pageRequest);
}

ページ番号とページサイズを引数に指定して、PageRequestのインスタンスを生成する
生成したインスタンスをリポジトリクラスに渡すと、ページ分けした結果が返される

Thymeleafのユーティリティオブジェクト

ユーティリティオブジェクトとは、Thymeleafの中に埋め込んで利用でき特別なオブジェクト
Javaクラスとして定義し、Thymeleaf内からその内部のメソッドを呼び出して結果を出力させることができる
ユーティリティオブジェクトを使うには、ユーティリティオブジェクト本体のクラスと、Dialectクラスを用意する必要がある

public class MyTLUtility {
  public String hello(String name){
    return "Hello, <b>" + name + "!</b>";
  }

  public String prevUrl(int num){
    return "/page/" + (num > 1 ? num - 1 : 1);
  }

  public String nextUrl(int num){
    return "/page/" + (num + 1);
  }
}

ユーティリティオブジェクトは、出力するテキストをreturnする形で作成する

public class MyTLDialect extends AbstractDialect implements IExpressionEnhancingDialect {
  private static final Map<String, Object> EXPRESSION_OBJECTS;

  static {
    Map<String, Object> objects = new HashMap<String, Object>();
    objects.put("myTLHelper", new MyTLUtility());
    EXPRESSION_OBJECTS = Collections.unmodifiableMap(objects);
  }

  public MyTLDialect() {
    super();
  }

  @Override
  public Map<String, Object> getAdditionalExpressionObjects(IProcessingContext processingContext) {
    return EXPRESSION_OBJECTS;
  }

  @Override
  public String getPrefix() {
    return null;
  }
}

Dialectクラスは、AbstractDialectのサブクラスとして作成する
また、IExpressionEnhancingDialectインタフェースを実装する
ここで、MyTLHelperという名前でユーティリティを詰めることで、Thymeleafから呼び出し可能になる

<table>
  <tr>
    <th>ID</th>
    <th>名前</th>
    <th>メール</th>
    <th>年齢</th>
    <th>メモ(tel)</th>
  </tr>
  <tr th:each="obj : ${datalist}">
    <td th:text="${obj.id}"></td>
    <td th:text="${obj.name}"></td>
    <td th:text="${obj.mail}"></td>
    <td th:text="${obj.age}"></td>
    <td th:text="${obj.memo}"></td>
  </tr>
  <tr>
    <td colspan="5">
      <table class="navi">
        <tr>
          <td><div style="text-align: left;"><a th:href="${#myTLHelper.prevUrl(pagenumber)}">← Prev</a></div></td>
          <td><div style="text-align: center;">page</div></td>
          <td><div style="text-align: right;"><a th:href="${#myTLHelper.nextUrl(pagenumber)}">Next→</a></div></td>
        </tr>
      </table>
    </td>
  </tr>
</table>

MongoDBの利用

SQLを使わず、シンプルにデータの読み書きが行えるようにしたデータベースとして、NoSQLというデータベースが利用されるようになってきた
MongoDBは、「ドキュメント志向データベース」と呼ばれる
データをテーブルとレコードではなく、巨大なテキストファイルとして扱う MongoDB用のリポジトリは、MongoRepositoryインタフェースを継承することで作成できる

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment