Spring Boot DB連携

Spring BootでDomaを利用してみた

Domaとは、S2Daoのスタイル(DAOパターンや2 Way SQL)を踏襲したJava6(JDBC4.0)対応のO/Rマッパーで、Springフレームワークに組み込んで使うことができる。今回は、MyBatisの代わりにDomaを利用するよう変更してみたので、そのサンプルプログラムを共有する。

前提条件

下記記事の実装が完了していること。

MyBatisのSQL文をXMLファイルに配置してみたMyBatisを利用するプログラムで、これまではMapperクラスに直接SQL文を記載していたが、今回はXMLファイルにSQL文を移動し...



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

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加/変更したプログラムである。

build.gradleの内容は以下の通りで、Domaのライブラリを追加すると共に、copySqlタスクを追加している。

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')
	// Domaのインストール
	implementation 'org.seasar.doma.boot:doma-spring-boot-starter:1.4.0'
	annotationProcessor 'org.seasar.doma:doma-processor:2.35.0'
}

// ビルド前に実行する、SQLファイルをクラスパスにコピーする処理
task copySql(type: Copy) {
	from './src/main/resources/META-INF'
	into './build/classes/java/main/META-INF'
}
サラリーマン型フリーランスSEという働き方でお金の不安を解消しよう先日、「サラリーマン型フリーランスSE」という働き方を紹介するYouTube動画を視聴しましたので、その内容をご紹介します。 「サ...

また、今回アクセスするテーブル(user_data)のエンティティクラスの内容は以下の通りで、テーブル名・カラム名や主キー設定を行っている。

package com.example.demo;

import lombok.Data;
import org.seasar.doma.Table;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Column;

/**
 * ユーザーデータテーブル(user_data)アクセス用エンティティ
 */
@Table(name = "user_data")
@Entity
@Data
public class UserData {

    /** ID */
    @Id
    private long id;

    /** 名前 */
    private String name;

    /** 生年月日_年 */
    @Column(name="birth_year")
    private int birthY;

    /** 生年月日_月 */
    @Column(name="birth_month")
    private int birthM;

    /** 生年月日_日 */
    @Column(name="birth_day")
    private int birthD;

    /** 性別 */
    private String sex;

}



さらに、今回アクセスするテーブル(user_data)のDaoインタフェースの内容は以下の通りで、「@ConfigAutowireable」「@Dao」アノテーションを先頭に付与し、各メソッドに「@Select」「@Delete」「@Update」「@Insert」アノテーションのいずれかを付与している。

package com.example.demo;

import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.Delete;
import org.seasar.doma.Insert;
import org.seasar.doma.Update;
import org.seasar.doma.boot.ConfigAutowireable;
import java.util.List;

@ConfigAutowireable
@Dao
public interface UserDataDao {

    /**
     * ユーザーデータテーブル(user_data)を全件取得する
     * @return ユーザーデータテーブル(user_data)を全データ
     */
    @Select
    List<UserData> findAll();

    /**
     * 指定したIDをもつユーザーデータテーブル(user_data)のデータを取得する
     * @param id ID
     * @return ユーザーデータテーブル(user_data)の指定したIDのデータ
     */
    @Select
    UserData findById(Long id);

    /**
     * 指定したIDをもつユーザーデータテーブル(user_data)のデータを削除する
     * @param userData ユーザーデータテーブル(user_data)の削除データ
     */
    @Delete
    int deleteById(UserData userData);

    /**
     * 指定したユーザーデータテーブル(user_data)のデータを追加する
     * @param userData ユーザーデータテーブル(user_data)の追加データ
     */
    @Insert
    int create(UserData userData);

    /**
     * 指定したユーザーデータテーブル(user_data)のデータを更新する
     * @param userData ユーザーデータテーブル(user_data)の更新データ
     */
    @Update
    int update(UserData userData);

    /**
     * ユーザーデータテーブル(user_data)の最大値IDを取得する
     * @return ユーザーデータテーブル(user_data)の最大値ID
     */
    @Select
    long findMaxId();
}
ウズウズカレッジJavaコースはわかりやすい動画教材と充実した就業サポートで優良企業を目指せるプログラミングスクールだったJavaは、世界中で広く使われていて、現在の需要が高く将来性もある開発言語になります。 https://www.acrovision....

また、SQLファイルの内容は以下の通りで、Daoインタフェースで「@Select」アノテーションを付与したメソッドに対応するSQL文をそれぞれ記載している。

SELECT
      id
    , name
    , birth_year
    , birth_month
    , birth_day
    , sex
FROM USER_DATA
ORDER BY id
SELECT
      id
    , name
    , birth_year
    , birth_month
    , birth_day
    , sex
