2021SC@SDUSC
AbstractSQLExecutor—SQL执行器
接着上次的分析,在上次的分析中,我们在execute方法里面是调用了executeAppJoin方法,但在上次并没有对其进行详细的分析,这次便来分析一下它的作用
JOIN查询附表
/**@ APP JOIN 查询副表并缓存到 childMap
* @param config
* @param resultList
* @param childMap
* @throws Exception
*/
protected void executeAppJoin(SQLConfig config, List<JSONObject> resultList, Map<String, JSONObject> childMap) throws Exception {
List<Join> joinList = config.getJoinList();
if (joinList != null) {
SQLConfig jc;
SQLConfig cc;
for (Join j : joinList) {
if (j.isAppJoin() == false) {
continue;
}
cc = j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好
if (cc == null) {
if (Log.DEBUG) {
throw new NullPointerException("服务器内部错误, executeAppJoin cc == null ! 导致不能缓存 @ APP JOIN 的副表数据!");
}
continue;
}
jc = j.getJoinConfig();
//取出 "id@": "@/User/userId" 中所有 userId 的值
List<Object> targetValueList = new ArrayList<>();
JSONObject mainTable;
Object targetValue;
for (int i = 0; i < resultList.size(); i++) {
mainTable = resultList.get(i);
targetValue = mainTable == null ? null : mainTable.get(j.getTargetKey());
if (targetValue != null && targetValueList.contains(targetValue) == false) {
targetValueList.add(targetValue);
}
}
//替换为 "id{}": [userId1, userId2, userId3...]
jc.putWhere(j.getOriginKey(), null, false); // remove orginKey
jc.putWhere(j.getKey() + "{}", targetValueList, true); // add orginKey{}
jc.setMain(true).setPreparedValueList(new ArrayList<>());
boolean prepared = jc.isPrepared();
final String sql = jc.getSQL(false);
jc.setPrepared(prepared);
if (StringUtil.isEmpty(sql, true)) {
throw new NullPointerException(TAG + ".executeAppJoin StringUtil.isEmpty(sql, true) >> return null;");
}
//执行副表的批量查询 并 缓存到 childMap
ResultSet rs = null;
try {
rs = executeQuery(jc);
int index = -1;
ResultSetMetaData rsmd = rs.getMetaData();
final int length = rsmd.getColumnCount();
JSONObject result;
String cacheSql;
while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题
index ++;
Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n");
result = new JSONObject(true);
for (int i = 1; i <= length; i++) {
result = onPutColumn(jc, rs, rsmd, index, result, i, null);
}
//每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result);
//缓存到 childMap
cc.putWhere(j.getKey(), result.get(j.getKey()), true);
cacheSql = cc.getSQL(false);
childMap.put(cacheSql, result);
Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size());
}
}
finally {
if (rs != null) {
try {
rs.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
首先就是获取到相对应的joinlist,这个我们原来在进行查询是都有关联表的时候进行过分析,这个列表存放的是查询所需要的关联的表,如果有副表,那么去除这个列表的每一项,然后就搞了两个sqlconfig对象,分别读出getCacheConfig()和getJoinConfig()对其进行赋值。
然后就是对结果集列表的操作,取出 “id@”: “@/User/userId” 中所有 userId 的值,替换为 “id{}”: [userId1, userId2, userId3…],这一步其实是很好理解,就是一个结果集的替换,然后获取到sql,开始进行从数据库获取数据executeQuery,而这个ResultSetMetaData对象我们在下面会讲。
总结来说,这个executeAppJoin就是先根据副表的内容,去生成不同的sql语言,然后根据不同的sql语言与数据库进行交互从而获得数据,然后讲这些数据在进行合并。
判断是否为JSON类型方法
@Override
public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable) {
try {
String column = rsmd.getColumnTypeName(position);
//return rsmd.getColumnType(position) == 1;
if (column.toLowerCase().contains("json")) {
return true;
}
} catch (SQLException e) {
e.printStackTrace();
}
// List<String> json = config.getJson();
// return json != null && json.contains(lable);
return false;
}
在这个方法中传入的是ResultSetMetaData类的对象,而ResultSetMetaData类存储了 ResultSet的MetaData。所谓的MetaData在英文中的解释为“Data about Data”,直译成中文则为“有关数据的数据”或者“描述数据的数据”,实际上就是描述及解释含义的数据。以Result的MetaData为例,ResultSet是以表格的形式存在,所以getMetaData就包括了数据的字段名称、类型以及数目等表格所必须具备的信息。
我们这里调用的是ResultSetMetaData类的方法:
`String getColumnTypeName(int column)`
这个方法的作用就是检索指定列的数据库特定的类型名称,而对于CHAR和JSON类型的字段,getColumnType返回值都是1 ,如果不用CHAR,改用VARCHAR,则可以用String column = rsmd.getColumnTypeName(position)这个代码来提高性能。
获取statement:
public PreparedStatement getStatement(@NotNull SQLConfig config) throws Exception {
PreparedStatement statement; //创建Statement对象
if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id
statement = getConnection(config).prepareStatement(config.getSQL(config.isPrepared()), Statement.RETURN_GENERATED_KEYS);
}
else {
statement = getConnection(config).prepareStatement(config.getSQL(config.isPrepared()));
}
List<Object> valueList = config.isPrepared() ? config.getPreparedValueList() : null;
if (valueList != null && valueList.isEmpty() == false) {
for (int i = 0; i < valueList.size(); i++) {
statement = setArgument(config, statement, i, valueList.get(i));
}
}
// statement.close();
return statement;
}
在这里面大多基本是和我们的JDBC相同,但是其中主要是与config进行了信息的传递,通过config获得相应的statement
在这里面将SQL的配置对象传入,然后就是根据getId的返回是不是null也就是说是不是自增的id,根据这两种情况返回不同的statement。
获取connection:
public Connection getConnection(@NotNull SQLConfig config) throws Exception {
String connectionKey = config.getDatasource() + "-" + config.getDatabase();
connection = connectionMap.get(connectionKey);
if (connection == null || connection.isClosed()) {
Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ;
// PostgreSQL 不允许 cross-database
connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword());
connectionMap.put(connectionKey, connection);
}
int ti = getTransactionIsolation();
if (ti != Connection.TRANSACTION_NONE) { //java.sql.SQLException: Transaction isolation level NONE not supported by MySQL
begin(ti);
}
return connection;
}
这个方法的核心作用就是说针对不同的数据库比如MySQL或者PostgreSQL这样的数据库,根据它们的数据库类型从而获得相应的connection对象。
关闭连接:
public void close() {
cacheMap.clear();
cacheMap = null;
generatedSQLCount = 0;
cachedSQLCount = 0;
executedSQLCount = 0;
if (connectionMap == null) {
return;
}
Collection<Connection> connections = connectionMap.values();
if (connections != null) {
for (Connection connection : connections) {
try {
if (connection != null && connection.isClosed() == false) {
connection.close();
}
}
catch (SQLException e) {
e.printStackTrace();
}
}
}
connectionMap.clear();
connectionMap = null;
}
这个方法很好理解,就是讲应用的各种资源去归零也就是释放资源,然后就是对于当前连接的所有connection去进行一个循环关闭。