APIJSON 博客3 AbstractSQLConfig 第三篇

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

  1. 服务器接收SQL,以SQL和一些其他条件为key查找缓存表
  2. 如果缓存命中,则直接返回缓存
  3. 如果缓存没有命中,则执行SQL查询,包括SQL解析、优化等。
  4. 执行完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行

 

 

上一篇:SQL注入中information_schema的作用


下一篇:什么是数据湖 Data Lake