blog

ジオメトリ型データの保存とクエリを実装する Mybatis インターセプター

周知のように、Mybatisにはインターセプターが存在します。実際、ビジネス上の必要性から、データベースには地理座標を格納するためのGEOMETRY型のフィールドを持つテーブルがあります。 Mybat...

Aug 11, 2020 · 14 min. read
シェア

Mybatis インターセプターによるジオメトリ型データの保存とクエリの実装

ご存知のように、SpringBootにはインターセプターがあり、実際、Mybatisにもインターセプターがあります。

Mybatis-Plusを使っているので、長い間SQLを書いておらず、怠け者になってしまいました。

しかし、Mybatis-Plusはこのタイプを扱うことができません。

  • SQL 関数 ST_GEOMFROMTEXT() の使用が追加されました。
  • SQL関数ST_ASTEXT()を使用するクエリ。

怠け者であることは耐え難いことです今日はこの種のデータを扱うインターセプターを書いてみましょう。

Mybatis

はじめに、Mybatisインターセプターの紹介から始めましょう。

org.apache.ibatis.plugin.Interceptorインターフェースを実装することから始めましょう。acceptメソッドを書き換えます。

実は、もう一つ重要なのはアノテーションです。

このアノテーションの中に、もうひとつ設定するアノテーションがあります。

以下:

@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}
)})

型には以下の型があり、括弧内はメソッドです。


  • Executor
  • リクエストパラメータの処理
    ParameterHandler
  • 結果セットを返します。
    ResultSetHandler
  • SQLステートメントは
    StatementHandler

完全な例

