All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pugwoo.dbhelper.sql.SQLUtils Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package com.pugwoo.dbhelper.sql;

import com.pugwoo.dbhelper.annotation.*;
import com.pugwoo.dbhelper.enums.DatabaseEnum;
import com.pugwoo.dbhelper.enums.FeatureEnum;
import com.pugwoo.dbhelper.enums.JoinTypeEnum;
import com.pugwoo.dbhelper.exception.*;
import com.pugwoo.dbhelper.impl.DBHelperContext;
import com.pugwoo.dbhelper.json.NimbleOrmJSON;
import com.pugwoo.dbhelper.utils.*;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SQL解析工具类
 * 
 * @author pugwoo
 * 2017年3月16日 23:02:47
 */
public class SQLUtils {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(SQLUtils.class);

	/**
	 * select 字段 from t_table, 不包含where子句及以后的语句
	 * @param clazz 注解了Table的类
	 * @param selectOnlyKey 是否只查询key
	 * @param isSelect1 是否只select 1,不查询实际字段;当该值为true时,selectOnlyKey无效。
	 * @param features 将dbHelper的特性开关传入,用于处理生成的SQL
	 * @param postSql 将postSql传入,目前仅用于确定select 1字段的附加computed字段是否加入
	 * @return 返回拼凑返回的SQL
	 */
	public static String getSelectSQL(Class clazz, boolean selectOnlyKey, boolean isSelect1,
									  Map features, String postSql) {
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT ");

		// 处理join方式clazz
		JoinTable joinTable = DOInfoReader.getJoinTable(clazz);
		if(joinTable != null) {
			Field leftTableField = DOInfoReader.getJoinLeftTable(clazz);
			Field rightTableField = DOInfoReader.getJoinRightTable(clazz);
			
			JoinLeftTable joinLeftTable = leftTableField.getAnnotation(JoinLeftTable.class);
			JoinRightTable joinRightTable = rightTableField.getAnnotation(JoinRightTable.class);

			if(isSelect1) {
			    sql.append("1");
				String computedColumnsForCountSelect = getComputedColumnsForCountSelect(
						leftTableField.getType(), joinLeftTable.alias() + ".", features, postSql);
				if (InnerCommonUtils.isNotBlank(computedColumnsForCountSelect)) {
					sql.append(",").append(computedColumnsForCountSelect);
				}
				computedColumnsForCountSelect = getComputedColumnsForCountSelect(
						rightTableField.getType(), joinRightTable.alias() + ".", features, postSql);
				if (InnerCommonUtils.isNotBlank(computedColumnsForCountSelect)) {
					sql.append(",").append(computedColumnsForCountSelect);
				}
            } else {
                List fields1 = DOInfoReader.getColumnsForSelect(leftTableField.getType(), selectOnlyKey);
                List fields2 = DOInfoReader.getColumnsForSelect(rightTableField.getType(), selectOnlyKey);
                sql.append(join(fields1, ",", joinLeftTable.alias() + ".", features));
                sql.append(",");
                sql.append(join(fields2, ",", joinRightTable.alias() + ".", features));
            }

	        sql.append(" FROM ").append(getTableName(leftTableField.getType()))
	           .append(" ").append(joinLeftTable.alias()).append(" ");
			if (InnerCommonUtils.isNotBlank(joinLeftTable.forceIndex())) {
				sql.append(" FORCE INDEX(").append(joinLeftTable.forceIndex()).append(") ");
			}
	        sql.append(joinTable.joinType().getCode()).append(" ");
	        sql.append(getTableName(rightTableField.getType())).append(" ").append(joinRightTable.alias());
			if (InnerCommonUtils.isNotBlank(joinRightTable.forceIndex())) {
				sql.append(" FORCE INDEX(").append(joinRightTable.forceIndex()).append(") ");
			}
	        if(InnerCommonUtils.isBlank(joinTable.on())) {
	        	throw new OnConditionIsNeedException("join table :" + clazz.getName());
	        }
	        sql.append(" on ").append(joinTable.on());
	        
		} else {
			Table table = DOInfoReader.getTable(clazz);
			if (InnerCommonUtils.isNotBlank(table.virtualTableSQL())) {
				if (InnerCommonUtils.isNotBlank(table.value())) {
					LOGGER.warn("table DO class:{} table name:{} is ignored because virtualTableSQL has value:{}",
							clazz, table.value(), table.virtualTableSQL());
				}
				return table.virtualTableSQL();
			}
			if (InnerCommonUtils.isNotBlank(table.virtualTablePath())) {
				if (InnerCommonUtils.isNotBlank(table.value())) {
					LOGGER.warn("table DO class:{} table name:{} is ignored because virtualTablePath has value:{}",
							clazz, table.value(), table.virtualTableSQL());
				}
				return InnerCommonUtils.readClasspathResourceAsString(table.virtualTablePath());
			}

			if(isSelect1) {
			    sql.append("1");
				String computedColumnsForCountSelect = getComputedColumnsForCountSelect(
						clazz, null, features, postSql);
				if (InnerCommonUtils.isNotBlank(computedColumnsForCountSelect)) {
					sql.append(",").append(computedColumnsForCountSelect);
				}
			} else {
                List fields = DOInfoReader.getColumnsForSelect(clazz, selectOnlyKey);
                sql.append(join(fields, ",", features));
            }

			sql.append(" FROM ").append(getTableName(clazz)).append(" ").append(table.alias());
		}
		
		return sql.toString();
	}

	/**
	 * 获得计算列同时也是postSql中出现的列的Column的集合
	 */
	private static String getComputedColumnsForCountSelect(Class clazz, String fieldPrefix,
													Map features, String postSql) {
		List fields = DOInfoReader.getColumnsForSelect(clazz, false);

		List field2 = new ArrayList<>();
		for (Field field : fields) {
			Column column = field.getAnnotation(Column.class);
			if (column != null && InnerCommonUtils.isNotBlank(column.computed())) {
				// 这里用简单的postSql是否出现计算列的字符串来判断计算列是否要加入,属于放宽松的做法,程序不会有bug,但优化有空间
				if (postSql != null && postSql.contains(column.value())) {
					field2.add(field);
				}
			}
		}

		if (field2.isEmpty()) {
			return "";
		} else {
			return join(field2, ",", fieldPrefix, features);
		}
	}
	
	/**
	 * select count(1) from t_table, 不包含where子句及以后的语句
	 * @param clazz 注解了Table的表
	 * @return 生成的SQL
	 */
	public static String getSelectCountSQL(Class clazz) {
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT count(*)");
		
		// 处理join方式clazz
		JoinTable joinTable = DOInfoReader.getJoinTable(clazz);
		if(joinTable != null) {
			Field leftTableField = DOInfoReader.getJoinLeftTable(clazz);
			Field rightTableField = DOInfoReader.getJoinRightTable(clazz);
			
			JoinLeftTable joinLeftTable = leftTableField.getAnnotation(JoinLeftTable.class);
			JoinRightTable joinRightTable = rightTableField.getAnnotation(JoinRightTable.class);

	        sql.append(" FROM ").append(getTableName(leftTableField.getType()))
	           .append(" ").append(joinLeftTable.alias()).append(" ");
			if (InnerCommonUtils.isNotBlank(joinLeftTable.forceIndex())) {
				sql.append(" FORCE INDEX(").append(joinLeftTable.forceIndex()).append(") ");
			}
	        sql.append(joinTable.joinType().getCode()).append(" ");
	        sql.append(getTableName(rightTableField.getType())).append(" ").append(joinRightTable.alias());
			if (InnerCommonUtils.isNotBlank(joinRightTable.forceIndex())) {
				sql.append(" FORCE INDEX(").append(joinRightTable.forceIndex()).append(") ");
			}
	        if(InnerCommonUtils.isBlank(joinTable.on())) {
	        	throw new OnConditionIsNeedException("join table VO:" + clazz.getName());
	        }
	        sql.append(" on ").append(joinTable.on());
	        
		} else {
			Table table = DOInfoReader.getTable(clazz);
			if (InnerCommonUtils.isNotBlank(table.virtualTableSQL())) {
				if (InnerCommonUtils.isNotBlank(table.value())) {
					LOGGER.warn("table DO class:{} table name:{} is ignored because virtualTable has value:{}",
							clazz, table.value(), table.virtualTableSQL());
				}
				sql.append(" FROM ( ").append(table.virtualTableSQL()).append(" )");
				return sql.toString();
			}
			if (InnerCommonUtils.isNotBlank(table.virtualTablePath())) {
				if (InnerCommonUtils.isNotBlank(table.value())) {
					LOGGER.warn("table DO class:{} table name:{} is ignored because virtualTablePath has value:{}",
							clazz, table.value(), table.virtualTablePath());
				}
				String vSQL = InnerCommonUtils.readClasspathResourceAsString(table.virtualTablePath());
				sql.append(" FROM ( ").append(vSQL).append(" )");
				return sql.toString();
			}

			sql.append(" FROM ").append(getTableName(clazz)).append(" ").append(table.alias());
		}
		
		return sql.toString();
	}
	
