) where;
+ return fullMatch(parenthesis.get(0), logicField);
}
return false;
diff --git a/solon-plugin-data-sql/mybatis-plus-extension-solon-plugin/src/main/java/com/baomidou/mybatisplus/solon/plugins/inner/DataChangeRecorderInnerInterceptor.java b/solon-plugin-data-sql/mybatis-plus-extension-solon-plugin/src/main/java/com/baomidou/mybatisplus/solon/plugins/inner/DataChangeRecorderInnerInterceptor.java
index 0037eeecfa3fa6475437ef2bee755700662411fb..28dd83e27b0c248870803586101ebc05819777e3 100644
--- a/solon-plugin-data-sql/mybatis-plus-extension-solon-plugin/src/main/java/com/baomidou/mybatisplus/solon/plugins/inner/DataChangeRecorderInnerInterceptor.java
+++ b/solon-plugin-data-sql/mybatis-plus-extension-solon-plugin/src/main/java/com/baomidou/mybatisplus/solon/plugins/inner/DataChangeRecorderInnerInterceptor.java
@@ -15,44 +15,77 @@
*/
package com.baomidou.mybatisplus.solon.plugins.inner;
+import java.io.Reader;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import net.sf.jsqlparser.statement.select.Values;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.ParameterMapping;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
+import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
+import com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler;
+import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.solon.parser.JsqlParserGlobal;
+
import lombok.Data;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.JdbcParameter;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.expression.RowConstructor;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
-import net.sf.jsqlparser.statement.select.*;
+import net.sf.jsqlparser.statement.select.AllColumns;
+import net.sf.jsqlparser.statement.select.FromItem;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
+import net.sf.jsqlparser.statement.select.SelectItem;
+import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
-import org.apache.ibatis.executor.statement.StatementHandler;
-import org.apache.ibatis.mapping.BoundSql;
-import org.apache.ibatis.mapping.MappedStatement;
-import org.apache.ibatis.mapping.ParameterMapping;
-import org.apache.ibatis.mapping.SqlCommandType;
-import org.apache.ibatis.reflection.MetaObject;
-import org.apache.ibatis.reflection.SystemMetaObject;
-import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.Reader;
-import java.math.BigDecimal;
-import java.sql.*;
-import java.util.Date;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
/**
*
* 数据变动记录插件
* 默认会生成一条log,格式:
* ----------------------INSERT LOG------------------------------
+ *
+ *
* {
* "tableName": "h2user",
* "operation": "insert",
@@ -90,12 +123,17 @@ import java.util.concurrent.ConcurrentHashMap;
* @date 2022-8-21
*/
public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
+
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@SuppressWarnings("unused")
public static final String IGNORED_TABLE_COLUMN_PROPERTIES = "ignoredTableColumns";
private final Map> ignoredTableColumns = new ConcurrentHashMap<>();
private final Set ignoreAllColumns = new HashSet<>();//全部表的这些字段名,INSERT/UPDATE都忽略,delete暂时保留
+ //批量更新上限, 默认一次最多1000条
+ private int BATCH_UPDATE_LIMIT = 1000;
+ private boolean batchUpdateLimitationOpened = false;
+ private final Map BATCH_UPDATE_LIMIT_MAP = new ConcurrentHashMap<>();//表名->批量更新上限
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
@@ -108,7 +146,7 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
OperationResult operationResult;
long startTs = System.currentTimeMillis();
try {
- Statement statement = CCJSqlParserUtil.parse(mpBs.sql());
+ Statement statement = JsqlParserGlobal.parse(mpBs.sql());
if (statement instanceof Insert) {
operationResult = processInsert((Insert) statement, mpSh.boundSql());
} else if (statement instanceof Update) {
@@ -120,6 +158,9 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
return;
}
} catch (Exception e) {
+ if (e instanceof DataUpdateLimitationException) {
+ throw (DataUpdateLimitationException) e;
+ }
logger.error("Unexpected error for mappedStatement={}, sql={}", ms.getId(), mpBs.sql(), e);
return;
}
@@ -153,58 +194,85 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
}
public OperationResult processInsert(Insert insertStmt, BoundSql boundSql) {
+ String operation = SqlCommandType.INSERT.name().toLowerCase();
+ Table table = insertStmt.getTable();
+ String tableName = table.getName();
+ Optional optionalOperationResult = ignoredTableColumns(tableName, operation);
+ if (optionalOperationResult.isPresent()) {
+ return optionalOperationResult.get();
+ }
OperationResult result = new OperationResult();
- result.setOperation("insert");
- result.setTableName(insertStmt.getTable().getName());
+ result.setOperation(operation);
+ result.setTableName(tableName);
result.setRecordStatus(true);
- result.buildDataStr(compareAndGetUpdatedColumnDatas(result.getTableName(), boundSql, insertStmt, null));
+ Map updatedColumnDatas = getUpdatedColumnDatas(tableName, boundSql, insertStmt);
+ result.buildDataStr(compareAndGetUpdatedColumnDatas(result.getTableName(), null, updatedColumnDatas));
return result;
}
public OperationResult processUpdate(Update updateStmt, MappedStatement mappedStatement, BoundSql boundSql, Connection connection) {
Expression where = updateStmt.getWhere();
- Select selectStmt = new Select();
PlainSelect selectBody = new PlainSelect();
Table table = updateStmt.getTable();
- final Set ignoredColumns = ignoredTableColumns.get(table.getName().toUpperCase());
- if (ignoredColumns != null) {
- if (ignoredColumns.stream().anyMatch("*"::equals)) {
- OperationResult result = new OperationResult();
- result.setOperation("update");
- result.setTableName(table.getName() + ":*");
- result.setRecordStatus(false);
- return result;
- }
+ String tableName = table.getName();
+ String operation = SqlCommandType.UPDATE.name().toLowerCase();
+ Optional optionalOperationResult = ignoredTableColumns(tableName, operation);
+ if (optionalOperationResult.isPresent()) {
+ return optionalOperationResult.get();
}
selectBody.setFromItem(table);
List updateColumns = new ArrayList<>();
for (UpdateSet updateSet : updateStmt.getUpdateSets()) {
updateColumns.addAll(updateSet.getColumns());
}
- Columns2SelectItemsResult buildColumns2SelectItems = buildColumns2SelectItems(table.getName(), updateColumns);
+ Columns2SelectItemsResult buildColumns2SelectItems = buildColumns2SelectItems(tableName, updateColumns);
selectBody.setSelectItems(buildColumns2SelectItems.getSelectItems());
selectBody.setWhere(where);
- selectStmt.setSelectBody(selectBody);
+ SelectItem plainSelectSelectItem = new SelectItem<>(selectBody);
- BoundSql boundSql4Select = new BoundSql(mappedStatement.getConfiguration(), selectStmt.toString(),
+ BoundSql boundSql4Select = new BoundSql(mappedStatement.getConfiguration(), plainSelectSelectItem.toString(),
prepareParameterMapping4Select(boundSql.getParameterMappings(), updateStmt),
boundSql.getParameterObject());
- MetaObject metaObject = SystemMetaObject.forObject(boundSql);
- Map additionalParameters = (Map) metaObject.getValue("additionalParameters");
+ PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
+ Map additionalParameters = mpBoundSql.additionalParameters();
if (additionalParameters != null && !additionalParameters.isEmpty()) {
for (Map.Entry ety : additionalParameters.entrySet()) {
boundSql4Select.setAdditionalParameter(ety.getKey(), ety.getValue());
}
}
- OriginalDataObj originalData = buildOriginalObjectData(selectStmt, buildColumns2SelectItems.getPk(), mappedStatement, boundSql4Select, connection);
+ Map updatedColumnDatas = getUpdatedColumnDatas(tableName, boundSql, updateStmt);
+ OriginalDataObj originalData = buildOriginalObjectData(updatedColumnDatas, selectBody, buildColumns2SelectItems.getPk(), mappedStatement, boundSql4Select, connection);
OperationResult result = new OperationResult();
- result.setOperation("update");
- result.setTableName(table.getName());
+ result.setOperation(operation);
+ result.setTableName(tableName);
result.setRecordStatus(true);
- result.buildDataStr(compareAndGetUpdatedColumnDatas(result.getTableName(), boundSql, updateStmt, originalData));
+ result.buildDataStr(compareAndGetUpdatedColumnDatas(result.getTableName(), originalData, updatedColumnDatas));
return result;
}
+ private Optional ignoredTableColumns(String table, String operation) {
+ final Set ignoredColumns = ignoredTableColumns.get(table.toUpperCase());
+ if (ignoredColumns != null) {
+ if (ignoredColumns.stream().anyMatch("*"::equals)) {
+ OperationResult result = new OperationResult();
+ result.setOperation(operation);
+ result.setTableName(table + ":*");
+ result.setRecordStatus(false);
+ return Optional.of(result);
+ }
+ }
+ return Optional.empty();
+ }
+
+ private TableInfo getTableInfoByTableName(String tableName) {
+ for (TableInfo tableInfo : TableInfoHelper.getTableInfos()) {
+ if (tableName.equalsIgnoreCase(tableInfo.getTableName())) {
+ return tableInfo;
+ }
+ }
+ return null;
+ }
+
/**
* 将update SET部分的jdbc参数去除
*
@@ -215,7 +283,7 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
private List prepareParameterMapping4Select(List originalMappingList, Update updateStmt) {
List updateValueExpressions = new ArrayList<>();
for (UpdateSet updateSet : updateStmt.getUpdateSets()) {
- updateValueExpressions.addAll(updateSet.getExpressions());
+ updateValueExpressions.addAll(updateSet.getValues());
}
int removeParamCount = 0;
for (Expression expression : updateValueExpressions) {
@@ -226,48 +294,78 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
return originalMappingList.subList(removeParamCount, originalMappingList.size());
}
- /**
- * @param updateSql
- * @param originalDataObj
- * @return
- */
- private List compareAndGetUpdatedColumnDatas(String tableName, BoundSql updateSql, Statement statement, OriginalDataObj originalDataObj) {
+ protected Map getUpdatedColumnDatas(String tableName, BoundSql updateSql, Statement statement) {
Map columnNameValMap = new HashMap<>(updateSql.getParameterMappings().size());
+ Map columnSetIndexMap = new HashMap<>(updateSql.getParameterMappings().size());
List selectItemsFromUpdateSql = new ArrayList<>();
if (statement instanceof Update) {
Update updateStmt = (Update) statement;
+ int index = 0;
for (UpdateSet updateSet : updateStmt.getUpdateSets()) {
selectItemsFromUpdateSql.addAll(updateSet.getColumns());
- final List updateList = updateSet.getExpressions();
+ final ExpressionList updateList = (ExpressionList) updateSet.getValues();
for (int i = 0; i < updateList.size(); ++i) {
Expression updateExps = updateList.get(i);
if (!(updateExps instanceof JdbcParameter)) {
columnNameValMap.put(updateSet.getColumns().get(i).getColumnName().toUpperCase(), updateExps.toString());
}
+ columnSetIndexMap.put(index++, updateSet.getColumns().get(i).getColumnName().toUpperCase());
}
}
} else if (statement instanceof Insert) {
- selectItemsFromUpdateSql.addAll(((Insert) statement).getColumns());
+ Insert insert = (Insert) statement;
+ selectItemsFromUpdateSql.addAll(insert.getColumns());
+ columnNameValMap.putAll(detectInsertColumnValuesNonJdbcParameters(insert));
}
Map relatedColumnsUpperCaseWithoutUnderline = new HashMap<>(selectItemsFromUpdateSql.size(), 1);
for (Column item : selectItemsFromUpdateSql) {
//FIRSTNAME: FIRST_NAME/FIRST-NAME/FIRST$NAME/FIRST.NAME
- relatedColumnsUpperCaseWithoutUnderline.put(item.toString().replaceAll("[._\\-$]", "").toUpperCase(), item.toString().toUpperCase());
+ relatedColumnsUpperCaseWithoutUnderline.put(item.getColumnName().replaceAll("[._\\-$]", "").toUpperCase(), item.getColumnName().toUpperCase());
}
MetaObject metaObject = SystemMetaObject.forObject(updateSql.getParameterObject());
-
+ int index = 0;
for (ParameterMapping parameterMapping : updateSql.getParameterMappings()) {
String propertyName = parameterMapping.getProperty();
if (propertyName.startsWith("ew.paramNameValuePairs")) {
+ ++index;
continue;
}
String[] arr = propertyName.split("\\.");
String propertyNameTrim = arr[arr.length - 1].replace("_", "").toUpperCase();
- if (relatedColumnsUpperCaseWithoutUnderline.containsKey(propertyNameTrim)) {
- columnNameValMap.put(relatedColumnsUpperCaseWithoutUnderline.get(propertyNameTrim), metaObject.getValue(propertyName));
+ try {
+ final String columnName = columnSetIndexMap.getOrDefault(index++, getColumnNameByProperty(propertyNameTrim, tableName));
+ if (relatedColumnsUpperCaseWithoutUnderline.containsKey(propertyNameTrim)) {
+ final String colkey = relatedColumnsUpperCaseWithoutUnderline.get(propertyNameTrim);
+ Object valObj = metaObject.getValue(propertyName);
+ if (valObj instanceof IEnum) {
+ valObj = ((IEnum>) valObj).getValue();
+ } else if (valObj instanceof Enum) {
+ valObj = getEnumValue((Enum) valObj);
+ }
+ if (columnNameValMap.containsKey(colkey)) {
+ columnNameValMap.put(relatedColumnsUpperCaseWithoutUnderline.get(propertyNameTrim), String.valueOf(columnNameValMap.get(colkey)).replace("?", valObj == null ? "" : valObj.toString()));
+ }
+ if (columnName != null && !columnNameValMap.containsKey(columnName)) {
+ columnNameValMap.put(columnName, valObj);
+ }
+ } else {
+ if (columnName != null) {
+ columnNameValMap.put(columnName, metaObject.getValue(propertyName));
+ }
+ }
+ } catch (Exception e) {
+ logger.warn("get value error,propertyName:{},parameterMapping:{}", propertyName, parameterMapping);
}
}
+ dealWithUpdateWrapper(columnSetIndexMap, columnNameValMap, updateSql);
+ return columnNameValMap;
+ }
+ /**
+ * @param originalDataObj
+ * @return
+ */
+ private List compareAndGetUpdatedColumnDatas(String tableName, OriginalDataObj originalDataObj, Map columnNameValMap) {
final Set ignoredColumns = ignoredTableColumns.get(tableName.toUpperCase());
if (originalDataObj == null || originalDataObj.isEmpty()) {
DataChangedRecord oneRecord = new DataChangedRecord();
@@ -279,6 +377,7 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
}
}
oneRecord.setUpdatedColumns(updateColumns);
+// oneRecord.setUpdatedColumns(Collections.EMPTY_LIST);
return Collections.singletonList(oneRecord);
}
List originalDataList = originalDataObj.getOriginalDataObj();
@@ -291,9 +390,112 @@ public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
return updateDataList;
}
+ private Object getEnumValue(Enum enumVal) {
+ Optional enumValueFieldName = MybatisEnumTypeHandler.findEnumValueFieldName(enumVal.getClass());
+ if (enumValueFieldName.isPresent()) {
+ return SystemMetaObject.forObject(enumVal).getValue(enumValueFieldName.get());
+ }
+ return enumVal;
+
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void dealWithUpdateWrapper(Map columnSetIndexMap, Map columnNameValMap, BoundSql updateSql) {
+ if (columnSetIndexMap.size() <= columnNameValMap.size()) {
+ return;
+ }
+ MetaObject mpgenVal = SystemMetaObject.forObject(updateSql.getParameterObject());
+ if(!mpgenVal.hasGetter(Constants.WRAPPER)){
+ return;
+ }
+ Object ew = mpgenVal.getValue(Constants.WRAPPER);
+ if (ew instanceof UpdateWrapper || ew instanceof LambdaUpdateWrapper) {
+ final String sqlSet = ew instanceof UpdateWrapper ? ((UpdateWrapper) ew).getSqlSet() : ((LambdaUpdateWrapper) ew).getSqlSet();// columnName=#{val}
+ if (sqlSet == null) {
+ return;
+ }
+ MetaObject ewMeta = SystemMetaObject.forObject(ew);
+ final Map paramNameValuePairs = (Map) ewMeta.getValue("paramNameValuePairs");
+ String[] setItems = sqlSet.split(",");
+ for (String setItem : setItems) {
+ //age=#{ew.paramNameValuePairs.MPGENVAL1}
+ String[] nameAndValuePair = setItem.split("=", 2);
+ if (nameAndValuePair.length == 2) {
+ String setColName = nameAndValuePair[0].trim().toUpperCase();
+ String setColVal = nameAndValuePair[1].trim();//#{.mp}
+ if (columnSetIndexMap.containsValue(setColName)) {
+ String[] mpGenKeyArray = setColVal.split("\\.");
+ String mpGenKey = mpGenKeyArray[mpGenKeyArray.length - 1].replace("}", "");
+ final Object setVal = paramNameValuePairs.get(mpGenKey);
+ if (setVal instanceof IEnum) {
+ columnNameValMap.put(setColName, String.valueOf(((IEnum>) setVal).getValue()));
+ } else {
+ columnNameValMap.put(setColName, setVal);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Map detectInsertColumnValuesNonJdbcParameters(Insert insert) {
+ Map columnNameValMap = new HashMap<>(4);
+ final Select select = insert.getSelect();
+ List columns = insert.getColumns();
+ if (select instanceof SetOperationList) {
+ SetOperationList setOperationList = (SetOperationList) select;
+ final List