ふるてつのぶろぐ

福岡在住のエンジニアです。

写真提供:福岡市

いまだにJava 8 備忘録 - MyBatisで更新日付などを自動設定

今日すること

こんにちはふるてつです。

きょうはJava 8 のMyBatisを使ったAOPのおはなしです。
f:id:tetsufuru:20200607114634p:plain
今回書きたかったのは、各テーブルに共通でもつような項目、
例えば「作成者/作成日時」、「更新者/更新日時」を自動で設定する方法です。
Spring BootSprinrg AOPを使います。

実はQiitaにやりたかったことがほぼ書いてあり、真似させていただいてます😊
作者の方ありがとうございます。
https://qiita.com/kenhori/items/9941a159285f9360e877

AOPのインストール

まずSpring AOPをインストールします。
わたしの環境は Gradle を使っているので Maven Repository で Gradle の書き方例を探します。
下記の例がのっていました。
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop/2.3.0.RELEASE


compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.3.0.RELEASE'

こちらをbuild.gradleファイルに書き入れます。
しかしすこし書き方はすこし古いようですね。
compile groupimplementationなどに書き換えた方がいい気がします。
参考までにわたしはいまこのように書いています。
(バージョン名は${springbootVersion}のように外出しにしてます)


implementation "org.springframework.boot:spring-boot-starter-aop:${springbootVersion}"

Interceptorを追加

わたしはMyBatisの Generator で Mapper を自動作成しています。
そのため Mapper の名前は必ず xxMapper となり、AOPで動きをキャッチしやすいですね。
ちなみにわたしのMapper周りは下のような構成になっています。
f:id:tetsufuru:20200606201353p:plain

ではソースを紹介します。
MapperInterceptor.javaというクラスになります。
細かく解説を入れましたのでみなさまの参考になればと思います。

package com.example.demo.interceptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Objects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import com.example.demo.service.AccountService;

import lombok.RequiredArgsConstructor;

@Aspect
@Component
@RequiredArgsConstructor
public class MapperInterceptor {

    private static final String CONST_INSERT = "insert";
    private static final String CONST_UPDATE = "update";
    private static final String CONST_SET_ENTER_USER = "setEnterUser";
    private static final String CONST_SET_ENTER_DATE = "setEnterDate";
    private static final String CONST_SET_UPDATE_USER = "setUpdateUser";
    private static final String CONST_SET_UPDATE_DATE = "setUpdateDate";

    private final AccountService accountService;

    // ↓ ① Beforeなのでメソッドの実行前に呼び出される
    // ↓ ② executionでマッチするメソッドの条件を書いている
    //     (hogeMapper の #insertHoge() メソッドか、#updateHoge() メソッド)
    @Before("execution(* com.example.demo.repository.*Mapper.insert*(..)) || "
            + "execution(* com.example.demo.repository.*Mapper.update*(..))")
    public void setCommonInformations(JoinPoint joinPoint)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        // ③ JoinPoint をもとにいまインターセプトされているメソッドの名前を取得する
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();

        // ④ 登録、更新日時にセットしたい時刻を変数にいれる(UTC基準の現在時刻)
        LocalDateTime localDateTimeNowUtc = LocalDateTime.now(ZoneOffset.UTC);

        // ⑤ JoinPoint をもとにメソッドの1番目の引数を取得する(テーブル更新用のDTOが必ず最初の引数なので)
        Object[] args = joinPoint.getArgs();
        Object dto = args[0];

        if (methodName.startsWith(CONST_INSERT)) {
            // ⑥ こちらが Insert 系のメソッドの時
            //  「作成者・更新者の情報」と、「登録・更新日時にセットしたい時刻」を dto にセットします。
            //  (作成者・更新者情報は アカウントサービスから取得するようにしています)
            setupEnterInformations(accountService.getUserName(), localDateTimeNowUtc, dto);
            setupUpdateInformations(accountService.getUserName(), localDateTimeNowUtc, dto);

        } else if (methodName.startsWith(CONST_UPDATE)) {
            // ⑦ こちらが Update 系のメソッドの時
            //  「更新者の情報」と、「更新日時にセットしたい時刻」を dto にセットします。
            setupUpdateInformations(accountService.getUserName(), localDateTimeNowUtc, dto);
        }

    }

    // ↓ ⑧ 作成者/作成日時を dto に自動で設定するメソッド
    private void setupEnterInformations(String userName, LocalDateTime utcLocalDateTimeNow, Object dto)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        // ↓ ⑧ - 1 リフレクションで dto の #setEnterUser() メソッド(enterUser のセッター)を取得する
        Method setEnterUserMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_ENTER_USER, String.class);
        if (Objects.nonNull(setEnterUserMethod)) {
            // ↓ ⑧ - 2  dto の #setEnterUser() メソッドに userName をセットする
            setEnterUserMethod.invoke(dto, userName);
        }

        // ↓ ⑧ - 3 リフレクションで dto の #setEnterDate() メソッド(enterDateのセッター)を取得する
        Method setEnterDateMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_ENTER_DATE,
                LocalDateTime.class);
        if (Objects.nonNull(setEnterDateMethod)) {
            // ↓ ⑧ - 4  dto の #setEnterDate() メソッドに 現在時刻 をセットする
            setEnterDateMethod.invoke(dto, utcLocalDateTimeNow);
        }

    }

    // ↓ ⑨ 更新者/更新日時を dto に自動で設定するメソッド
    private void setupUpdateInformations(String userName, LocalDateTime utcLocalDateTimeNow, Object dto)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        Method setUpdateUserMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_UPDATE_USER, String.class);
        if (Objects.nonNull(setUpdateUserMethod)) {
            setUpdateUserMethod.invoke(dto, userName);
        }

        Method setUpdateDateMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_UPDATE_DATE,
                LocalDateTime.class);
        if (Objects.nonNull(setUpdateDateMethod)) {
            setUpdateDateMethod.invoke(dto, utcLocalDateTimeNow);
        }
    }
}

感想


今回はMyBatisAOPについて書きました。
Qiitaの内容をだいぶ参考にしたのでわたしのオリジナルな部分はかなり少なめですが。
しかしこのあたりの内容は忘れてしまいやすいので一度、自分のブログに書きとめておきたかった次第です。

きょうは久しぶりにブログを書きました。
久しぶりすぎてどうやって書いていたか思い出せずちょっと戸惑いました~

それではまた😎