mybatis 拦截器_Mybatis拦截器实现Geometry类型数据存储与查询

本文介绍如何使用Mybatis拦截器处理Geometry类型的数据库字段,通过自定义注解和拦截StatementHandler来实现数据的存储和查询。文章详细讲解了拦截器的工作原理,以及在新增和查询操作中的配置和应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

众所周知,SpringBoot中有拦截器,其实Mybatis里面也有拦截器,之前因为业务的需求,数据库中有一张表的一个字段是GEOMETRY类型,这是用来存储地理坐标的。

因为使用了Mybatis-Plus,所以很久没写SQL了,人也变懒了?。

可是Mybatis-Plus无法处理这个类型,只能写SQL来进行存储和查询。

  • 新增要使用SQL函数:ST_GEOMFROMTEXT()
  • 查询要使用SQL函数:ST_ASTEXT()

作为一个懒人,是无法忍受的?,今天就来写一个拦截器(插件)来处理这种类型的数据吧。

Mybatis拦截器

动手之前,先介绍一下Mybatis拦截器。

首先要实现org.apache.ibatis.plugin.Interceptor这个接口。复写其中的intercept方法。

其实还有一个重要的东西是注解:@Intercepts

这个注解里面还要配置一个注解,也可以是多个@Signature

如下:

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

type有以下类型,括号里的是method

  • 执行
    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • 请求参数处理
    ParameterHandler (getParameterObject, setParameters)
  • 返回结果集处理
    ResultSetHandler (handleResultSets, handleOutputParameters)
  • SQL语句构建
    StatementHandler (prepare, parameterize, batch, update, query)

完整例子

@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}),
})

当然一个拦截器一般只针对一种情况处理,所以只需要一个@Signature就可以了。

准备工作

为了能区分Geometry类型的字段,所以我自定义了一个注解。

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

新建了一张表

f047b47673e85902ec301bd2d6069634.png

对应的实体类

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执行的时候是不会把它当做函数的,还是不行。同样查询的时候,拦截返回结果集处理也不行。

貌似只有一种方式,那就是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;

/**
 * @author lww
 * @date 2020-08-01 18:35
 */
@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 resultMaps = mappedStatement.getResultMaps();
            ResultMap map = resultMaps.get(0);
            Class> type = map.getType();
            List 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                 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 locations = insertGetLocations(fields, paramObj);
            String originalSql = boundSql.getSql();int j = 0;
            StringBuilder sb = new StringBuilder();for (int i = 0; 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 selectGetLocations(Class> type) {
        List 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 insertGetLocations(Field[] declaredFields, Object parameterObject) {
        List 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;
    }@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);
    }@Overridepublic void setProperties(Properties properties) {
    }
}

realTarget方法是com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTarget这个拷贝过来的,是为了减少第三方Jar包依赖。主要用于获得真正的处理对象。

metaObject.setValue("delegate.boundSql.sql", SQL语句);是用来修改SQL的。

selectGetLocations这个方法的作用:在查询的时候,判断对象里哪些字段上有@Geometry注解,并记录位置。

insertGetLocations这个方法的作用:在插入的时候,判断对象里哪些字段上有@Geometry注解,并记录位置。

这个位置就是我们修改SQL语句的重要依据。

新增操作,替换对应位置的?号。

查询操作,替换对应位置的字段。

配置

因为使用的是Mybatis-Plus,所以配置拦截器很简单,在Mybatis配置类中,创建SqlSessionFactory这个BeansqlSessionFactory()方法里,加一句configuration.addInterceptor(new MybatisGeometryHandler());

bf68ad9b7171dab0768a560a5ab1fbb6.png
新增操作
@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();
}
92ae4f95371b8efa73c73469cec01290.png 072beac81fecc89567f304a7622c474c.png
查询操作
@ApiOperation("查询一个")
@GetMapping(value = "/one", name = "查询一个")
public HttpResult one() {
    User user = userService.getById(1L);
    return HttpResult.success(user);
}
@ApiOperation("查询多个")
@GetMapping(value = "/list", name = "查询多个")
public HttpResult list() {
    List list = userService.list(new QueryWrapper<>());return HttpResult.success(list);
}
d88e6af69ebe1ffc5d8aaa4cb4915256.png

查一个4f94026844dafea001f269e30c770dd0.png查多个f24060da0bf0a8c556e1960d122a05bb.png

如果不使用这个拦截器,新增会报错,查询会乱码。

0833cd8b4e2e917dfbebbda309c2ba98.png a51c7d74703598d2e00a600bcacba119.png

最后

欢迎大家关注我的公众号,共同学习,一起进步。加油?

41bcbe7799ecb9b43905cb919534b022.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值