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 はエラーを報告し、クエリは文字化けします。




