2021SC@SDUSC
这一篇博客来分析一下SQLConfig,方便进行后续对AbstractSQLConfig的分析
String DATABASE_MYSQL = "MYSQL"; String DATABASE_POSTGRESQL = "POSTGRESQL"; String DATABASE_SQLSERVER = "SQLSERVER"; String DATABASE_ORACLE = "ORACLE"; String DATABASE_DB2 = "DB2"; String DATABASE_CLICKHOUSE = "CLICKHOUSE"; String SCHEMA_INFORMATION = "information_schema"; //MySQL, PostgreSQL, SQL Server 都有的系统模式 String SCHEMA_SYS = "sys"; //SQL Server 系统模式 String TABLE_SCHEMA = "table_schema"; String TABLE_NAME = "table_name";
首先是定义了要用到的一些字符串名称,用DATABASE_MYSQL代指字符串MYSQL,方便后续使用,这是一个很好的方式,在需要用的特定字符串上,这样就添加了备注信息。
int TYPE_CHILD = 0; int TYPE_ITEM = 1; int TYPE_ITEM_CHILD_0 = 2; boolean isMySQL(); boolean isPostgreSQL(); boolean isSQLServer(); boolean isOracle(); boolean isDb2(); boolean isClickHouse(); boolean limitSQLCount(); //用来给 Table, Column 等系统属性表来绕过 MAX_SQL_COUNT 等限制
接下来定义了一些boolean函数
在SQLConfig中,定义的方法由以下代码实现。
代码分析
boolean limitSQLCount(); //用来给 Table, Column 等系统属性表来绕过 MAX_SQL_COUNT 等限制
@Override
public boolean limitSQLCount() {
return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false;
}这里AbstractSQLConfig重写了limitSQLCount()
用到了 apijson.Log 里面的 Log.DEBUG和AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable())
进行判断后返回他们的或结果
后面同样是重写了SQLConfig的方法,后面三百到六百行都是对SQLConfig方法的重写,这里选出重写比较复杂的代码进行解析。
public AbstractSQLConfig setSchema(String schema) { if (schema != null) { String quote = getQuote(); String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); } } this.schema = schema; return this; }
schema是表所在的数据库名,这里调用了apijson.StringUtil这个类,加入了一个对库名的判断是否为空,调用apijson.StringUtil中的方法解决可能的报错。
另一处较为复杂的是对getgroupString()的重写
@JSONField(serialize = false) public String getGroupString(boolean hasPrefix) { //加上子表的 group String joinGroup = ""; if (joinList != null) { SQLConfig cfg; String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); if (StringUtil.isEmpty(cfg.getAlias(), true)) { cfg.setAlias(cfg.getTable()); } c = ((AbstractSQLConfig) cfg).getGroupString(false); if (StringUtil.isEmpty(c, true) == false) { joinGroup += (first ? "" : ", ") + c; first = false; } } } group = StringUtil.getTrimedString(group); String[] keys = StringUtil.split(group); if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; } for (int i = 0; i < keys.length; i++) { if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! if (StringUtil.isName(keys[i]) == false) { throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); } } keys[i] = getKey(keys[i]); } return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); }
首先参数是前缀Prefix,joinList 不为空时,对cfg进行判断,用到了j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig();这里涉及到了StringUtil中的方法,从return中我们可以得知,返回的内容是判断了GROUP BY,返回了SQL中关于分组判断的信息。
下一组比较复杂的方法是String getHavingString(boolean hasPrefix)
知识学习
String getHavingString(boolean hasPrefix)和getgroupString()都用到了下面这个引用
com.alibaba.fastjson.annotation.JSONField
使用场景:字段和方法
1、字段:根据@JSONField(name=“XXX”) 中的name 对string转换为类时name中的描述就是转换后的字段名称
2、方法:在set方法前和在get方法前
当JSON.parseObject(str,class)方法被调用时,set方法被调用,方法上面的@JSONField(name="XXX")中的XXX属性,将json中的XXXkey被赋值到set方法描述的属性中。
当JSON.toJSONString(dto)方法被调用时,get方法被调用,方法上面的@JSONField(name="XXX")中的XXX属性,就是得到的jsonString中的属性的key。
在String getHavingString(boolean hasPrefix)中,前半段跟getgroupString()相同,后面增加了额外的判断机制,用来判断SQL请求中不合法情况的错误。
包括
1.expression.length() > 50 长度过长情况
2.isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false 不符合正则表达式情况
3.start >= end value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式
4.StringUtil.isName(method) == false 必须符合小写英文单词的 SQL 函数名格式
5.SQL_FUNCTION_MAP.containsKey(method) == false function 必须符合小写英文单词的 SQL 函数名格式,且必须是后端允许调用的 SQL 函数
6.isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false) 必须符合正则表达式且不包含连续减号 -- 或注释符 /* !不允许多余的空格
7.origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false 所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式且不包含连续减号 -- !不允许多余的空格
最后正确return一个Having语句。
下面840-1030行同样是一些override,都是调用了this方法,所以我们不进行过多赘述
以下是一个请求方法的错误判断
public String getColumnString(boolean inSQLJoin) throws Exception
return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c;
其中的判断方法与上述的错误类型大致相同,写明了各种错误类型的判断。
public static int getCache(String cache) { int cache2; if (cache == null) { cache2 = JSONRequest.CACHE_ALL; } else { // if (isSubquery) { // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); // } switch (cache) { case "0": case JSONRequest.CACHE_ALL_STRING: cache2 = JSONRequest.CACHE_ALL; break; case "1": case JSONRequest.CACHE_ROM_STRING: cache2 = JSONRequest.CACHE_ROM; break; case "2": case JSONRequest.CACHE_RAM_STRING: cache2 = JSONRequest.CACHE_RAM; break; default: throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); } } return cache2; }
这是一个getCache方法,用到了JSONRequest,而JSONRequest又extends JSONObject,所以我们能看到的JSONRequest.CACHE_ALL_STRING等都来自JSONObject
public static final int CACHE_ALL = 0;
public static final int CACHE_ROM = 1;
public static final int CACHE_RAM = 2;
public static final String CACHE_ALL_STRING = "ALL";
public static final String CACHE_ROM_STRING = "ROM";
public static final String CACHE_RAM_STRING = "RAM";
这些是在JSONObject中定义好的变量
知识学习
MySQL缓存机制即缓存sql 文本及缓存结果,用KV形式保存再服务器内存中,如果运行相同的sql,服务器直接从缓存中去获取结果,不需要再去解析、优化、执行sql
- 服务器接收SQL,以SQL和一些其他条件为key查找缓存表
- 如果缓存命中,则直接返回缓存
- 如果缓存没有命中,则执行SQL查询,包括SQL解析、优化等。
- 执行完SQL查询结果以后,将SQL查询结果写入缓存表
- MySQL缓存机制会在内存中开辟一块内存(query_cache_size)区来维护缓存数据,其中大概有40K的空间是用来维护缓存数据的元数据的,例如空间内存、数据表和查询结果的映射,SQL和查询结果的映射。
- MySQL缓存机制将大内存块分为小内存块(query_cache_min_res_unit),每个小块中存储自身的类型、大小和查询结果数据,还有前后内存块的指针。
- MySQL缓存机制会在SQL查询开始(还未得到结果)时就去申请一块内存空间,所以即使缓存数据没有达到这个大小也需要占用申请的内存块空间(like linux filesystem’s block)。如果超出申请内存块的大小,则需要再申请一个内存块。当查询完成发现申请的内存有富余,则会将富余的内存空间释放掉,因而可能会造成内存碎片。
小结:AbstractSQLConfig重写了其他类的方法,定义了一些方法,与其它类的关系繁杂,所以分析是往往要先去看他引用到的类,而引用到的类又会引用其他类,所以 AbstractSQLConfig是和其它类的关系都是合作完成,这让我真正的一个项目不是单单的先后顺序,而是复杂的交织关系,有不同的类共同完成任务。
以上就是本周对APIJSON项目中AbstractSQLConfig的分析
记录位置为1462行