FROM USER_DATA
WHERE id = /* id */1
SELECT NVL(max(id), 0)
FROM USER_DATA

なお、今回のサンプルでは使用していないが、「@Delete」「@Update」「@Insert」アノテーションでsqlFile属性をtrueにした場合も、SQLファイルが必要になる。



さらに、サービスクラスの実装クラスの内容は以下の通りで、先ほどのDaoインタフェースを呼び出すように修正している。

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 UserDataDao userDataDao;
 
    /**
     * {@inheritDoc}
     */
    @Override
    public List<DemoForm> demoFormList() {
        List<DemoForm> demoFormList = new ArrayList<>();
        //ユーザーデータテーブル(user_data)から全データを取得する
        Collection<UserData> userDataList = userDataDao.findAll();
        for (UserData userData : userDataList) {
            demoFormList.add(getDemoForm(userData));
        }
        return demoFormList;
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public DemoForm findById(String id) {
        Long longId = stringToLong(id);
        UserData userData = userDataDao.findById(longId);
        return getDemoForm(userData);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(readOnly = false)
    public void deleteById(String id){
        UserData userData = new UserData();
        userData.setId(stringToLong(id));
        userDataDao.deleteById(userData);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(readOnly = false)
    public void createOrUpdate(DemoForm demoForm){
        //更新・追加処理を行うエンティティを生成
        UserData userData = getUserData(demoForm);
        //追加・更新処理
        if(demoForm.getId() == null){
            userData.setId(userDataDao.findMaxId() + 1);
            userDataDao.create(userData);
        }else{
            userDataDao.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("1".equals(userData.getSex()) ? "男" : "女");
        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());
        return userData;
    }
 
    /**
     * 引数の文字列をLong型に変換する
     * @param id ID
     * @return Long型のID
     */
    private Long stringToLong(String id){
        try{
            return Long.parseLong(id);
        }catch(NumberFormatException ex){
            return null;
        }
    }
 
}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-doma/demo



「EaseUS Partition Master」はパーティション分割・結合・作成・サイズ変更等を直感的に行える便利ツールだったハードディスクの記憶領域を論理的に分割し、分割された個々の領域のことを、パーティションといいます。 例えば、以下の図の場合、C/D...

サンプルプログラムのビルド

サンプルプログラムをビルドするには、ビルド前にSQLファイルをクラスパスが通ったMETA-INFディレクトリ下に配置する必要がある。その手順は以下の通り。

1) IntelliJ IDEA上で右上「Gradle」タブをクリックし、Gradleタスクを表示する。
サンプルプログラムのビルド_1

2) buildタスクを選択し右クリックし、「demo[build]の作成」メニューを押下する。
サンプルプログラムのビルド_2

3) 以下の画面が開くため、起動前の「+」ボタンを押下し、「Gradleタスクの実行」メニューを選択する。
サンプルプログラムのビルド_3

4) build.gradleに設定したcopySqlタスクを設定し、「OK」ボタンを押下する。
サンプルプログラムのビルド_4

5) 起動前タスクにcopySqlタスクが設定されていることを確認後、「適用」ボタンを押下後、「OK」ボタンを押下する。
サンプルプログラムのビルド_5

6) buildタスクを選択し右クリックし、「実行」メニューを押下する。
サンプルプログラムのビルド_6

7) buildタスク実行時のコンソールログの内容は、以下の通り。
サンプルプログラムのビルド_7_1

サンプルプログラムのビルド_7_2

8) buildタスクによって、以下のビルド後ファイルが作成される。
サンプルプログラムのビルド_8_1

サンプルプログラムのビルド_8_2



サンプルプログラムの実行結果

Spring Bootアプリケーションを起動し、「http://(サーバー名):(ポート番号)/」とアクセスすると、以下の画面が表示される。
サンプルプログラムの実行結果_1

また、下記記事と同じように、データの追加・更新・削除を行うことができる。

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

さらに、プログラム実行時のSQLログの内容は、以下の通り。
サンプルプログラムの実行結果_2

要点まとめ

  • Domaとは、S2Daoのスタイル(DAOパターンや2 Way SQL)を踏襲したJava6(JDBC4.0)対応のO/Rマッパーで、Springフレームワークに組み込んで使うことができる。
  • Domaで実行するには、エンティティクラス、DAOインタフェース、SQLファイルが必要である。
  • SQLファイルは、DAOインタフェースにおいて、SELECT文またはsqlFile=trueであるDML文で必要になる。
  • SQLファイルは、「(クラスパスが通ったMETA-INFディレクトリ)/(実行するDaoのクラスパス)/(実行するDaoのメソッド名).sql」に記載する。