以前、Oracleと連携するSpring BootのWEBアプリケーションを作成していたが、同じ機能をMongoDBと連携するように変更してみたので、そのサンプルプログラムを共有する。
なお、今回はMongoDB上のデータ参照/作成/更新/削除を簡単に行うことができる「MongoRepository」を利用している。
前提条件
MongoDBのインストールが完了し、下記サイト内のデータベース「test」の下にコレクション「user_data」が作成されていること
完成した画面イメージ
下記記事の「完成した画面イメージの共有」を参照のこと。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、今回説明するプログラムを示している。
build.gradleの内容は以下の通りで、MongoDBを利用するための「spring-boot-starter-data-mongodb」を追加している。
plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' //lombokを利用するための設定 compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' //MongoDBを利用するための設定 compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb', version: '2.2.6.RELEASE' }
また、application.ymlの内容は以下の通りで、MongoDBの接続先となるホスト名・ポート番号・データベース名を指定している。
server: port: 8084 # MongoDB接続情報 spring: data: mongodb: host: localhost port: 27017 database: test
さらに、コレクション「user_data」のエンティティクラスの内容は以下の通りで、コレクション名を@Documentアノテーションで指定し、主キーを@Idアノテーションで指定している。
package com.example.demo; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; //コレクション名を@Documentアノテーションで指定 @Data @Document(collection = "user_data") public class UserData { /** MongoDBの主キー(変数名は任意) */ @Id private String pKeyId; /** ID */ private long id; /** 名前 */ private String name; /** 生年月日_年 */ private int birth_year; /** 生年月日_月 */ private int birth_month; /** 生年月日_日 */ private int birth_day; /** 性別 */ private int sex; /** メモ */ private String memo; }
また、MongoRepositoryを継承したインタフェースの内容は以下の通りで、これを利用してMongoDBへのアクセスを行えるようにしている。また、IDが最大のユーザーデータを取得するためのメソッドを追加している。
package com.example.demo; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @Repository public interface UserDataRepository extends MongoRepository<UserData, String> { /** * ユーザーデータから、IDが最大のデータを取得する * @return IDが最大のユーザーデータ */ UserData findTopByOrderByIdDesc(); }
さらに、MongoDBへのアクセスを行うサービスクラスのインタフェースの内容は以下の通り。
package com.example.demo; import java.util.List; public interface DemoService { /** * ユーザーデータリストを全件取得 * @return ユーザーデータリスト */ List<DemoForm> demoFormList(); /** * 引数のIDに対応するユーザーデータを取得 * @param pKeyID MongoDBの主キー * @return ユーザーデータ */ DemoForm findByPKeyId(String pKeyID); /** * 引数のユーザーデータを削除 * @param demoForm 追加・更新用Formオブジェクト */ void delete(DemoForm demoForm); /** * 引数のユーザーデータがあれば更新し、無ければ追加 * @param demoForm 追加・更新用Formオブジェクト */ void createOrUpdate(DemoForm demoForm); }
次に、サービスクラスのインタフェースの実装内容は以下の通り。UserDataRepositoryクラスを用いてMongoDBにアクセスしていて、全データ取得はfindAllメソッド、特定データの取得はfindByIdメソッド、データ作成/更新はsaveメソッド、データ削除はdeleteメソッドを、それぞれ利用している。
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.Optional; import static org.springframework.data.domain.Sort.Direction.ASC; @Service public class DemoServiceImpl implements DemoService{ /** * ユーザーデータ(user_data)へアクセスするリポジトリ */ @Autowired private UserDataRepository repository; /** * {@inheritDoc} */ @Override public List<DemoForm> demoFormList(){ List<DemoForm> demoFormList = new ArrayList<>(); //ユーザーデータ(user_data)から全データを取得する List<UserData> userDataList = repository.findAll(new Sort(ASC, "id")); for (UserData userData : userDataList) { demoFormList.add(getDemoForm(userData)); } return demoFormList; } /** * {@inheritDoc} */ @Override public DemoForm findByPKeyId(String pKeyID) { Optional<UserData> userData = repository.findById(pKeyID); return getDemoForm(userData.get()); } /** * {@inheritDoc} */ @Override @Transactional(readOnly = false) public void delete(DemoForm demoForm){ UserData userData = getUserData(demoForm); repository.delete(userData); } /** * {@inheritDoc} */ @Override @Transactional(readOnly = false) public void createOrUpdate(DemoForm demoForm){ //更新・追加処理を行うエンティティを生成 UserData userData = getUserData(demoForm); //追加・更新処理 if(demoForm.getId() == null){ UserData tmpUserData = repository.findTopByOrderByIdDesc(); userData.setId(tmpUserData == null ? 1 : tmpUserData.getId() + 1); repository.save(userData); }else{ repository.save(userData); } } /** * DemoFormオブジェクトに引数のユーザーデータの各値を設定する * @param userData ユーザーデータ * @return DemoFormオブジェクト */ private DemoForm getDemoForm(UserData userData){ if(userData == null){ return null; } DemoForm demoForm = new DemoForm(); demoForm.setPKeyId(userData.getPKeyId()); demoForm.setId(String.valueOf(userData.getId())); demoForm.setName(userData.getName()); demoForm.setBirthYear(String.valueOf(userData.getBirth_year())); demoForm.setBirthMonth(String.valueOf(userData.getBirth_month())); demoForm.setBirthDay(String.valueOf(userData.getBirth_day())); demoForm.setSex(String.valueOf(userData.getSex())); demoForm.setMemo(userData.getMemo()); demoForm.setSex_value(userData.getSex() == 1 ? "男" : "女"); return demoForm; } /** * UserDataオブジェクトに引数のフォームの各値を設定する * @param demoForm DemoFormオブジェクト * @return ユーザーデータ */ private UserData getUserData(DemoForm demoForm){ UserData userData = new UserData(); userData.setPKeyId(demoForm.getPKeyId()); if(!StringUtils.isEmpty(demoForm.getId())){ userData.setId(Long.valueOf(demoForm.getId())); } userData.setName(demoForm.getName()); userData.setBirth_year(Integer.valueOf(demoForm.getBirthYear())); userData.setBirth_month(Integer.valueOf(demoForm.getBirthMonth())); userData.setBirth_day(Integer.valueOf(demoForm.getBirthDay())); userData.setSex(Integer.valueOf(demoForm.getSex())); userData.setMemo(demoForm.getMemo()); return userData; } }
また、フォームクラス、コントローラクラスの内容は以下の通り。コントローラクラスは、先ほどのサービスクラスを呼び出している。
package com.example.demo; import com.example.demo.check.CheckDate; import com.example.demo.check.CheckZenkaku; import com.example.demo.check.FutureDate; import lombok.Data; import org.thymeleaf.util.StringUtils; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotEmpty; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; //日付チェック・未来日チェックを独自アノテーションで実施 @Data @CheckDate(dtYear = "birthYear", dtMonth = "birthMonth", dtDay = "birthDay" , message = "{validation.date-invalidate}") @FutureDate(dtYear = "birthYear", dtMonth = "birthMonth", dtDay = "birthDay" , message = "{validation.date-future}") public class DemoForm implements Serializable { /** MongoDBの主キー */ private String pKeyId; /** ID */ private String id; /** 名前 */ @NotEmpty @CheckZenkaku private String name; /** 生年月日_年 */ private String birthYear; /** 生年月日_月 */ private String birthMonth; /** 生年月日_日 */ private String birthDay; /** 性別 */ @NotEmpty private String sex; /** メモ */ private String memo; /** 確認チェック */ @NotEmpty private String checked; /** 性別(文字列) */ private String sex_value; /** 生年月日_月のMapオブジェクト */ public Map<String,String> getMonthItems(){ Map<String, String> monthMap = new LinkedHashMap<String, String>(); for(int i = 1; i <= 12; i++){ monthMap.put(String.valueOf(i), String.valueOf(i)); } return monthMap; } /** 生年月日_日のMapオブジェクト */ public Map<String,String> getDayItems(){ Map<String, String> dayMap = new LinkedHashMap<String, String>(); for(int i = 1; i <= 31; i++){ dayMap.put(String.valueOf(i), String.valueOf(i)); } return dayMap; } /** 性別のMapオブジェクト */ public Map<String,String> getSexItems(){ Map<String, String> sexMap = new LinkedHashMap<String, String>(); sexMap.put("1", "男"); sexMap.put("2", "女"); return sexMap; } /** * 生年月日の年・月・日が入力されているかをチェックする * @return チェック結果 */ @AssertTrue(message = "{validation.date-empty}") public boolean isBirthDayRequired(){ if(StringUtils.isEmpty(birthYear) && StringUtils.isEmpty(birthMonth) && StringUtils.isEmpty(birthDay)){ return false; } return true; } /** * 性別が不正な値でないかチェックする * @return チェック結果 */ @AssertTrue(message = "{validation.sex-invalidate}") public boolean isSexInvalid(){ return StringUtils.isEmpty(sex) || getSexItems().keySet().contains(sex); } }
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.SessionAttributes; 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.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; } /** * 初期表示(一覧)画面に遷移する * @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.findByPKeyId(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.findByPKeyId(id); //ユーザーデータを更新 model.addAttribute("demoForm", demoForm); return "confirm_delete"; } /** * 削除処理を行う * @param demoForm 追加・更新用Formオブジェクト * @return 一覧画面の表示処理 */ @PostMapping(value = "/delete", params = "next") public String delete(DemoForm demoForm){ //指定したユーザーデータを削除 demoService.delete(demoForm); //一覧画面に戻る 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(value = "/add", params = "next") 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){ //追加・更新用Formオブジェクトのチェック処理でエラーがある場合は、 //入力画面のままとする if(result.hasErrors()){ return "input"; } //エラーが無ければ確認画面に遷移する return "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){ //チェック処理を行い、エラーがなければ、更新・追加処理を行う if(result.hasErrors()){ return "input"; } demoService.createOrUpdate(demoForm); return "redirect:/complete"; } /** * 完了画面に遷移する * @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"; } }
また、HTMLファイルの中で今回変更した「list.html」の内容は以下の通りで、updateメソッド・delete_confirmメソッドを呼び出す際の引数にpKeyId(MongoDBの主キー)を渡すようにしている。
<!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>index page</title> </head> <body> ユーザーデータテーブル(user_data)の全データ<br/><br/> <table border="1" cellpadding="5"> <tr> <th>ID</th> <th>名前</th> <th>生年月日</th> <th>性別</th> <th></th> <th></th> </tr> <tr th:each="obj : ${demoFormList}"> <td th:text="${obj.id}"></td> <td th:text="${obj.name}"></td> <td th:text="|${obj.birthYear}年 ${obj.birthMonth}月 ${obj.birthDay}日|"></td> <td th:text="${obj.sex_value}"></td> <td><a href="/update" th:href="@{/update(id=${'__${obj.pKeyId}__'})}">更新</a></td> <td><a href="/delete_confirm" th:href="@{/delete_confirm(id=${'__${obj.pKeyId}__'})}">削除</a></td> </tr> </table> <br/><br/> <form method="post" th:action="@{/add}"> <input type="submit" name="next" value="データ追加" /><br/><br/> <input type="button" value="閉じる" onclick="window.close();" /> </form> </body> </html>
さらに、DemoApplicationクラスの内容は以下の通り。
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @SpringBootApplication public class DemoApplication implements WebMvcConfigurer { @Autowired private MessageSource messageSource; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public LocalValidatorFactoryBean validator() { //Spring Bootデフォルトのエラーメッセージのプロパティファイルを //ValidationMessages.propertiesからmessages.propertiesに変更する LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); localValidatorFactoryBean.setValidationMessageSource(messageSource); return localValidatorFactoryBean; } @Override public org.springframework.validation.Validator getValidator() { return validator(); } }
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-mongo-repository/demo
要点まとめ
- Spring Boot上でMongoDBを操作するには、MongoRepositoryを利用すると、MongoDB上のデータ参照/作成/更新/削除を簡単に行うことができる。