サービス
「Spring Bootの基本的な処理の流れ」の単元で、クラス分類や主な役割を紹介しました。
これまでは理解しやすさを優先し、Controllerに処理を直接書く例を紹介してきましたが、実は一般的ではありません。
下記のように書いていた通りで、
Controller: リクエストを受け取り、戻ってきた結果をレスポンスに変換して返す
Service: 部分的な処理を行い結果を返す。Controllerからビジネスロジックを取り出し集中管理する役割。
Controllerはあくまで「指示出し役」で、Serviceが「具体的なデータアクセス(Repositoryとのやり取り)やデータ処理」を担うべきです。
今後は下記の流れで実装するようにしましょう。
「リクエスト → Controller → Service → Repository → Entity→View(レスポンス)」

Serviceの役割
ここで、Serviceの役割を掘り下げておきましょう。
・ビジネスロジックの実装
データの処理や計算、条件判定など、アプリケーションの主要なビジネスロジックを担います。
・リポジトリとのやり取り
データベースへのアクセスを担当するRepositoryクラスと連携し、データの取得や保存を行います。
ただし、データアクセス自体ではなく、データアクセスのために必要なデータの受け渡しや変数への格納といった外側の処理を担います。
・トランザクション管理
Service層は、ビジネスロジックが複数のリポジトリを操作する場合に、トランザクションの開始やコミットを管理する役割も持ちます。
参考サイト:
サービスクラスの定義や概要 | 株式会社一創
Serviceの使い方
それでは、実際にServiceを導入して使用法を確認していきましょう!
Serviceは、「カスタムリポジトリー」と同じく、インターフェースと実装クラスの2種を作るのが一般的です。
理由としては、テストのしやすさ・拡張性や柔軟性の向上・疎結合(実装の変更が外部に影響を与えにくい)などです。
または、小規模なプロジェクトや将来的な拡張性が見込まれない場合、迅速な開発が求められる場合には
インターフェースを作成せず、Serviceクラス自身にメソッドを直接定義することもあります。
ここでは簡単に後者を採用し、インターフェースを作成せずにServiceクラスを作成し直接処理を記載していく例を紹介します。
「Spring Data JPA」単元で使用したEmployeeControllerのうち、getAllメソッド対応するServiceクラスのメソッドと、新規にユニーク登録するメソッドを実装していきます。
【com.cmps.spring.service】パッケージを作成し、EmployeeServiceクラスを作成します。
【com/cmps/spring/service/EmployeeService.java】
package com.cmps.spring.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cmps.spring.entity.Employee;
import com.cmps.spring.repository.EmployeeRepository;
@Service
public class EmployeeService {
@Autowired
EmployeeRepository employeeRepository;
/**
* ユーザー全件を取得
*
* @return
*/
public List<Employee> findAll() {
List<Employee> employees = employeeRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
return employees;
}
/**
* 受け取ったidのデータが存在すれば更新、しなければ新規登録
*
* @param id int
* @param name String
* @return
*/
public Employee saveIfUnique(int id, String name) {
// idのEntityが存在するかチェック
boolean exists = employeeRepository.existsById(id);
Employee emp;
if(!exists) {
// Entityを作成
emp = new Employee();
} else {
// 既存であれば検索して取得
emp = employeeRepository.findById(id).get();
}
// 名前を格納して保存
emp.setName(name);
employeeRepository.save(emp);
return emp;
}
}
・@Serviceアノテーションは、@Controllerなどと同じく、このクラスがServiceクラスであることを表します。記述必須です。
・EmployeeRepositoryを呼び出すので、@Autowiredを付与して呼び出しています。
・public List<Employee> findAll() {Serviceクラスに定義したこのfindAll()メソッドは、引数はなく、戻り値をList<Employee>とし、repositoryにアクセスして受け取った結果を返す形になっています。ちなみに、Serviceクラス上のメソッド名や処理の内容はすべて自分で定義するものですので、決まった形式はありません。
・public Employee saveIfUnique(int id, String name) {
saveIfUniqueメソッドについては、受け取ったidのデータが存在すれば更新、存在しないidであれば新規登録する処理になっています。
少し行数の多い記述です。これをすべてControllerに記載するとControllerの記述量が膨れ上がってしまいますが、このようにServiceメソッドに分離することによってControllerを軽量化することができます。
【com/cmps/spring/controller/EmployeeController.java】に追記、メソッドを修正
import com.cmps.spring.service.EmployeeService;////追記
@RequestMapping("/emp")
@Controller
public class EmployeeController {
@Autowired
private EmployeeService employeeService;////追記
@GetMapping("/all")
public String getAll(Model model) {
List<Employee> employees = employeeService.findAll();////修正
model.addAttribute("employees", employees);
return "employee/list";
}
////以下、追記
/**
* ユニークな(重複を防いで)値を登録する
*
* @param model Model Model情報
* @return "redirect:/emp/all" String 一覧表示画面へリダイレクト
*/
@GetMapping("/save-unique")
public String saveIfUnique(Model model) {
// idのデータが存在すれば更新、しなければ新規登録
employeeService.saveIfUnique(8, "田中");
return "redirect:/emp/all";
}
}
Serviceクラスのメソッドを呼び出すには、EmployeeServiceをimportし、@Autowiredで紐づけするだけです。
Controller側に記述する処理は
①「Serviceのメソッドを呼び出す」と、②model.addAttributeでViewへ変数の引き渡し、③使用するViewの指定
の3つ程度の簡素な記述のみになるのが理想的な形です。
なお本来、Serviceクラスを導入している場合、ControllerからはRepositoryには直接アクセスする必要がなくなります。
CotrollerからはServiceを呼び出し、ServiceからはRepositoryを呼び出す、という関係性ですね。
参考サイト:
【初心者向け】3層アーキテクチャ in Spring Boot – Qiita
サービスクラスの定義や概要 | 株式会社一創
練習問題
問1:EmployeeControllerの残りのメソッドについても、Repositoryメソッドを呼び出す記述をServiceクラスに移植し、
EmployeeController上のEmployeeRepositoryに対する@Autowiredの記述をなくしてください。
