今回は、Oracle接続する処理をMyBatisにより実装してみたので、そのサンプルプログラムを共有する。MyBatisを利用すると、SQL文をそのまま利用することができるため、これまでのJPAと比べ、複数のテーブル結合を含む複雑なSQL文に対しても容易に対応できる。
前提条件
下記サイトのソースコード実装が完了していること。
また、テーブル「m_sex」を新規作成していること。実行したSQLは以下の通り。
create table m_sex (
sex_cd char(1) primary key not null,
sex_value varchar2(4) NOT NULL
);
insert into m_sex values (1, '男'); insert into m_sex values (2, '女'); commit;
完成した画面イメージ
下記サイトの「完成した画面イメージの共有」を参照のこと。
作成したサンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。「前提条件」で記載したソースコードと異なるプログラムを赤枠で囲っている。

「build.gradle」は下記の通りで、「mybatis-spring-boot-starter」を追加している。
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'
compileOnly 'org.projectlombok:lombok:1.18.10'
annotationProcessor 'org.projectlombok:lombok:1.18.10'
compile files('lib/ojdbc6.jar')
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
}また、「UserData.java」は下記の通りで、USER_DATAテーブルの各項目値と、性別(文字列)を指定している。
性別(文字列)は、M_SEXテーブルから取得したSEX_VALUEの値が入る。
package com.example.demo;
import lombok.Data;
/**
* ユーザーデータテーブル(user_data)アクセス用エンティティ
*/
@Data
public class UserData {
/** ID */
private long id;
/** 名前 */
private String name;
/** 生年月日_年 */
private int birthY;
/** 生年月日_月 */
private int birthM;
/** 生年月日_日 */
private int birthD;
/** 性別 */
private String sex;
/** 性別(文字列) */
private String sex_value;
}
また、「UserDataMapper.java」は下記の通りで、USER_DATAテーブルとアクセスする処理を記載している。「@Mapper」「@Select」「@Insert」「@Delete」「@Update」の各アノテーションを利用している。さらに、findAllメソッドでは、2テーブルを結合した結果を取得している。
package com.example.demo;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;
import java.util.Collection;
@Mapper
public interface UserDataMapper {
/**
* ユーザーデータテーブル(user_data)を全件取得する
* @return ユーザーデータテーブル(user_data)を全データ
*/
@Select("SELECT u.id, u.name, u.birth_year as birthY, u.birth_month as birthM"
+ ", u.birth_day as birthD, u.sex as sex, m.sex_value as sex_value "
+ " FROM USER_DATA u, M_SEX m WHERE u.sex = m.sex_cd ORDER BY u.id ")
Collection<UserData> findAll();
/**
* 指定したIDをもつユーザーデータテーブル(user_data)のデータを取得する
* @param id ID
* @return ユーザーデータテーブル(user_data)の指定したIDのデータ
*/
@Select("SELECT id, name, birth_year as birthY, birth_month as birthM"
+ ", birth_day as birthD, sex FROM USER_DATA WHERE id = #{id}")
UserData findById(Long id);
/**
* 指定したIDをもつユーザーデータテーブル(user_data)のデータを削除する
* @param id ID
*/
@Delete("DELETE FROM USER_DATA WHERE id = #{id}")
void deleteById(Long id);
/**
* 指定したユーザーデータテーブル(user_data)のデータを追加する
* @param userData ユーザーデータテーブル(user_data)の追加データ
*/
@Insert("INSERT INTO USER_DATA VALUES (#{id}, #{name}, #{birthY}"
+ ", #{birthM}, #{birthD}, #{sex})")
void create(UserData userData);
/**
* 指定したユーザーデータテーブル(user_data)のデータを更新する
* @param userData ユーザーデータテーブル(user_data)の更新データ
*/
@Update("UPDATE USER_DATA SET name = #{name}, birth_year = #{birthY}"
+ ", birth_month = #{birthM}, birth_day = #{birthD}"
+ ", sex = #{sex} WHERE id = #{id}")
void update(UserData userData);
/**
* ユーザーデータテーブル(user_data)の最大値IDを取得する
* @return ユーザーデータテーブル(user_data)の最大値ID
*/
@Select("SELECT NVL(max(id), 0) as maxId FROM USER_DATA")
long findMaxId();
}
さらに、「DemoForm.java」は、以下のように、性別(文字列)を追加している。
package com.example.demo;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Formオブジェクトのクラス
*/
@Data
public class DemoForm {
/** ID */
private String id;
/** 名前 */
@NotEmpty
private String name;
/** 生年月日_年 */
private String birthYear;
/** 生年月日_月 */
private String birthMonth;
/** 生年月日_日 */
private String birthDay;
/** 性別 */
@NotEmpty
private String sex;
/** 確認チェック */
@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;
}
}
また、「UserDataMapper.java」を呼び出すコントローラ処理「DemoController.java」は下記の通り。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
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.Collection;
import java.util.List;
@Controller
@SessionAttributes(types = {DemoForm.class})
public class DemoController {
/**
* ユーザーデータテーブル(user_data)へアクセスするマッパー
*/
@Autowired
private UserDataMapper mapper;
/**
* ユーザーデータテーブル(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 = new ArrayList<>();
//ユーザーデータテーブル(user_data)から全データを取得する
Collection<UserData> userDataList = mapper.findAll();
for(UserData userData : userDataList){
demoFormList.add(getDemoForm(userData));
}
//ユーザーデータリストを更新
model.addAttribute("demoFormList", demoFormList);
return "list";
}
/**
* 更新処理を行う画面に遷移する
* @param id 更新対象のID
* @param model Modelオブジェクト
* @return 入力・更新画面へのパス
*/
@GetMapping("/update")
public String update(@RequestParam("id") String id, Model model){
UserData userData = mapper.findById(Long.parseLong(id));
model.addAttribute("demoForm", getDemoForm(userData));
return "input";
}
/**
* 削除確認画面に遷移する
* @param id 更新対象のID
* @param model Modelオブジェクト
* @return 削除確認画面へのパス
*/
@GetMapping("/delete_confirm")
public String delete_confirm(@RequestParam("id") String id, Model model){
UserData userData = mapper.findById(Long.parseLong(id));
model.addAttribute("demoForm", getDemoForm(userData));
return "confirm_delete";
}
/**
* 削除処理を行う
* @param demoForm Formオブジェクト
* @return 一覧画面の表示処理
*/
@PostMapping(value = "/delete", params = "next")
@Transactional(readOnly = false)
public String delete(DemoForm demoForm){
mapper.deleteById(Long.parseLong(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){
//formオブジェクトのチェック処理を行う
if(result.hasErrors()){
//エラーがある場合は、入力画面のままとする
return "input";
}
//アノテーション以外のチェック処理を行い、画面遷移する
return checkOthers(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")
@Transactional(readOnly = false)
public String send(@Validated DemoForm demoForm, BindingResult result){
//formオブジェクトのチェック処理を行う
if(result.hasErrors()){
//エラーがある場合は、入力画面のままとする
return "input";
}
//アノテーション以外のチェック処理を行い、
//エラーがなければ、更新・追加処理を行う
String normalPath = "redirect:/complete";
String checkOthersPath = checkOthers(demoForm, result, normalPath);
if(normalPath.equals(checkOthersPath)){
//更新・追加処理を行うエンティティを生成
UserData userData = getUserData(demoForm);
//追加・更新処理
if(demoForm.getId() == null){
userData.setId(mapper.findMaxId() + 1);
mapper.create(userData);
}else{
mapper.update(userData);
}
}
return checkOthersPath;
}
/**
* 完了画面に遷移する
* @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";
}
/**
* アノテーション以外のチェック処理を行い、画面遷移先を返却
* @param demoForm Formオブジェクト
* @param result バインド結果
* @param normalPath 正常時の画面遷移先
* @return 確認画面または入力画面へのパス
*/
private String checkOthers(DemoForm demoForm, BindingResult result
, String normalPath){
//** アノテーション以外のチェック処理を行う
//** エラーがある場合は、エラーメッセージ・(エラー時に赤反転するための)
//** エラーフィールドの設定を行い、入力画面のままとする
//生年月日のチェック処理
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){
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;
}
}
また、「list.html」は以下の通りで、sex_valueの値を直接表示するようにしている。
<!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.id}__'})}">更新</a></td>
<td><a href="/delete_confirm"
th:href="@{/delete_confirm(id=${'__${obj.id}__'})}">削除</a></td>
</tr>
</table>
<br/><br/>
<form method="post" th:action="@{/add}">
<input type="submit" value="データ追加" /><br/><br/>
<input type="button" value="閉じる" onclick="window.close();" />
</form>
</body>
</html>さらに、「application.properties」は以下の通りで、下2行でSQLログ出力の定義を行っている。
server.port = 8084 # DB接続情報 spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe spring.datasource.username=USER01 spring.datasource.password=USER01 spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver # SQLログ出力 logging.level.org.springframework=warn logging.level.com.example.demo.UserDataMapper=debug
上記「application.properties」によるコンソールログ出力例は以下の通り。

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-mybatis/demo
要点まとめ
- MyBatisを利用すると、複雑なSQL文に対しても容易に対応できる。
- MyBatisを利用できるようにするには、build.gradleに「mybatis-spring-boot-starter」を追加する。
- エンティティクラスには、テーブルの各項目値を指定すればよい。
- テーブルにアクセスするMapperクラスは、クラスに「@Mapper」を付与し、各メソッドに「@Select」「@Insert」「@Delete」「@Update」のいずれかを付与すればよい。
- SQLログ・バインドパラメータの出力定義は、application.propertiesに記載する。