@Intercepts({
  @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
  @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
  @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
  @Signature(type = Executor.class, method = "query", args = {
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
  @Signature(type = Executor.class, method = "query", args = {
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})

もちろん、インターセプターは一般的に1つの状況しか扱わないので、@Signatureだけで十分です。

準備

ジオメトリ型のフィールドを区別できるようにするため、ノートをカスタマイズしました。

@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Geometry {
}

新しいテーブルが作成されました。

対応するエンティティクラス

public class User extends Model<User> {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private Integer name;
    @Geometry
    private String location;
    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

インターセプタ

まず知っておかなければならないのは、いつインターセプトするかということです。

  • MybatisのSQL実行は前処理されており、ここでインターセプトしてもSQL関数は呼び出されません。
  • 挿入などのパラメータ処理を要求する場合、ST_GEOMFROMTEXT()のレイヤをパラメータに巻き付けたり、前処理の関係でSQL実行時に関数として扱われなかったり、動作しません。同様に、クエリ実行時に、戻り値の結果セット処理をインターセプトしても動作しません。

MybatisGeometryHandler

import com.ler.manager.annotation.Geometry;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;
import org.springframework.core.annotation.AnnotationUtils;
/**
 * 
 * 
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisGeometryHandler implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        //SELECT操作
        if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            List<ResultMap> resultMaps = mappedStatement.getResultMaps();
            ResultMap map = resultMaps.get(0);
            Class<?> type = map.getType();
            List<Integer> locations = selectGetLocations(type);
            StringBuilder sb = new StringBuilder();
            sb.append("SELECT ");
            String originalSql = boundSql.getSql();
            String reSql = originalSql.replace("SELECT", "");
            int from = reSql.indexOf("FROM");
            String subSql = reSql.substring(0, from);
            String[] split = subSql.split(",");
            for (int i = 0; i < split.length; i++) {
                if (locations.contains(i + 1)) {
                    sb.append("ST_ASTEXT(").append(split[i]).append(") AS ").append(split[i]).append(",");
                } else {
                    sb.append(split[i]).append(",");
                }
            }
            String substring = sb.substring(0, sb.length() - 1);
            String lastSql = reSql.substring(from);
            metaObject.setValue("delegate.boundSql.sql", substring + " " + lastSql);
            metaObject.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
            metaObject.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
            return invocation.proceed();
        } else {
            Object paramObj = boundSql.getParameterObject();
            Field[] fields = paramObj.getClass().getDeclaredFields();
            List<Integer> locations = insertGetLocations(fields, paramObj);
            String originalSql = boundSql.getSql();
            int j = 0;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < originalSql.length(); i++) {
                String subStr = originalSql.substring(i, i + 1);
                if ("?".equalsIgnoreCase(subStr)) {
                    j++;
                    if (locations.contains(j)) {
                        sb.append("ST_GEOMFROMTEXT(" + subStr + ")");
                    } else {
                        sb.append(subStr);
                    }
                } else {
                    sb.append(subStr);
                }
            }
            metaObject.setValue("delegate.boundSql.sql", sb.toString());
            return invocation.proceed();
        }
    }
    private List<Integer> selectGetLocations(Class<?> type) {
        List<Integer> locations = new ArrayList<>();
        Field[] fields = type.getDeclaredFields();
        int i = 0;
        for (Field field : fields) {
            if (!"serialVersionUID".equalsIgnoreCase(field.getName())) {
                i++;
                if (field.getAnnotation(Geometry.class) != null) {
                    locations.add(i);
                }
            }
        }
        return locations;
    }
    private List<Integer> insertGetLocations(Field[] declaredFields, Object parameterObject) {
        List<Integer> locations = new ArrayList<>();
        int i = 0;
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            try {
                Object o = declaredField.get(parameterObject);
                if (o != null && !"serialVersionUID".equalsIgnoreCase(declaredField.getName())) {
                    i++;
                    Geometry annotation = AnnotationUtils.findAnnotation(declaredField, Geometry.class);
                    if (annotation != null) {
                        locations.add(i);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return locations;
    }
    private Object realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return target;
    }
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTargetrealTargetメソッドは、サードパーティのJarパッケージの依存性を減らすためのコピーです。これは主に、実際の処理オブジェクトを取得するために使用されます。

metaObject.setValue("delegate.boundSql.sql", SQL ;はSQLを修正するために使われます。

selectGetLocations メソッドの機能は、クエリ時にオブジェクトのどのフィールドに @Geometry アノテーションがあるかを判別し、その場所を記録することです。

insertGetLocations このメソッドを使用して、挿入時にオブジェクト内のどのフィールドに @Geometry アノテーションが付 いているかを判別し、その位置を記録します。

この場所でSQL文を修正することが重要です。

の対応する位置を置き換える操作を追加します。番号の対応する位置を置き換える操作を追加します。

対応する位置のフィールドを置き換えるクエリー操作。

を設定します。

Mybatis-Plusの使用のため、インターセプタの構成は非常に簡単ですので、Mybatis構成クラスでは、作成SqlSessionFactoryこのビーンsqlSessionFactory()メソッドで、文を追加します。configuration.addInterceptor(new MybatisGeometryHandler());

追加操作

@ApiOperation("を追加する)
@PostMapping(value = "/add", name = "を追加する)
public HttpResult add() {
    User user = new User();
    user.setName(123456);
    user.setLocation("POINT(121.58623 31.150897)");
    user.insert();
    return HttpResult.success();
}

クエリ操作

@ApiOperation("クエリ1 ")
@GetMapping(value = "/one", name = "クエリ1 ")
public HttpResult one() {
    User user = userService.getById(1L);
    return HttpResult.success(user);
}
@ApiOperation("複数クエリ")
@GetMapping(value = "/list", name = "複数クエリ")
public HttpResult list() {
    List<User> list = userService.list(new QueryWrapper<>());
    return HttpResult.success(list);
}

1つチェック 複数チェック

このインターセプターを使用しないと、 adding はエラーを報告し、クエリは文字化けします。

Read next

オブジェクトの作成、プロトタイプの連鎖、継承メソッド

作成されたオブジェクト 1.リテラル 2.コンストラクタ・メソッドで作成されたオブジェクト、プロトタイプ・オブジェクトがパラメータ 3つの作成方法の結果を示します:プロトタイプ・チェイン 1.コンストラクタ、インスタンス、プロトタイプ・オブジェクト

Aug 11, 2020 · 4 min read

ES6クイックスタート

Aug 10, 2020 · 11 min read

Javaスレッド割り込みの秘密

Aug 10, 2020 · 12 min read

EventBusソースコード解析

Aug 10, 2020 · 17 min read