Spring Boot DB連携

Spring Boot上でMongoDBをMongoRepositoryで操作してみた

以前、Oracleと連携するSpring BootのWEBアプリケーションを作成していたが、同じ機能をMongoDBと連携するように変更してみたので、そのサンプルプログラムを共有する。

なお、今回はMongoDB上のデータ参照/作成/更新/削除を簡単に行うことができる「MongoRepository」を利用している。

前提条件

MongoDBのインストールが完了し、下記サイト内のデータベース「test」の下にコレクション「user_data」が作成されていること

MongoDBをMongo Shellで操作してみた今回は、コマンドラインでMongoDBを操作できる「Mongo Shell」を使用して、MongoDBへのDB追加、コレクション追加、デ...

完成した画面イメージ

下記記事の「完成した画面イメージの共有」を参照のこと。

Spring BootのWEB画面上でCRUDを含むOracleアクセス処理を実装してみた(完成イメージ編)今回は、C(Create)・R(Read)・U(Update)・D(Delete)を一通り含むOracle接続処理をSpring Boo...

サンプルプログラムの作成

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、今回説明するプログラムを示している。

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上のデータ参照/作成/更新/削除を簡単に行うことができる。