これまではコントローラクラスに主要な処理を記載していたが、MVCモデルに従うと、C(コントローラ)とM(モデル)で明確に分割した方が望ましい。
MVCモデルについては、以下を参照のこと。
https://qiita.com/s_emoto/items/975cc38a3e0de462966a
そこで、今回は、以前作成したサンプルプログラムを修正し、M(モデル)の役割をもつサービスクラスに、主要な処理を移動してみたので、そのソースコードを共有する。
前提条件
下記サイトのソースコード実装が完了していること。
完成した画面イメージ
下記サイトの「完成した画面イメージの共有」を参照のこと。
作成したサンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。「前提条件」で記載したソースコードと異なるプログラムを赤枠で囲っている。

「DemoService.java」は以下の通りで、インタフェースを定義し、そこに主要なメソッドが記載されている。
package com.example.demo;
import org.springframework.validation.BindingResult;
import java.util.List;
public interface DemoService {
/**
* ユーザーデータリストを取得
* @return ユーザーデータリスト
*/
List<DemoForm> demoFormList();
/**
* 引数のIDに対応するユーザーデータを取得
* @param id ID
* @return ユーザーデータ
*/
DemoForm findById(String id);
/**
* 引数のIDに対応するユーザーデータを削除
* @param id ID
*/
void deleteById(String id);
/**
* 引数のユーザーデータがあれば更新し、無ければ追加
* @param demoForm フォームオブジェクト
*/
void createOrUpdate(DemoForm demoForm);
/**
* フォームオブジェクトのチェック処理を行い、画面遷移先を返す
* @param demoForm フォームオブジェクト
* @param result バインド結果
* @param normalPath 正常時の画面遷移先
* @return 画面遷移先
*/
String checkForm(DemoForm demoForm, BindingResult result, String normalPath);
}
「DemoServiceImpl.java」は以下の通りで、「@Service」アノテーションを付与しインスタンス化できるようにした上で、インタフェースに定義したメソッドの実装が記載されている。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Service
public class DemoServiceImpl implements DemoService{
/**
* ユーザーデータテーブル(user_data)へアクセスするマッパー
*/
@Autowired
private UserDataMapper mapper;
/**
* {@inheritDoc}
*/
@Override
public List<DemoForm> demoFormList() {
List<DemoForm> demoFormList = new ArrayList<>();
//ユーザーデータテーブル(user_data)から全データを取得する
Collection<UserData> userDataList = mapper.findAll();
for (UserData userData : userDataList) {
demoFormList.add(getDemoForm(userData));
}
return demoFormList;
}
/**
* {@inheritDoc}
*/
@Override
public DemoForm findById(String id) {
Long longId = stringToLong(id);
UserData userData = mapper.findById(longId);
return getDemoForm(userData);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(readOnly = false)
public void deleteById(String id){
Long longId = stringToLong(id);
mapper.deleteById(longId);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(readOnly = false)
public void createOrUpdate(DemoForm demoForm){
//更新・追加処理を行うエンティティを生成
UserData userData = getUserData(demoForm);
//追加・更新処理
if(demoForm.getId() == null){
userData.setId(mapper.findMaxId() + 1);
mapper.create(userData);
}else{
mapper.update(userData);
}
}
/**
* {@inheritDoc}
*/
@Override
public String checkForm(DemoForm demoForm, BindingResult result
, String normalPath){
//formオブジェクトのチェック処理を行う
if(result.hasErrors()){
//エラーがある場合は、入力画面のままとする
return "input";
}
//生年月日の日付チェック処理を行う
//エラーがある場合は、エラーメッセージ・エラーフィールドの設定を行い、
//入力画面のままとする
int checkDate = DateCheckUtil.checkDate(demoForm.getBirthYear()
, demoForm.getBirthMonth(), demoForm.getBirthDay());
switch(checkDate){
case 1:
//生年月日_年が空文字の場合のエラー処理
result.rejectValue("birthYear", "validation.date-empty"
, new String[]{"生年月日_年"}, "");
return "input";
case 2:
//生年月日_月が空文字の場合のエラー処理
result.rejectValue("birthMonth", "validation.date-empty"
, new String[]{"生年月日_月"}, "");
return "input";
case 3:
//生年月日_日が空文字の場合のエラー処理
result.rejectValue("birthDay", "validation.date-empty"
, new String[]{"生年月日_日"}, "");
return "input";
case 4:
//生年月日の日付が不正な場合のエラー処理
result.rejectValue("birthYear", "validation.date-invalidate");
//生年月日_月・生年月日_日は、エラーフィールドの設定を行い、
//メッセージを空文字に設定している
result.rejectValue("birthMonth", "validation.empty-msg");
result.rejectValue("birthDay", "validation.empty-msg");
return "input";
case 5:
//生年月日の日付が未来日の場合のエラー処理
result.rejectValue("birthYear", "validation.date-future");
//生年月日_月・生年月日_日は、エラーフィールドの設定を行い、
//メッセージを空文字に設定している
result.rejectValue("birthMonth", "validation.empty-msg");
result.rejectValue("birthDay", "validation.empty-msg");
return "input";
default:
//性別が不正に書き換えられていないかチェックする
if(!demoForm.getSexItems().keySet().contains(demoForm.getSex())){
result.rejectValue("sex", "validation.sex-invalidate");
return "input";
}
//エラーチェックに問題が無いので、正常時の画面遷移先に遷移
return normalPath;
}
}
/**
* DemoFormオブジェクトに引数のユーザーデータの各値を設定する
* @param userData ユーザーデータ
* @return DemoFormオブジェクト
*/
private DemoForm getDemoForm(UserData userData){
if(userData == null){
return null;
}
DemoForm demoForm = new DemoForm();
demoForm.setId(String.valueOf(userData.getId()));
demoForm.setName(userData.getName());
demoForm.setBirthYear(String.valueOf(userData.getBirthY()));
demoForm.setBirthMonth(String.valueOf(userData.getBirthM()));
demoForm.setBirthDay(String.valueOf(userData.getBirthD()));
demoForm.setSex(userData.getSex());
demoForm.setSex_value(userData.getSex_value());
return demoForm;
}
/**
* UserDataオブジェクトに引数のフォームの各値を設定する
* @param demoForm DemoFormオブジェクト
* @return ユーザーデータ
*/
private UserData getUserData(DemoForm demoForm){
UserData userData = new UserData();
if(!DateCheckUtil.isEmpty(demoForm.getId())){
userData.setId(Long.valueOf(demoForm.getId()));
}
userData.setName(demoForm.getName());
userData.setBirthY(Integer.valueOf(demoForm.getBirthYear()));
userData.setBirthM(Integer.valueOf(demoForm.getBirthMonth()));
userData.setBirthD(Integer.valueOf(demoForm.getBirthDay()));
userData.setSex(demoForm.getSex());
userData.setSex_value(demoForm.getSex_value());
return userData;
}
/**
* 引数の文字列をLong型に変換する
* @param id ID
* @return Long型のID
*/
private Long stringToLong(String id){
try{
return Long.parseLong(id);
}catch(NumberFormatException ex){
return null;
}
}
}
また、「DemoController.java」は以下の通りで、インスタンス化するクラスを宣言する「@Autowired」を付与しているクラスは、「DemoServiceImpl.java」でなく「DemoService.java」となっている。「DemoService.java」のメソッドを呼び出すことで、「DemoServiceImpl.java」に定義した実装が呼び出される仕組みになっている。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.support.SessionStatus;
import java.util.ArrayList;
import java.util.List;
@Controller
@SessionAttributes(types = {DemoForm.class})
public class DemoController {
/**
* Demoサービスクラスへのアクセス
*/
@Autowired
private DemoService demoService;
/**
* ユーザーデータテーブル(user_data)のデータを取得して返却する
* @return ユーザーデータリスト
*/
@ModelAttribute("demoFormList")
public List<DemoForm> userDataList(){
List<DemoForm> demoFormList = new ArrayList<>();
return demoFormList;
}
/**
* Formオブジェクトを初期化して返却する
* @return Formオブジェクト
*/
@ModelAttribute("demoForm")
public DemoForm createDemoForm(){
DemoForm demoForm = new DemoForm();
return demoForm;
}
/**
* 初期表示(一覧)画面に遷移する
* @param model Modelオブジェクト
* @return 一覧画面へのパス
*/
@RequestMapping("/")
public String index(Model model){
//ユーザーデータリストを取得
List<DemoForm> demoFormList = demoService.demoFormList();
//ユーザーデータリストを更新
model.addAttribute("demoFormList", demoFormList);
return "list";
}
/**
* 更新処理を行う画面に遷移する
* @param id 更新対象のID
* @param model Modelオブジェクト
* @return 入力・更新画面へのパス
*/
@GetMapping("/update")
public String update(@RequestParam("id") String id, Model model){
//更新対象のユーザーデータを取得
DemoForm demoForm = demoService.findById(id);
//ユーザーデータを更新
model.addAttribute("demoForm", demoForm);
return "input";
}
/**
* 削除確認画面に遷移する
* @param id 更新対象のID
* @param model Modelオブジェクト
* @return 削除確認画面へのパス
*/
@GetMapping("/delete_confirm")
public String delete_confirm(@RequestParam("id") String id, Model model){
//削除対象のユーザーデータを取得
DemoForm demoForm = demoService.findById(id);
//ユーザーデータを更新
model.addAttribute("demoForm", demoForm);
return "confirm_delete";
}
/**
* 削除処理を行う
* @param demoForm Formオブジェクト
* @return 一覧画面の表示処理
*/
@PostMapping(value = "/delete", params = "next")
public String delete(DemoForm demoForm){
//指定したユーザーデータを削除
demoService.deleteById(demoForm.getId());
//一覧画面に遷移
return "redirect:/to_index";
}
/**
* 削除完了後に一覧画面に戻る
* @param model Modelオブジェクト
* @return 一覧画面
*/
@GetMapping("/to_index")
public String toIndex(Model model){
return index(model);
}
/**
* 削除確認画面から一覧画面に戻る
* @param model Modelオブジェクト
* @return 一覧画面
*/
@PostMapping(value = "/delete", params = "back")
public String confirmDeleteBack(Model model){
return index(model);
}
/**
* 追加処理を行う画面に遷移する
* @param model Modelオブジェクト
* @return 入力・更新画面へのパス
*/
@PostMapping("/add")
public String add(Model model){
model.addAttribute("demoForm", new DemoForm());
return "input";
}
/**
* エラーチェックを行い、エラーが無ければ確認画面に遷移し、
* エラーがあれば入力画面のままとする
* @param demoForm Formオブジェクト
* @param result バインド結果
* @return 確認画面または入力画面へのパス
*/
@PostMapping(value = "/confirm", params = "next")
public String confirm(@Validated DemoForm demoForm, BindingResult result){
//チェック処理を行い、画面遷移する
return demoService.checkForm(demoForm, result, "confirm");
}
/**
* 一覧画面に戻る
* @param model Modelオブジェクト
* @return 一覧画面の表示処理
*/
@PostMapping(value = "/confirm", params = "back")
public String confirmBack(Model model){
return index(model);
}
/**
* 完了画面に遷移する
* @param demoForm Formオブジェクト
* @param result バインド結果
* @return 完了画面
*/
@PostMapping(value = "/send", params = "next")
public String send(@Validated DemoForm demoForm, BindingResult result){
//チェック処理を行い、エラーがなければ、更新・追加処理を行う
String normalPath = "redirect:/complete";
String checkPath = demoService.checkForm(
demoForm, result, "redirect:/complete");
if(normalPath.equals(checkPath)){
demoService.createOrUpdate(demoForm);
}
return checkPath;
}
/**
* 完了画面に遷移する
* @param sessionStatus セッションステータス
* @return 完了画面
*/
@GetMapping("/complete")
public String complete(SessionStatus sessionStatus){
//セッションオブジェクトを破棄
sessionStatus.setComplete();
return "complete";
}
/**
* 入力画面に戻る
* @return 入力画面
*/
@PostMapping(value = "/send", params = "back")
public String sendBack(){
return "input";
}
}その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-service/demo
また、同一インタフェースに複数の実装クラスがある場合については、以下を参照のこと。
https://qiita.com/gagagaga_dev/items/c16e5b6b3dff6df7e406
要点まとめ
- MVCモデルに従うと、C(コントローラクラス)とM(モデル)の役割を明確に分けた方が望ましい。
- インタフェースとその実装を用意した場合、呼び出す側で「@Autowired」を付与するクラスをインタフェースクラスにしそのメソッドを呼び出すことで、実装クラスの同名のメソッドが呼び出される仕組みになっている。





