全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

动态表名与列名

MyBatis 的 #{} 预编译参数无法用于表名、列名、ORDER BY 字段等 SQL 结构,必须使用 ${} 进行字符串替换。本文梳理动态结构拼接的使用方法和安全防范。

#{ } 与 ${ } 的本质区别

特性#{} 预编译参数${} 字符串替换
处理方式PreparedStatement ? 占位符直接字符串拼接
SQL 注入天然免疫存在风险
适用场景值参数(WHERE/INSERT/UPDATE)表名、列名、ORDER BY 字段
性能预编译可复用执行计划每次生成新 SQL
示例WHERE id = #{id}FROM ${tableName}

#{} 仅适用于值替换,不能用于表名和列名,否则会导致 SQL 语法错误:SELECT * FROM ? 是无效 SQL。

动态表名拼接

分表场景

Java
public interface UserMapper {
  // 按月份分表:user_202401, user_202402 ...
  List<User> selectFromTable(@Param("tableSuffix") String tableSuffix,
                              @Param("id") Long id);
}
XML
<select id="selectFromTable" resultType="User">
  SELECT * FROM user_${tableSuffix} WHERE id = #{id}
</select>

调用示例:

Java
// 查询 2024 年 3 月的用户表
String suffix = "202403";
User user = mapper.selectFromTable(suffix, 1001L);

多环境表路由

XML
<select id="selectByEnv" resultType="Config">
  SELECT * FROM config_${env} WHERE key = #{key}
</select>
Java
// 开发环境查 config_dev,生产环境查 config_prod
mapper.selectByEnv("dev", "app.name");

动态列名拼接

动态排序字段

XML
<select id="selectSorted" resultType="User">
  SELECT id, name, create_time FROM user
  ORDER BY ${sortColumn} ${sortDirection}
  LIMIT #{limit} OFFSET #{offset}
</select>
Java
public interface UserMapper {
  List<User> selectSorted(@Param("sortColumn") String sortColumn,
                           @Param("sortDirection") String sortDirection,
                           @Param("limit") int limit,
                           @Param("offset") int offset);
}

ORDER BY 和 LIMIT 后的值不能使用 #{},必须使用 ${},因为它们是 SQL 结构而非值参数。

动态选择查询列

XML
<select id="selectColumns" resultType="map">
  SELECT ${columns} FROM user WHERE id = #{id}
</select>
Java
// 只查询指定列,减少数据传输
String cols = "id, name, email";
Map<String, Object> result = mapper.selectColumns(cols, 1001L);

分库分表实战

水平分表路由

SQL
-- order_0 ~ order_15,共 16 张分表
CREATE TABLE order_0 ( ... );
CREATE TABLE order_1 ( ... );
...
CREATE TABLE order_15 ( ... );
Java
public class ShardingRouter {
  // 按 user_id 哈希取模路由
  public static String routeTable(Long userId, String baseTable, int shardCount) {
    int index = (int) (Math.abs(userId.hashCode()) % shardCount);
    return baseTable + "_" + index;
  }
}
XML
<select id="selectOrder" resultType="Order">
  SELECT * FROM ${tableName} WHERE order_id = #{orderId}
</select>
Java
// 调用
String table = ShardingRouter.routeTable(userId, "order", 16);
Order order = mapper.selectOrder(table, orderId);

按时间分表

Java
public class DateShardingRouter {
  public static String routeByMonth(String baseTable, LocalDate date) {
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMM");
    return baseTable + "_" + date.format(fmt);
  }
  
  public static String routeByYear(String baseTable, int year) {
    return baseTable + "_" + year;
  }
}
XML
<!-- 查询特定月份的数据 -->
<select id="selectByMonth" resultType="Log">
  SELECT * FROM ${tableName}
  <where>
    <if test="startTime != null">
      AND create_time >= #{startTime}
    </if>
    <if test="endTime != null">
      AND create_time &lt;= #{endTime}
    </if>
  </where>
</select>

SQL 注入防范

风险场景

XML
<!-- 危险:用户可控的表名直接拼接 -->
<select id="unsafeSelect" resultType="User">
  SELECT * FROM ${tableName} WHERE id = #{id}
</select>

攻击者传入 tableName = "user; DROP TABLE user" 会导致恶意 SQL 执行。

防范策略一:白名单校验

Java
public class TableNameValidator {
  // 允许的表名白名单(正则匹配)
  private static final Pattern TABLE_PATTERN = 
      Pattern.compile("^(user|order|log)_[0-9]{4,6}$");
  
  public static String validate(String tableName) {
    if (tableName == null || !TABLE_PATTERN.matcher(tableName).matches()) {
      throw new IllegalArgumentException("Invalid table name: " + tableName);
    }
    return tableName;
  }
}
Java
public Order selectOrder(String tableSuffix, Long orderId) {
  String table = "order_" + tableSuffix;
  TableNameValidator.validate(table); // 校验通过后拼接
  return mapper.selectOrder(table, orderId);
}

防范策略二:枚举约束

Java
public enum SortColumn {
  ID("id"), NAME("name"), CREATE_TIME("create_time"), UPDATE_TIME("update_time");
  
  private final String columnName;
  
  SortColumn(String columnName) {
    this.columnName = columnName;
  }
  
  public String getColumnName() {
    return columnName;
  }
}
XML
<select id="selectSorted" resultType="User">
  SELECT id, name, create_time FROM user
  ORDER BY ${sortColumn} ${sortDirection}
  LIMIT #{limit} OFFSET #{offset}
</select>
Java
public List<User> selectSorted(SortColumn sortColumn, String direction, int limit, int offset) {
    // sortColumn 枚举值安全,无需额外校验
    return mapper.selectSorted(sortColumn.getColumnName(), 
        "ASC".equalsIgnoreCase(direction) ? "ASC" : "DESC", limit, offset);
}

防范策略三:MyBatis 拦截器过滤

Java
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", 
               args = {Connection.class, Integer.class})
})
public class SqlInjectionInterceptor implements Interceptor {
  private static final Pattern DANGER_PATTERN = 
      Pattern.compile("(;|--|/\\*|\\*/|xp_|exec|drop|delete|update|insert)", 
                       Pattern.CASE_INSENSITIVE);
  
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler handler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    
    if (DANGER_PATTERN.matcher(sql).find()) {
      throw new SecurityException("Potential SQL injection detected: " + sql);
    }
    return invocation.proceed();
  }
  
  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  
  @Override
  public void setProperties(Properties properties) {}
}

${ } 使用场景汇总

场景使用方式安全等级
表名拼接${tableName}高(需白名单)
列名拼接${columnName}高(需白名单)
ORDER BY${sortColumn} ${direction}中(枚举约束)
LIMIT/OFFSET使用 #{}安全(值参数)
GROUP BY${groupByColumn}中(枚举约束)
动态 SQL 结构${sqlFragment}低(内部使用)

要点总结

  • #{} 是预编译参数,仅适用于值替换;${} 是字符串拼接,用于表名、列名、排序字段等 SQL 结构。
  • 分表场景必须使用 ${} 拼接表名,配合路由算法实现数据分片查询。
  • SQL 注入防范三板斧:白名单正则校验、枚举约束排序方向、拦截器过滤危险字符。
  • 动态列名建议使用枚举类约束,避免外部可控值直接参与拼接。
  • LIMIT/OFFSET 参数使用 #{} 即可,它们是值而非 SQL 结构。
  • 生产环境建议所有 ${} 使用前必须经过校验,不可直接信任外部输入。

📝 发现内容有误?点击此处直接编辑

← 上一篇 线程安全与并发
下一篇 → 复杂继承映射
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库