前言
MybatisPlus通过内置的一些方法使我们可以不需要编写sql代码即可执行sql语句,而对于空间类型却不太支持,所以本文章是对MybatisPlus支持空间数据类型的一种优化
注:本文章仅针对SqlServer的空间数据进行优化,因其语句执行比较友好。专业的空间数据库没学过,在此不讨论。虽然也可以支持MySQL,但比较麻烦,所以就没写了
环境
springboot:2.7.10
mybatis-plus:3.5.5
系统:Windows 11
java:1.8.351
Maven:3.9.2
实验
1、准备一张表
CREATE TABLE [geo](
[id] bigint PRIMARY KEY,
[location] geography
);
2、编写实体类
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class Geo {
@TableId
private Long id;
@TableField(value = "location.STAsText()")
private String location;
}
这里直接通过@TableField注解注入调用函数的sql,在使用IService的方法时会自动带上location作为别名,以能正确映射到该属性上
3、Mapper和Service
省略,以平常一致
4、通过拦截器修改sql
第2步只是确保了查询时能够转为wkt,但插入和更新会自动使用@TableField注解里的名字,这将导致错误,接下来,就是要修复该错误了。
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
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.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;
@Component
// Intercepts注解用于声明拦截器,此处将会拦截StatementHandler类的prepare(Coonection,Integer)方法
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args ={Connection.class, Integer.class}
)
})
public class GeoInterceptor implements Interceptor {
@Lazy
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取拦截的对象
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 获取该方法所对应的sql命令类型
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 如果是插入和更新命令,则进行处理
if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT || mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
// 获取sql
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
// 移除后面的函数
sql = sql.replaceAll("\\.STAsText\\(\\)", "");
// 将新的sql放回去
Field delegate = RoutingStatementHandler.class.getDeclaredField("delegate");
delegate.setAccessible(true);
Object o = delegate.get(statementHandler);
delegate.setAccessible(false);
Field field = BaseStatementHandler.class.getDeclaredField("boundSql");
field.setAccessible(true);
field.set(o, new BoundSql(sqlSessionFactory.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()));
field.setAccessible(false);
}
// 后续执行
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
至此,大功告成
结语
该拦截器是Mybatis的功能,所以也会对Mapper里面自己手写sql的方法也会生效,若要避免这种情况,需要进行额外处理。
本功能将SqlServer的空间数据类型转为字符串进行传输(插入更新时SqlServer会对字符串自动进行转换,MySQL则必须使用函数转为空间数据才可进行插入更新),后续通过类型处理器可将其转为几何类来方便计算。
总之呢,个人感觉用处不大,本文章仅为学习时的记录。毕竟要用空间数据类型的话,大多数时候都是需要使用空间函数的,所以,倒不如手写SQL还来的快速方便