	/**
	 * 获得主键where子句,包含where关键字。会自动处理软删除条件
	 * 
	 * @param t 注解了Table的类的对象
	 * @param keyValues 返回传入sql的参数,如果提供list则写入
	 * @return 返回值前面会带空格,以确保安全。
	 * @throws NoKeyColumnAnnotationException 当t的类没有注解任何isKey=true的列时抛出
	 * @throws NullKeyValueException 当t中的主键都是null时抛出
	 */
	public static  String getKeysWhereSQL(T t, List keyValues) 
	    throws NoKeyColumnAnnotationException, NullKeyValueException {
		
		List keyFields = DOInfoReader.getKeyColumns(t.getClass());
		
		List _keyValues = new ArrayList<>();
		String where = joinWhereAndGetValue(keyFields, "AND", _keyValues, t);
		
		// 检查主键不允许为null
		for(Object value : _keyValues) {
			if(value == null) {
				throw new NullKeyValueException();
			}
		}
		
		if(keyValues != null) {
			keyValues.addAll(_keyValues);
		}
		
		return autoSetSoftDeleted("WHERE " + where, t.getClass());
	}
	
	/**
	 * 获得主键where子句,包含where关键字
	 * 
	 * @param clazz 注解了Table的类
	 * @throws NoKeyColumnAnnotationException 当没有注解isKey=1的列时抛出
	 */
	public static String getKeysWhereSQLWithoutSoftDelete(Class clazz)
			throws NoKeyColumnAnnotationException {
		List keyFields = DOInfoReader.getKeyColumns(clazz);
		String where = joinWhere(keyFields, "AND");
		return "WHERE " + where;
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * @param t 注解了Table的对象
	 * @param values 必须,要插入的参数值
	 * @param isWithNullValue 标记是否将null字段放到insert语句中
	 * @return 生成的SQL
	 */
	public static  String getInsertSQL(T t, List values, boolean isWithNullValue) {
        StringBuilder sql = new StringBuilder("INSERT INTO ");

        List fields = DOInfoReader.getColumns(t.getClass());

        sql.append(getTableName(t.getClass())).append(" (");
        List _values = new ArrayList<>(); // 之所以增加一个临时变量,是避免values初始不是空的易错情况
		String insertSql = joinAndGetValueForInsert(fields, ",", _values, t, isWithNullValue);

		sql.append(insertSql);
        sql.append(") VALUES ");
        String dotSql = "(" + join("?", _values.size(), ",") + ")";
        sql.append(dotSql);
        values.addAll(_values);

		return sql.toString();
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * @param list 要插入的数据,非空
	 * @param values 返回的参数列表
	 * @return 插入的SQL
	 */
	public static  InsertSQLForBatchDTO getInsertSQLForBatch(Collection list, List values,
																DatabaseEnum databaseType) {
		StringBuilder sql = new StringBuilder("INSERT INTO ");

		// 获得元素的class,list非空,因此clazz和t肯定有值
		Class clazz = list.iterator().next().getClass();
		List fields = DOInfoReader.getColumns(clazz);

		// 根据list的值,只留下有值的field和非computed的列
		fields = filterFieldWithValue(fields, list);

		appendTableName(sql, clazz);
		appendInsertColumnSql(sql, fields);

		int sqlLogEndIndex = 0;
		int paramLogEndIndex = 0;
		boolean isFirst = true;
		for (T t : list) {
			sql.append(isFirst ? "VALUES" : ",");
			appendValueForBatchInsert(sql, fields, values, t, databaseType);
			if (isFirst) {
				sqlLogEndIndex = sql.length();
				paramLogEndIndex = values.size();
			}
			isFirst = false;
		}

		return new InsertSQLForBatchDTO(sql.toString(), sqlLogEndIndex, paramLogEndIndex);
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * @param list 要插入的数据,非空
	 * @param values 返回的参数列表
	 * @return 插入的SQL
	 */
	public static InsertSQLForBatchDTO getInsertSQLForBatch(String tableName, Collection> list,
			List values, DatabaseEnum databaseType) {
		StringBuilder sql = new StringBuilder("INSERT INTO `");
		sql.append(tableName.trim());
		sql.append("` (");

		boolean isFirst = true;
		int sqlLogEndIndex = 0;
		int paramLogEndIndex = 0;

		// 先从map获得所有的列
		Set colSet = new HashSet<>();
		for (Map map : list) {
			colSet.addAll(map.keySet());
		}
		List cols = new ArrayList<>(colSet);

		for (Map map : list) {
			StringBuilder sb = new StringBuilder("(");
			for (String col : cols) {
				Object value = map.get(col);
				if (value == null) {
					sb.append(SQLDialect.getInsertDefaultValue(databaseType));
				} else {
					sb.append("?");
					values.add(value);
				}
				sb.append(",");
			}
			if (isFirst) {
				for (int i = 0; i < cols.size(); i++) {
					if (i != 0) {
						sql.append(",");
					}
					sql.append("`").append(cols.get(i)).append("`");
				}
				sql.append(") VALUES ");
			} else {
				sql.append(",");
			}
			String dotSql = sb.substring(0, sb.length() - 1) + ")";
			sql.append(dotSql);

			if (isFirst) {
				sqlLogEndIndex = sql.length();
				paramLogEndIndex = values.size();
				isFirst = false;
			}
		}

		return new InsertSQLForBatchDTO(sql.toString(), sqlLogEndIndex, paramLogEndIndex);
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * @param values 返回的参数列表
	 * @return 插入的SQL
	 */
	public static InsertSQLForBatchDTO getInsertSQLForBatch(String tableName, List cols,
															Collection list, DatabaseEnum databaseType,
															List values) {
		StringBuilder sql = new StringBuilder("INSERT INTO `");
		sql.append(tableName.trim());
		sql.append("` (");

		boolean isFirst = true;
		int sqlLogEndIndex = 0;
		int paramLogEndIndex = 0;

		for (Object[] valueArray : list) {
			StringBuilder sb = new StringBuilder("(");
			for (Object value : valueArray) {
				if (value == null) {
					sb.append(SQLDialect.getInsertDefaultValue(databaseType));
				} else {
					sb.append("?");
					values.add(value);
				}
				sb.append(",");
			}
			if (isFirst) {
				for (int i = 0; i < cols.size(); i++) {
					if (i != 0) {
						sql.append(",");
					}
					sql.append("`").append(cols.get(i)).append("`");
				}
				sql.append(") VALUES ");
			} else {
				sql.append(",");
			}
			String dotSql = sb.substring(0, sb.length() - 1) + ")";
			sql.append(dotSql);

			if (isFirst) {
				sqlLogEndIndex = sql.length();
				paramLogEndIndex = values.size();
				isFirst = false;
			}
		}

		return new InsertSQLForBatchDTO(sql.toString(), sqlLogEndIndex, paramLogEndIndex);
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * 说明:这种方式是交给jdbc驱动来处理批量插入。
	 *
	 * @param list 要插入的数据,非空
	 * @param values 返回的参数列表
	 * @return 插入的SQL
	 */
	public static  String getInsertSQLForBatchForJDBCTemplate(Collection list, List values) {
		StringBuilder sql = new StringBuilder("INSERT INTO ");

		// 获得元素的class,list非空,因此clazz和t肯定有值
		Class clazz = list.iterator().next().getClass();
		List fields = DOInfoReader.getColumns(clazz);

		// 根据list的值,只留下有值的field和非computed的列
		fields = filterFieldWithValue(fields, list);

		appendTableName(sql, clazz);
		sql.append(" (");

		boolean isFirst = true;
		for (T t : list) {
			List _values = new ArrayList<>();
			String insertSql = joinAndGetValueForInsert(fields, ",", _values, t, true);
			if (isFirst) {
				sql.append(insertSql);
				sql.append(") VALUES ");
				String dotSql = "(" + join("?", _values.size(), ",") + ")";
				sql.append(dotSql);
			}
			isFirst = false;
			values.add(_values.toArray());
		}

		return sql.toString();
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * 说明:这种方式是交给jdbc驱动来处理批量插入。
	 *
	 * @param tableName 要插入的表名
	 * @param list 列和数据的集合
	 * @param values 返回的参数列表
	 * @return 插入的SQL
	 */
	public static String getInsertSQLForBatchForJDBCTemplate(String tableName,
			Collection> list, List values) {
		StringBuilder sql = new StringBuilder("INSERT INTO `");
		sql.append(tableName.trim());
		sql.append("` (");

		// 先从map获得所有的列
		Set colSet = new HashSet<>();
		for (Map map : list) {
			colSet.addAll(map.keySet());
		}

		List cols = new ArrayList<>(colSet);
		boolean isFirst = true;
		for (Map map : list) {
			List _values = new ArrayList<>();
			for (String col : cols) {
				_values.add(map.get(col));
			}
			if (isFirst) {
				boolean isColFirst = true;
				for (String col : cols) {
					if (!isColFirst) {
						sql.append(",");
					} else {
						isColFirst = false;
					}
					sql.append("`").append(col.trim()).append("`");
				}

				sql.append(") VALUES ");
				String dotSql = "(" + join("?", _values.size(), ",") + ")";
				sql.append(dotSql);
				isFirst = false;
			}
			values.add(_values.toArray());
		}

		return sql.toString();
	}

	/**
	 * 生成insert语句insert into (...) values (?,?,?),将值放到values中。
	 * 说明:这种方式是交给jdbc驱动来处理批量插入。
	 *
	 * @param tableName 要插入的表名
	 * @param cols 列和列表
	 * @return 插入的SQL
	 */
	public static String getInsertSQLForBatchForJDBCTemplate(String tableName, List cols) {
		StringBuilder sql = new StringBuilder();
		sql.append("INSERT INTO `").append(tableName.trim())
				.append("` (");
		boolean isFirst = true;
		for(String col : cols) {
			if (!isFirst) {
				sql.append(",");
			} else {
				isFirst = false;
			}
			sql.append("`").append(col.trim()).append("`");
		}
		sql.append(") VALUES (").append(join("?", cols.size(), ",")).append(")");
		return sql.toString();
	}

	private static  List filterFieldWithValue(List fields, Collection list) {
		fields = InnerCommonUtils.filter(fields, o -> {
			Column column = o.getAnnotation(Column.class);
			return column != null && InnerCommonUtils.isBlank(column.computed());
		});

		List result = new ArrayList<>();
		for (Field field : fields) {
			for (T t : list) {
				if (DOInfoReader.getValue(field, t) != null) {
					result.add(field);
					break;
				}
			}
		}
		return result;
	}

	private static void appendValueForBatchInsert(StringBuilder sb, List fields, List values,
												  Object obj, DatabaseEnum databaseType) {
		if(values == null || obj == null) {
			throw new InvalidParameterException("joinAndGetValueForInsert require values and obj");
		}

		sb.append("(");
		for(int i = 0; i < fields.size(); i++) {
			if (i > 0) {
				sb.append(",");
			}
			Field field = fields.get(i);
			Column column = field.getAnnotation(Column.class);
			if(InnerCommonUtils.isNotBlank(column.computed())) {
				continue; // insert不加入computed字段
			}

			Object value = DOInfoReader.getValue(field, obj);
			if(value != null && column.isJSON()) {
				value = NimbleOrmJSON.toJson(value);
			}
			if (value == null) {
				sb.append(SQLDialect.getInsertDefaultValue(databaseType));
			} else {
				sb.append("?");
				values.add(value);
			}
		}
		sb.append(")");
	}

	public static class BatchUpdateResultDTO {
		private String sql;
		private String logSql;
		private List logParams;

		public String getSql() {
			return sql;
		}

		public void setSql(String sql) {
			this.sql = sql;
		}

		public String getLogSql() {
			return logSql;
		}

		public void setLogSql(String logSql) {
			this.logSql = logSql;
		}

		public List getLogParams() {
			return logParams;
		}

		public void setLogParams(List logParams) {
			this.logParams = logParams;
		}
	}

	/**
	 * 生成批量update的sql
	 * @param list 要更新的对象
	 * @param values sql中对应的参数
	 * @param casVersionColumn cas版本号列,如果为null则表示没有
	 * @param keyColumn 主键列,目前的主键列只有一列,在外层调用时限制了
	 * @param notKeyColumns 非主键列
	 * @return 批量update的sql;如果返回空字符串表示不需要更新,且应该当成功处理
	 */
	public static  BatchUpdateResultDTO getBatchUpdateSQL(
			Collection list, List values, Field casVersionColumn,
			Field keyColumn, List notKeyColumns, Class clazz) {

		// 1. 找出所有的主键的值,如果值为null,这抛出异常
		List keys = new ArrayList<>(list.size());
		for (T t : list) {
			Object key = DOInfoReader.getValue(keyColumn, t);
			if (key == null) {
				throw new NullKeyValueException("class:" + t.getClass().getName() + ",values:" + NimbleOrmJSON.toJson(t));
			}
			keys.add(key);
		}

		// 2. 找出所有非null的字段,对于list中已经全是null的字段,不参与update
		List notKeyNotNullFields = new ArrayList<>();
		for (Field field : notKeyColumns) {
			for (T t : list) {
				if (DOInfoReader.getValue(field, t) != null) {
					notKeyNotNullFields.add(field);
					break;
				}
			}
		}
		if (casVersionColumn == null && notKeyNotNullFields.isEmpty()) {
			BatchUpdateResultDTO dto = new BatchUpdateResultDTO();
			dto.setSql("");
			return dto; // 没有需要更新的列
		}

		StringBuilder sql = new StringBuilder();
		StringBuilder logSql = new StringBuilder();
		List logParams = new ArrayList<>();

		// 3. 生成update语句
		sql.append("UPDATE ").append(getTableName(clazz)).append(" SET ");
		logSql.append(sql);

		boolean isFirst = true;
		for (Field field : notKeyNotNullFields) {
			if (isFirst) {
				isFirst = false;
			} else {
				sql.append(",");
				logSql.append(",");
			}

			sql.append(getColumnName(field)).append("=(CASE");
			logSql.append(getColumnName(field)).append("=(CASE");

			boolean isFirstT = true;
			for (T t : list) {
				StringBuilder sqlOld = null;
				List valuesOld = null;
				if (isFirstT) {
					sqlOld = sql;
					valuesOld = values;
					sql = new StringBuilder();
					values = new ArrayList<>();
				}

				if (casVersionColumn == null) { // 没有CAS的场景
					sql.append(" WHEN ").append(getColumnName(keyColumn)).append("=? THEN ");
					values.add(DOInfoReader.getValue(keyColumn, t));

					Object value = DOInfoReader.getValue(field, t);
					if (value == null) {
						sql.append(getColumnName(field));
					} else {
						if (field.getAnnotation(Column.class).isJSON()) {
							value = NimbleOrmJSON.toJson(value);
						}
						sql.append("?");
						values.add(value);
					}
				} else {
					sql.append(" WHEN ").append(getColumnName(keyColumn)).append("=? AND ")
							.append(getColumnName(casVersionColumn)).append("=? THEN ");
					values.add(DOInfoReader.getValue(keyColumn, t));
					values.add(DOInfoReader.getValue(casVersionColumn, t));

					Object value = DOInfoReader.getValue(field, t);
					if (value == null) {
						sql.append(getColumnName(field));
					} else {
						if (field.getAnnotation(Column.class).isJSON()) {
							value = NimbleOrmJSON.toJson(value);
						}
						sql.append("?");
						values.add(value);
					}

					sql.append(" WHEN ").append(getColumnName(keyColumn)).append("=? AND ")
							.append(getColumnName(casVersionColumn)).append("!=? THEN ");
					values.add(DOInfoReader.getValue(keyColumn, t));
					values.add(DOInfoReader.getValue(casVersionColumn, t));
					sql.append(getColumnName(field));
				}

				if (isFirstT) {
					logSql.append(sql);
					sqlOld.append(sql);
					valuesOld.addAll(values);
					logParams.addAll(values);
					sql = sqlOld;
					values = valuesOld;
					isFirstT = false;
				}
			}

			sql.append(" END)");
			logSql.append(" END)");
		}

		// 最后再追加CAS列的更新
		if (casVersionColumn != null) {
			if (!isFirst) {
				sql.append(",");
				logSql.append(",");
			}
			sql.append(getColumnName(casVersionColumn)).append("=(CASE");
			logSql.append(getColumnName(casVersionColumn)).append("=(CASE");

			boolean isFirstT = true;
			for (T t : list) {
				StringBuilder sqlOld = null;
				List valuesOld = null;
				if (isFirstT) {
					sqlOld = sql;
					valuesOld = values;
					sql = new StringBuilder();
					values = new ArrayList<>();
				}

				sql.append(" WHEN ").append(getColumnName(keyColumn)).append("=? AND ")
						.append(getColumnName(casVersionColumn)).append("=? THEN ")
						.append(getColumnName(casVersionColumn)).append("+1");
				values.add(DOInfoReader.getValue(keyColumn, t));
				values.add(DOInfoReader.getValue(casVersionColumn, t));
				sql.append(" WHEN ").append(getColumnName(keyColumn)).append("=? AND ")
						.append(getColumnName(casVersionColumn)).append("!=? THEN ")
						.append(getColumnName(casVersionColumn));
				values.add(DOInfoReader.getValue(keyColumn, t));
				values.add(DOInfoReader.getValue(casVersionColumn, t));

				if (isFirstT) {
					logSql.append(sql);
					sqlOld.append(sql);
					valuesOld.addAll(values);
					logParams.addAll(values);
					sql = sqlOld;
					values = valuesOld;
					isFirstT = false;
				}
			}

			sql.append(" END)");
			logSql.append(" END)");
		}

		String where = "WHERE " + getColumnName(keyColumn) + " IN (?)";
		values.add(keys);

		// 对于casVersion,要将cas版本加入到where子句中,因为返回的affected rows应该是where match到的行数,而不是实际修改的行数
		if (casVersionColumn != null) {
			where += " AND (" + getColumnName(keyColumn) + "," + getColumnName(casVersionColumn) + ") IN (?)";
			List idAndCas = new ArrayList<>();
			for (T t : list) {
				idAndCas.add(new Object[]{
						DOInfoReader.getValue(keyColumn, t),
						DOInfoReader.getValue(casVersionColumn, t)
				});
			}
			values.add(idAndCas);
		}

		where = autoSetSoftDeleted(where, clazz);
		sql.append(where);
		logSql.append(where);

		BatchUpdateResultDTO dto = new BatchUpdateResultDTO();
		dto.setSql(sql.toString());
		dto.setLogSql(logSql.toString());
		dto.setLogParams(logParams);

		return dto;
	}

	/**
	 * 生成update语句
	 * @param t 注解了Table的对象
	 * @param values 要更新的值
	 * @param withNull 是否更新null值
	 * @param postSql 附带的where子句
	 * @return 返回值为null表示不需要更新操作,这个是这个方法特别之处
	 */
	public static  String getUpdateSQL(T t, List values, boolean withNull, String postSql) {
		
		StringBuilder sql = new StringBuilder();
		sql.append("UPDATE ");
		
		List keyFields = DOInfoReader.getKeyColumns(t.getClass());
		List notKeyFields = DOInfoReader.getNotKeyColumns(t.getClass());
		
		sql.append(getTableName(t.getClass())).append(" SET ");
		
		List setValues = new ArrayList<>();
		String setSql = joinSetAndGetValue(notKeyFields, setValues, t, withNull);
		if(setValues.isEmpty()) {
			return null; // all field is empty, not need to update
		}
		sql.append(setSql);
		values.addAll(setValues);
		
		List whereValues = new ArrayList<>();
		String where = "WHERE " + joinWhereAndGetValue(keyFields, "AND", whereValues, t);
		// 检查key值是否有null的,不允许有null
		for(Object v : whereValues) {
			if(v == null) {
				throw new NullKeyValueException();
			}
		}
		values.addAll(whereValues);

		Field casVersionField = DOInfoReader.getCasVersionColumn(t.getClass());
		if(casVersionField != null) {
			List casVersionFields = new ArrayList<>();
			casVersionFields.add(casVersionField);
			List casValues = new ArrayList<>();
			String casWhere = joinWhereAndGetValue(casVersionFields, "AND", casValues, t);
			if(casValues.size() != 1 || casValues.get(0) == null) {
				throw new CasVersionNotMatchException("casVersion column value is null");
			}
			values.add(casValues.get(0));
			where = where + " AND " + casWhere;
		}
		
		// 带上postSql
		if(postSql != null) {
			postSql = postSql.trim(); // 这里必须要trim,因为下面靠startsWith判断是否从where开头
			if(!postSql.isEmpty()) {
				if(postSql.startsWith("where")) {
					postSql = " AND " + postSql.substring(5);
				}
				where = where + postSql;
			}
		}
		
		sql.append(autoSetSoftDeleted(where, t.getClass()));
		
		return sql.toString();
	}
	
	/**
	 * 获得批量更新sql
	 * @param clazz 注解了Table的类
	 * @param setSql update的set子句
	 * @param whereSql 附带的where子句
	 * @param extraWhereSql 会放在最后,以满足update子select语句的要求
	 * @return 生成的SQL
	 */
	public static  String getUpdateAllSQL(Class clazz, String setSql, String whereSql,
			String extraWhereSql) {
		StringBuilder sql = new StringBuilder();
		sql.append("UPDATE ");
		
		List fields = DOInfoReader.getColumns(clazz);
		
		sql.append(getTableName(clazz)).append(" ");
		
		if(setSql.trim().toLowerCase().startsWith("set ")) { // 这里必须有trim()
			sql.append(setSql);
		} else {
			sql.append("SET ").append(setSql);
		}
		
		// 加上更新时间和updateValueScript
		for(Field field : fields) {
			Column column = field.getAnnotation(Column.class);

			if(column.setTimeWhenUpdate()) {
				String nowDateTime = PreHandleObject.getNowDateTime(field.getType());
				if (nowDateTime != null) {
					sql.append(",").append(getColumnName(column))
						.append("='").append(nowDateTime).append("'");
				}
			}

			if(InnerCommonUtils.isNotBlank(column.updateValueScript())) {
				Object value = ScriptUtils.getValueFromScript(column.ignoreScriptError(), column.updateValueScript());
				if(value != null) {
					sql.append(",").append(getColumnName(column)).append("=")
							.append(TypeAutoCast.toSqlValueStr(value));
				}
			}
		}
		
		sql.append(autoSetSoftDeleted(whereSql, clazz, extraWhereSql));
		return sql.toString();
	}

	/**
	 * 获得自定义更新的sql
	 * @param t 注解了Table的对象
	 * @param values 要update的参数值
	 * @param setSql set子句SQL
	 * @return 生成的SQL
	 */
	public static  String getCustomUpdateSQL(T t, List values, String setSql) {
		StringBuilder sql = new StringBuilder();
		sql.append("UPDATE ");
		
		List fields = DOInfoReader.getColumns(t.getClass());
		List keyFields = DOInfoReader.getKeyColumns(t.getClass());
		
		sql.append(getTableName(t.getClass())).append(" ");
		
		if(setSql.trim().toLowerCase().startsWith("set ")) {
			sql.append(setSql);
		} else {
			sql.append("SET ").append(setSql);
		}
		
		// 加上更新时间和casVersion字段、updateValueScript字段
		for(Field field : fields) {
			Column column = field.getAnnotation(Column.class);

			if(column.setTimeWhenUpdate()) {
				String nowDateTime = PreHandleObject.getNowDateTime(field.getType());
				if (nowDateTime != null) {
					sql.append(",").append(getColumnName(column))
							.append("='").append(nowDateTime).append("'");
				}
			}

			if(column.casVersion()) {
				Object value = DOInfoReader.getValue(field, t);
				if(value == null) {
					throw new CasVersionNotMatchException("casVersion column value is null");
				}
				long _v;
				if(value instanceof Long) {
					_v = (Long) value;
				} else if (value instanceof Integer) {
					_v = ((Integer) value).longValue();
				} else {
					throw new CasVersionNotMatchException("casVersion column value type must be Integer or Long");
				}
				sql.append(",").append(getColumnName(column)).append("=").append(_v + 1);
			}

			if(InnerCommonUtils.isNotBlank(column.updateValueScript())) {
				Object value = ScriptUtils.getValueFromScript(t, column.ignoreScriptError(), column.updateValueScript());
				if(value != null) {
					sql.append(",").append(getColumnName(column)).append("=")
							.append(TypeAutoCast.toSqlValueStr(value));
				}
			}
		}
		
		List whereValues = new ArrayList<>();
		String where = "WHERE " + joinWhereAndGetValue(keyFields, "AND", whereValues, t);
		
		for(Object value : whereValues) {
			if(value == null) {
				throw new NullKeyValueException();
			}
		}
		values.addAll(whereValues);

		Field casVersionField = DOInfoReader.getCasVersionColumn(t.getClass());
		if(casVersionField != null) {
			List casVersionFields = new ArrayList<>();
			casVersionFields.add(casVersionField);
			List casValues = new ArrayList<>();
			String casWhere = joinWhereAndGetValue(casVersionFields, "AND", casValues, t);
			if(casValues.size() != 1 || casValues.get(0) == null) {
				throw new CasVersionNotMatchException("casVersion column value is null");
			}
			values.add(casValues.get(0));
			where = where + " AND " + casWhere;
		}
		
		sql.append(autoSetSoftDeleted(where, t.getClass()));
		
		return sql.toString();
	}
	
	/**
	 * 获得软删除SQL
	 * @param t 注解了Table的对象
	 * @param values 要传回给调用方的更新值
	 * @return 生成的SQL
	 */
	public static  String getSoftDeleteSQL(T t, Column softDeleteColumn, List values) {
		StringBuilder sql = new StringBuilder();
		sql.append("UPDATE ");
		sql.append(getTableName(t.getClass())).append(" SET ");
		sql.append(getColumnName(softDeleteColumn)).append("=").append(softDeleteColumn.softDelete()[1]);

		List fields = DOInfoReader.getColumns(t.getClass());
		for(Field field : fields) {
			Column column = field.getAnnotation(Column.class);
			// 加上删除时间
			if(column.setTimeWhenDelete()) {
				String nowDateTime = PreHandleObject.getNowDateTime(field.getType());
				if (nowDateTime != null) {
					sql.append(",").append(getColumnName(column))
							.append("='").append(nowDateTime).append("'");
				}
			}
			// 处理deleteValueScript
			if(InnerCommonUtils.isNotBlank(column.deleteValueScript())) {
				// 这里不需要再执行deleteValueScript脚本了 ,因为前面preHandleDelete已经执行了
				Object value = DOInfoReader.getValue(field, t);
				if(value != null) {
					sql.append(",").append(getColumnName(column))
							.append("=").append(TypeAutoCast.toSqlValueStr(value));
				}
			}
		}

		List keyFields = DOInfoReader.getKeyColumns(t.getClass());
		List whereValues = new ArrayList<>();
		String where = "WHERE " + joinWhereAndGetValue(keyFields, "AND", whereValues, t);
		for(Object value : whereValues) {
			if(value == null) {
				throw new NullKeyValueException();
			}
		}
		values.addAll(whereValues);

		sql.append(autoSetSoftDeleted(where, t.getClass()));
		return sql.toString();
	}
	
	/**
	 * 获得自定义删除SQL,给物理删除的
	 */
	public static  String getCustomDeleteSQL(Class clazz, String postSql) {
		return "DELETE FROM " + getTableName(clazz) + " " + postSql;
	}

	public static  String getCustomSoftDeleteSQL(Class clazz, String postSql, Field softDelete) {
		
		List fields = DOInfoReader.getColumns(clazz);
		Column softDeleteColumn = softDelete.getAnnotation(Column.class);
		
		StringBuilder sql = new StringBuilder();
		
		sql.append("UPDATE ").append(getTableName(clazz));
		sql.append(" SET ").append(getColumnName(softDeleteColumn));
		sql.append("=").append(softDeleteColumn.softDelete()[1]);
		
		// 特殊处理@Column setTimeWhenDelete时间
		for(Field field : fields) {
			Column column = field.getAnnotation(Column.class);
			if(column.setTimeWhenDelete()) {
				String nowDateTime = PreHandleObject.getNowDateTime(field.getType());
				if (nowDateTime != null) {
					sql.append(",").append(getColumnName(column)).append("='");
					sql.append(nowDateTime).append("'");
				}
			}
		}

		sql.append(autoSetSoftDeleted(postSql, clazz));
		
		return sql.toString();
	}

	/**
	 * 获得硬删除SQL
	 */
	public static  String getDeleteSQL(T t, List values) {
		List keyFields = DOInfoReader.getKeyColumns(t.getClass());
		
		StringBuilder sql = new StringBuilder();
		
		sql.append("DELETE FROM ");
		sql.append(getTableName(t.getClass()));
		
		List _values = new ArrayList<>();
		String where = "WHERE " + joinWhereAndGetValue(keyFields, "AND", _values, t);
		for(Object value : _values) { // 检查key的值是不是null
			if(value == null) {
				throw new NullKeyValueException();
			}
		}
		values.addAll(_values);

		sql.append(where);
		return sql.toString();
	}

	/**
	 * 往where sql里面插入AND关系的表达式。
	 * 例如:whereSql为 where a!=3 or a!=2 limit 1
	 *      condExpress为 deleted=0
	 * 那么返回:where (deleted=0 and (a!=3 or a!=2)) limit 1
	 * 
	 * @param whereSql 从where起的sql子句,如果有where必须带上where关键字。
	 * @param condExpression 例如a=?  不带where或and关键字。
	 * @return 注意返回字符串前面没有空格
	 * @throws JSQLParserException SQL解析错误时抛出
	 */
	public static String insertWhereAndExpression(String whereSql, String condExpression) 
			throws JSQLParserException {
		
		if(InnerCommonUtils.isBlank(condExpression)) {
			return whereSql == null ? "" : whereSql;
		}
		if(InnerCommonUtils.isBlank(whereSql)) {
			return "WHERE " + condExpression;
		}
		
		whereSql = whereSql.trim();
		if(!whereSql.toUpperCase().startsWith("WHERE ")) {
			return "WHERE " + condExpression + " " + whereSql;
		}
		
		// 为解决JSqlParse对复杂的condExpression不支持的问题,这里用替换的形式来达到目的
	    String magic = "A" + UUID.randomUUID().toString().replace("-", "");
		
		String selectSql = "select * from dual "; // 辅助where sql解析用
		Statement statement = CCJSqlParserUtil.parse(selectSql + whereSql);
		Select selectStatement = (Select) statement;
		PlainSelect plainSelect = (PlainSelect)selectStatement.getSelectBody();
		
		Expression ce = CCJSqlParserUtil.parseCondExpression(magic);
		Expression oldWhere = plainSelect.getWhere();
		Expression newWhere = new FixedAndExpression(oldWhere, ce);
		plainSelect.setWhere(newWhere);
		
		String result = plainSelect.toString().substring(selectSql.length());
		return result.replace(magic, condExpression);
	}

	public static String autoSetSoftDeleted(String whereSql, Class clazz) {
		return autoSetSoftDeleted(whereSql, clazz, "");
	}

	private static String _getDefaultOrderBy(Class clazz, String prefix) {
		List orderColumn = DOInfoReader.getKeyColumnsNoThrowsException(clazz);
		if (orderColumn.isEmpty()) { // 如果没有主键,那么全字段排序
			orderColumn = DOInfoReader.getColumns(clazz);
		}
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < orderColumn.size(); i++) {
			if (i > 0) {
				sb.append(",");
			}
			sb.append(prefix).append(getColumnName(orderColumn.get(i)));
		}
		return sb.toString();
	}

	private static String getDefaultOrderBy(Class clazz) {
		JoinTable joinTable = DOInfoReader.getJoinTable(clazz);
		if (joinTable == null) {
			return "ORDER BY " + _getDefaultOrderBy(clazz, "");
		} else {
			Field leftTableField = DOInfoReader.getJoinLeftTable(clazz);
			Field rightTableField = DOInfoReader.getJoinRightTable(clazz);
			JoinLeftTable joinLeftTable = leftTableField.getAnnotation(JoinLeftTable.class);
			JoinRightTable joinRightTable = rightTableField.getAnnotation(JoinRightTable.class);
			String orderBy1 = _getDefaultOrderBy(leftTableField.getType(), joinLeftTable.alias() + ".");
			String orderBy2 = _getDefaultOrderBy(rightTableField.getType(), joinRightTable.alias() + ".");
			return "ORDER BY " + orderBy1 + "," + orderBy2;
		}
	}

	private static List _getDefaultOrderByElement(Class clazz, String prefix) {
		List orderColumn = DOInfoReader.getKeyColumnsNoThrowsException(clazz);
		if (orderColumn.isEmpty()) { // 如果没有主键,那么全字段排序
			orderColumn = DOInfoReader.getColumns(clazz);
		}
		List list = new ArrayList<>();
		for (Field field : orderColumn) {
			OrderByElement ele = new OrderByElement();
			Column column = field.getAnnotation(Column.class);
			if (InnerCommonUtils.isBlank(column.computed())) {
				ele.setExpression(new net.sf.jsqlparser.schema.Column(prefix + getColumnName(field)));
			} else {
				ele.setExpression(new net.sf.jsqlparser.schema.Column(getColumnName(field, prefix)));
			}
			list.add(ele);
		}
		return list;
	}

	private static List getDefaultOrderByElement(Class clazz) {
		JoinTable joinTable = DOInfoReader.getJoinTable(clazz);
		if (joinTable == null) {
			return _getDefaultOrderByElement(clazz, "");
		} else {
			Field leftTableField = DOInfoReader.getJoinLeftTable(clazz);
			Field rightTableField = DOInfoReader.getJoinRightTable(clazz);
			JoinLeftTable joinLeftTable = leftTableField.getAnnotation(JoinLeftTable.class);
			JoinRightTable joinRightTable = rightTableField.getAnnotation(JoinRightTable.class);
			List list = new ArrayList<>();
			list.addAll(_getDefaultOrderByElement(leftTableField.getType(), joinLeftTable.alias() + "."));
			list.addAll(_getDefaultOrderByElement(rightTableField.getType(), joinRightTable.alias() + "."));
			return list;
		}
	}

	private static List getDefaultOrderByGroup(List groupByList) {
		List list = new ArrayList<>();
		for (Expression expression : groupByList) {
			OrderByElement ele = new OrderByElement();
			ele.setExpression(new net.sf.jsqlparser.schema.Column(expression.getASTNode().jjtGetValue().toString()));
			list.add(ele);
		}
		return list;
	}

	/**
	 * 移除whereSql中的limit子句;检查并加上order by子句
	 */
	public static String removeLimitAndAddOrder(String whereSql, boolean autoAddOrderForPagination, Class clazz) {
		// 当查询条件是空字符串时,默认带上order by主键
		if (InnerCommonUtils.isBlank(whereSql) && autoAddOrderForPagination) {
			return getDefaultOrderBy(clazz);
		}

		String selectSql = "SELECT * FROM dual "; // 辅助where sql解析用,这个大小写不能改动!
		Statement statement;
		try {
			statement = CCJSqlParserUtil.parse(selectSql + whereSql);
		} catch (JSQLParserException e) {
			LOGGER.error("fail to parse sql:{}", whereSql, e);
			return whereSql;
		}
		boolean isChange = false;
		Select selectStatement = (Select) statement;
		PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();

		// 移除limit
		Limit limit = plainSelect.getLimit();
		if (limit != null) {
			plainSelect.setLimit(null);
			isChange = true;
		}

		// 自动加order by
		if (autoAddOrderForPagination) {
			List orderBy = plainSelect.getOrderByElements();
			GroupByElement groupBy = plainSelect.getGroupBy();
			ExpressionList groupBys = groupBy == null ? null : groupBy.getGroupByExpressionList();
			List groupByList = groupBys == null ? null : groupBys.getExpressions();
			if (orderBy == null || orderBy.isEmpty()) {
				if (groupByList == null || groupByList.isEmpty()) {
					plainSelect.setOrderByElements(getDefaultOrderByElement(clazz));
				} else {
					plainSelect.setOrderByElements(getDefaultOrderByGroup(groupByList));
				}
				isChange = true;
			} else { // 如果用户自己指定了order by,那么不处理
				if (groupByList != null) {
					// 对于有group的情况,检查一下order by字段是否完成包含了group by字段
					for (Expression groupName : groupByList) {
						String name = groupName.getASTNode().jjtGetValue().toString();
						boolean isFound = false;
						for (OrderByElement order : orderBy) {
							if (order.getExpression().getASTNode().getClass().toString().equals(name)) {
								isFound = true;
								break;
							}
						}
						if (!isFound) {
							LOGGER.warn("class:{} postSql:[{}], group by field:{} not in order by list,"
											+ " it may cause unstable pagination result.",
									clazz, whereSql, name);
						}
					}
				}
			}
		}

		if (isChange) {
			String sql = plainSelect.toString();
			if (sql.startsWith(selectSql)) {
				return sql.substring(selectSql.length());
			} else {
				LOGGER.error("fail to remove limit and handle order by for sql:{}", whereSql);
				return whereSql;
			}
		} else {
			return whereSql; // 没变化
		}
	}

	/**
	 * 自动为【最后】where sql字句加上软删除查询字段。
	 * 说明:不支持virtualTable虚拟表。
	 *
	 * @param whereSql 如果有where条件的,【必须】带上where关键字;如果是group by或空的字符串或null都可以
	 * @param clazz 要操作的DO类
	 * @param extraWhere 附带的where语句,会加进去,不能带where关键字,仅能是where的条件字句,该子句会放到最后
	 * @return 无论如何前面会加空格,更安全
	 */
	public static String autoSetSoftDeleted(String whereSql, Class clazz, String extraWhere) {
		if (whereSql == null) {
			whereSql = "";
		}
		extraWhere = extraWhere == null ? "" : extraWhere;
		if(InnerCommonUtils.isNotBlank(extraWhere)) {
			extraWhere = "(" + extraWhere + ")";
		}
		
		String deletedExpression = "";
		
		// 处理join方式clazz
		JoinTable joinTable = DOInfoReader.getJoinTable(clazz);
		if(joinTable != null) {
			Field leftTableField = DOInfoReader.getJoinLeftTable(clazz);
			Field rightTableField = DOInfoReader.getJoinRightTable(clazz);
			
			JoinLeftTable joinLeftTable = leftTableField.getAnnotation(JoinLeftTable.class);
			JoinRightTable joinRightTable = rightTableField.getAnnotation(JoinRightTable.class);
			
			Field softDeleteT1 = DOInfoReader.getSoftDeleteColumn(leftTableField.getType());
			Field softDeleteT2 = DOInfoReader.getSoftDeleteColumn(rightTableField.getType());
			
			if(softDeleteT1 == null && softDeleteT2 == null) {
				try {
					return " " + insertWhereAndExpression(whereSql, extraWhere);
				} catch (JSQLParserException e) {
					LOGGER.error("Bad sql syntax,whereSql:{},deletedExpression:{}",
							whereSql, deletedExpression, e);
					throw new BadSQLSyntaxException(e);
				}
			}
			
			StringBuilder deletedExpressionSb = new StringBuilder();
			if(softDeleteT1 != null) {
				Column softDeleteColumn = softDeleteT1.getAnnotation(Column.class);
				String columnName = getColumnName(softDeleteColumn);
				if(joinTable.joinType() == JoinTypeEnum.RIGHT_JOIN) {
					deletedExpressionSb.append("(").append(joinLeftTable.alias()).append(".")
							.append(columnName).append("=").append(softDeleteColumn.softDelete()[0])
					   .append(" or ").append(joinLeftTable.alias()).append(".")
					   .append(columnName).append(" is null)");
				} else {
					deletedExpressionSb.append(joinLeftTable.alias()).append(".")
							.append(columnName).append("=").append(softDeleteColumn.softDelete()[0]);
				}
			}
			
			if(softDeleteT2 != null) {
				if(softDeleteT1 != null) {
					deletedExpressionSb.append(" AND ");
				}
				Column softDeleteColumn = softDeleteT2.getAnnotation(Column.class);
				String columnName = getColumnName(softDeleteColumn);
				if(joinTable.joinType() == JoinTypeEnum.LEFT_JOIN) {
					deletedExpressionSb.append("(").append(joinRightTable.alias()).append(".")
							.append(columnName).append("=").append(softDeleteColumn.softDelete()[0])
					    .append(" or ").append(joinRightTable.alias()).append(".")
					    .append(columnName).append(" is null)");
				} else {
					deletedExpressionSb.append(joinRightTable.alias()).append(".")
							.append(columnName).append("=").append(softDeleteColumn.softDelete()[0]);
				}
			}
			
			deletedExpression = deletedExpressionSb.toString();		
		} else {
			Field softDelete = DOInfoReader.getSoftDeleteColumn(clazz);
			if(softDelete == null) {
				try {
					return " " + insertWhereAndExpression(whereSql, extraWhere);
				} catch (JSQLParserException e) {
					LOGGER.error("Bad sql syntax,whereSql:{},deletedExpression:{}",
							whereSql, deletedExpression, e);
					throw new BadSQLSyntaxException(e);
				}
			}
			
			Column softDeleteColumn = softDelete.getAnnotation(Column.class);
			deletedExpression = getColumnName(softDeleteColumn) + "=" 
			                        + softDeleteColumn.softDelete()[0];
		}
		
		try {
			if(!extraWhere.isEmpty()) {
				deletedExpression = "(" + deletedExpression + " and " + extraWhere + ")";
			}
			return " " + SQLUtils.insertWhereAndExpression(whereSql, deletedExpression);
		} catch (JSQLParserException e) {
			LOGGER.error("Bad sql syntax,whereSql:{},deletedExpression:{}",
					whereSql, deletedExpression, e);
			throw new BadSQLSyntaxException(e);
		}
	}
	
	/**
	 * 拼凑limit字句。前面有空格。
	 * @param offset 可以为null
	 * @param limit 不能为null
	 * @return 生成的SQL
	 */
	public static String genLimitSQL(Integer offset, Integer limit) {
		StringBuilder sb = new StringBuilder();
		if (limit != null) {
			sb.append(" limit ");
			if(offset != null) {
				sb.append(offset).append(",");
			}
			sb.append(limit);
		}
		return sb.toString();
	}

	/**
	 * 拿到computed SQL在特性开关的情况下的返回值。说明:调用此方法请确保计算列是非空的。
	 * @param column 列注解
	 * @param features 特性开关map
	 * @return 返回计算列的结果SQL
	 */
	public static String getComputedColumn(Column column, Map features) {
		String computed = column.computed();

		Boolean autoSumNullToZero = features.get(FeatureEnum.AUTO_SUM_NULL_TO_ZERO);
		if (autoSumNullToZero != null && autoSumNullToZero) {
			String computedLower = computed.toLowerCase().trim();
			if (computedLower.startsWith("sum(") && computedLower.endsWith(")")) {
				computed = "COALESCE(" + computed + ",0)";
			}
		}

		return computed;
	}

	/**
	 * 判断postSql是否包含了limit子句
	 * @param postSql 从where开始的子句
	 * @return 是返回true,否返回false;如果解析异常返回false
	 */
	public static boolean isContainsLimit(String postSql) throws JSQLParserException {
		Boolean result = containsLimitCache.get(postSql);
		if (result != null) {
			return result;
		}

		String selectSql = "select * from dual "; // 辅助where sql解析用

		Statement statement = CCJSqlParserUtil.parse(selectSql + postSql);
		Select selectStatement = (Select) statement;
		PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
		Limit limit = plainSelect.getLimit();
		boolean isContainsLimit = limit != null;
		containsLimitCache.put(postSql, isContainsLimit); // 这里能用缓存是因为该postSql来自于注解,数量固定
		return isContainsLimit;
	}

	private static final Map containsLimitCache = new ConcurrentHashMap<>();

    /**
     * 拼凑select的field的语句
     */
	private static String join(List fields, String sep, Map features) {
	    return join(fields, sep, null, features);
    }
	
    /**
     * 拼凑select的field的语句
     */
    private static String join(List fields, String sep, String fieldPrefix,
							   Map features) {
    	return joinAndGetValueForSelect(fields, sep, fieldPrefix, features);
    }
	
	/**
	 * 拼凑where子句,并把需要的参数写入到values中。返回sql【不】包含where关键字
	 * 
	 * @param fields 注解了Column的field
	 * @param logicOperate 操作符,例如AND
	 * @param values where条件的参数值
	 */
	private static String joinWhereAndGetValue(List fields,
			String logicOperate, List values, Object obj) {
		StringBuilder sb = new StringBuilder();
		int fieldSize = fields.size();
		for(int i = 0; i < fieldSize; i++) {
			Column column = fields.get(i).getAnnotation(Column.class);
			sb.append(getColumnName(column)).append("=?");
			if(i < fieldSize - 1) {
				sb.append(" ").append(logicOperate).append(" ");
			}
			Object val = DOInfoReader.getValue(fields.get(i), obj);
			if(val != null && column.isJSON()) {
				val = NimbleOrmJSON.toJson(val);
			}
			values.add(val);
		}
		return sb.toString();
	}
	
	/**
	 * 拼凑where子句。返回sql【不】包含where关键字
	 * @param logicOperate 操作符,例如AND
	 */
	private static String joinWhere(List fields, String logicOperate) {
		StringBuilder sb = new StringBuilder();
		int fieldSize = fields.size();
		for(int i = 0; i < fieldSize; i++) {
			Column column = fields.get(i).getAnnotation(Column.class);
			sb.append(getColumnName(column)).append("=?");
			if(i < fieldSize - 1) {
				sb.append(" ").append(logicOperate).append(" ");
			}
		}
		return sb.toString();
	}

    /**
     * 拼凑字段逗号,分隔子句(用于select)。会处理computed的@Column字段
     */
	private static String joinAndGetValueForSelect(List fields, String sep, String fieldPrefix,
												   Map features) {
        fieldPrefix = fieldPrefix == null ? "" : fieldPrefix.trim();

    	StringBuilder sb = new StringBuilder();
    	for(Field field : fields) {
    		Column column = field.getAnnotation(Column.class);
    		
    		if(InnerCommonUtils.isNotBlank(column.computed())) {
				// 计算列不支持默认前缀,当join时,请自行区分计算字段的命名
    			sb.append("(").append(SQLUtils.getComputedColumn(column, features)).append(") AS ")
						.append(getColumnName(column, fieldPrefix)).append(sep);
    		} else {
				// 非计算列的话,表的别名要放在`外边
				sb.append(fieldPrefix).append(getColumnName(column)).append(sep);
			}
    	}
    	int len = sb.length();
    	return len == 0 ? "" : sb.substring(0, len - 1);
	}

	/**
	 * 获得指定字段拼凑而成的插入列,例如(name,age)。
	 * @param fields 由调用方保证有Column注解且没有computed值
	 */
	private static void appendInsertColumnSql(StringBuilder sb, List fields) {
		sb.append("(");
		if (fields != null) {
			for(int i = 0; i < fields.size(); i++) {
				if (i > 0) {
					sb.append(",");
				}
				Field field = fields.get(i);
				Column column = field.getAnnotation(Column.class);
				appendColumnName(sb, column.value());
			}
		}
		sb.append(")");
	}
    
    /**
     * 拼凑字段逗号,分隔子句(用于insert),并把参数obj的值放到values中。会排除掉computed的@Column字段
     *
     * @param values 不应该为null
     * @param obj 不应该为null
     * @param isWithNullValue 是否把null值放到values中
     */
	private static String joinAndGetValueForInsert(List fields, String sep,
			List values, Object obj, boolean isWithNullValue) {
		if(values == null || obj == null) {
			throw new InvalidParameterException("joinAndGetValueForInsert require values and obj");
		}

    	StringBuilder sb = new StringBuilder();
    	for(Field field : fields) {
    		Column column = field.getAnnotation(Column.class);
    		if(InnerCommonUtils.isNotBlank(column.computed())) {
    			continue; // insert不加入computed字段
    		}

			Object value = DOInfoReader.getValue(field, obj);
			if(value != null && column.isJSON()) {
				value = NimbleOrmJSON.toJson(value);
			}
			if(isWithNullValue) {
				values.add(value);
			} else {
				if(value == null) {
					continue; // 不加入该column
				} else {
					values.add(value);
				}
			}
    		
        	sb.append(getColumnName(column)).append(sep);
    	}
    	int len = sb.length();
    	return len == 0 ? "" : sb.substring(0, len - 1);
	}
	
	/**
	 * 例如:str=?,times=3,sep=,  返回 ?,?,?
	 */
    private static String join(String str, int times, String sep) {
    	StringBuilder sb = new StringBuilder();
    	for(int i = 0; i < times; i++) {
    		sb.append(str);
    		if(i < times - 1) {
    			sb.append(sep);
    		}
    	}
    	return sb.toString();
    }
    
	/**
	 * 拼凑set子句,将会处理casVersion的字段自动+1
	 * @param withNull 当为true时,如果field的值为null,也加入
	 */
	private static String joinSetAndGetValue(List fields,
			List values, Object obj, boolean withNull) {
		StringBuilder sb = new StringBuilder();
		for (Field field : fields) {
			Column column = field.getAnnotation(Column.class);
			Object value = DOInfoReader.getValue(field, obj);
			if (column.casVersion()) {
				if (value == null) {
					throw new CasVersionNotMatchException("casVersion column value is null");
				}
				long _v;
				if (value instanceof Long) {
					_v = (Long) value;
				} else if (value instanceof Integer) {
					_v = ((Integer) value).longValue();
				} else {
					throw new CasVersionNotMatchException("casVersion column type must be Integer or Long");
				}
				sb.append(getColumnName(column)).append("=").append(_v + 1).append(",");
			} else {
				if (value != null && column.isJSON()) {
					value = NimbleOrmJSON.toJson(value);
				}
				if (withNull || value != null) {
					sb.append(getColumnName(column)).append("=?,");
					values.add(value);
				}
			}
		}
		return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);
	}

	private static String getTableName(Class clazz) {
		String tableName = DBHelperContext.getTableName(clazz);
		if (InnerCommonUtils.isBlank(tableName)) {
			tableName = DOInfoReader.getTable(clazz).value();
		}
		return "`" + tableName + "`";
	}

	private static void appendTableName(StringBuilder sb, Class clazz) {
		String tableName = DBHelperContext.getTableName(clazz);
		if (InnerCommonUtils.isBlank(tableName)) {
			tableName = DOInfoReader.getTable(clazz).value();
		}
		sb.append("`").append(tableName).append("`");
	}

	private static String getColumnName(Column column, String prefix) {
		return getColumnName(column.value(), prefix);
	}

	private static String getColumnName(Column column) {
		return getColumnName(column.value());
	}

	/**返回字段名称,重要: 请自行确保field上有注解了@Column*/
	public static String getColumnName(Field field) {
		return getColumnName(field.getAnnotation(Column.class));
	}

	public static String getColumnName(Field field, String prefix) {
		return getColumnName(field.getAnnotation(Column.class), prefix);
	}

	public static String getColumnName(String columnName) {
		return "`" + columnName + "`";
	}

	private static String getColumnName(String columnName, String prefix) {
		return "`" + prefix + columnName + "`";
	}

	private static void appendColumnName(StringBuilder sb, String columnName) {
		sb.append("`").append(columnName).append("`");
	}

}