MySQL
对一般数据类型支持的都很好,可是Geometry
类型数据处理就比较麻烦。
前两天看到地理坐标类型的数据,其实不建议使用MySQL
,一般用Redis
或者MongoDB
,还有PostgreSQL
+PostGIS
不要看不起MySQL
,其实你与Geometry
之间只差一个拦截器。
之前写过一个拦截器,一开始的时候还觉得写的不错,现在回过头再来看,真的是一塌糊涂啊。
核心就是对SQL
解析,找到占位符的位置,然后添加下面的函数。
- 新增要使用SQL函数:
ST_GEOMFROMTEXT()
- 查询要使用SQL函数:
ST_ASTEXT()
一开始试了一下查询没什么问题,后来使用的时候发现问题不少。
- 由于是解析
SQL
的占位符,然后记录下来位置,然后在对应位置添加函数,使用Mybatis-Plus
自带的查询函数,偶尔会出问题,因为这个位置可能会不一致,有时候会变,几率很小。 - 自己写
SQL
,百分百会出问题。 - 增加和修改也支持的不好。
所以今天就来重新写一下,之前也是没有好的思路,所以用解析SQL
字符串的方式,今天就来换一个方法。使用反射。
不过有一点得注意,就是数据库里的列的名称和Java对象的属性要对应,只能是使用下换线转驼峰的命名规则,否则还是不行。
import com.ler.manager.annotation.Geometry;
import com.ler.manager.util.CommonUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @author lww
* @date 2020-08-01 18:35
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@Slf4j
public class MybatisGeometryInterceptor 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();
StringBuilder sb = new StringBuilder();
sb.append(SqlCommandType.SELECT.name());
String originalSql = boundSql.getSql();
String reSql = originalSql.replace(SqlCommandType.SELECT.name(), "");
int from = reSql.indexOf("FROM");
Field[] declaredFields = type.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
Geometry annotation = field.getAnnotation(Geometry.class);
String filedName = CommonUtil.humpToUnderline(field.getName());
if ("serialVersionUID".equalsIgnoreCase(filedName)) {
continue;
}
if (annotation == null) {
sb.append(" ").append(filedName).append(" ,");
} else {
sb.append("ST_ASTEXT(").append(filedName).append(") AS ").append(filedName).append(" ,");
}
}
sb.deleteCharAt(sb.length() - 1);
String lastSql = reSql.substring(from);
sb.append(lastSql);
metaObject.setValue("delegate.boundSql.sql", sb.toString());
metaObject.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
metaObject.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
return invocation.proceed();
} else if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
Object paramObj = boundSql.getParameterObject();
Field[] fields;
if (paramObj instanceof HashMap) {
fields = ((HashMap) paramObj).get("et").getClass().getDeclaredFields();
} else {
fields = paramObj.getClass().getDeclaredFields();
}
String originalSql = boundSql.getSql();
int start = originalSql.indexOf("(");
int end = originalSql.indexOf(")");
String paramNames = originalSql.substring(start + 1, end);
String[] split = paramNames.split(",");
originalSql = originalSql.toUpperCase();
String substring = originalSql.substring(0, originalSql.indexOf("VALUES"));
StringBuilder sb = new StringBuilder();
sb.append(substring).append(" ").append("VALUES ").append("( ");
for (String paramName : split) {
for (Field field : fields) {
if (field.getName().trim().equalsIgnoreCase(CommonUtil.underlineToHump(paramName.trim()))) {
Geometry annotation = field.getAnnotation(Geometry.class);
if (annotation != null) {
sb.append("ST_GEOMFROMTEXT(").append(" ? ").append(")").append(" ,");
} else {
sb.append("?").append(" ,");
}
}
}
}
sb.deleteCharAt(sb.length() - 1).append(" )");
metaObject.setValue("delegate.boundSql.sql", sb.toString());
return invocation.proceed();
} else if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) {
StringBuilder sb = new StringBuilder();
Object paramObj = boundSql.getParameterObject();
Field[] fields;
if (paramObj instanceof HashMap) {
fields = ((HashMap) paramObj).get("et").getClass().getDeclaredFields();
} else {
fields = paramObj.getClass().getDeclaredFields();
}
String originalSql = boundSql.getSql();
originalSql = originalSql.toUpperCase();
int start = originalSql.indexOf("SET");
int end = originalSql.indexOf("WHERE");
sb.append(originalSql, 0, start + 4);
String params = originalSql.substring(start + 4, end);
String after = params.replace("=", "").replace("?", "");
String[] split = after.split(",");
for (String paramName : split) {
for (Field field : fields) {
if (field.getName().trim().equalsIgnoreCase(CommonUtil.underlineToHump(paramName.trim()))) {
sb.append(paramName).append("=");
Geometry annotation = field.getAnnotation(Geometry.class);
if (annotation != null) {
sb.append("ST_GEOMFROMTEXT(").append(" ? ").append(")").append(" ,");
} else {
sb.append("?").append(" ,");
}
}
}
}
sb.deleteCharAt(sb.length() - 1).append(originalSql.substring(end));
metaObject.setValue("delegate.boundSql.sql", sb.toString());
return invocation.proceed();
}
return invocation.proceed();
}
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) {
}
}
完整的拦截器代码就是上面这个了。
查询拦截
核心就是这个
for (Field field : declaredFields) {
field.setAccessible(true);
Geometry annotation = field.getAnnotation(Geometry.class);
String filedName = CommonUtil.humpToUnderline(field.getName());
if ("serialVersionUID".equalsIgnoreCase(filedName)) {
continue;
}
if (annotation == null) {
sb.append(" ").append(filedName).append(" ,");
} else {
sb.append("ST_ASTEXT(").append(filedName).append(") AS ").append(filedName).append(" ,");
}
}
把类中的属性转为下划线命名,然后判断该属性上是否有注解Geometry
,有的话就用函数包裹一下。
插入拦截
插入就比较麻烦一点。
if (paramObj instanceof HashMap) {
fields = ((HashMap) paramObj).get("et").getClass().getDeclaredFields();
} else {
fields = paramObj.getClass().getDeclaredFields();
}
第一个get("et")
,是因为使用Mybatis-Plus
自带的更新或者插入函数,他是把对象作为Map
处理的,真正的参数是key
为et
的value
。
如果自己写SQL
就是对象。
核心是:
for (String paramName : split) {
for (Field field : fields) {
if (field.getName().trim().equalsIgnoreCase(CommonUtil.underlineToHump(paramName.trim()))) {
Geometry annotation = field.getAnnotation(Geometry.class);
if (annotation != null) {
sb.append("ST_GEOMFROMTEXT(").append(" ? ").append(")").append(" ,");
} else {
sb.append("?").append(" ,");
}
}
}
}
具体步骤是: 获取原SQL
,获取到要注入的列,判断该列在Java
对象中对应的属性上是否有注解,有的话就用函数包裹。
更新拦截
更新和插入差不多,不过一个是UPDATE
,一个是INSERT
核心:
for (String paramName : split) {
for (Field field : fields) {
if (field.getName().trim().equalsIgnoreCase(CommonUtil.underlineToHump(paramName.trim()))) {
sb.append(paramName).append("=");
Geometry annotation = field.getAnnotation(Geometry.class);
if (annotation != null) {
sb.append("ST_GEOMFROMTEXT(").append(" ? ").append(")").append(" ,");
} else {
sb.append("?").append(" ,");
}
}
}
}
核心代码都很相似,就多了一个=
号。
最后删除就不用拦截了。
至此就完成了,来测试看一下效果
测试
- 增加
- 查询
- 更新
可以看到,不论是使用Mybatis-Plus
自带的方法还是自己写SQL
,都能完美拦截,并且使用函数包裹。
用到的两个工具方法:
/**
* 驼峰转下划线
*/
public static String humpToUnderline(String str) {
String regex = "([A-Z])";
Matcher matcher = Pattern.compile(regex).matcher(str);
while (matcher.find()) {
String target = matcher.group();
str = str.replaceAll(target, "_" + target.toLowerCase());
}
return str;
}
/**
* 下划线转驼峰
*/
public static String underlineToHump(String str) {
String regex = "_(.)";
Matcher matcher = Pattern.compile(regex).matcher(str);
while (matcher.find()) {
String target = matcher.group(1);
str = str.replaceAll("_" + target, target.toUpperCase());
}
return str;
}
新的拦截器MybatisGeometryInterceptor
就这样了,欢迎大家尝试。
一定要注意,数据库中的字段名称和Java
类的属性要是下划线和驼峰的对应关系。
最后欢迎大家关注我的公众号
南诏Blog
共同学习,一起进步。加油🤣