一、错误现象

运行测试时抛出:

java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; 
check the manual that corresponds to your MySQL server version for the right syntax to use near ''t_car'' at line 1

执行 SQL:

select id, car_num, brand, guide_price, produce_time, car_type from ?

参数:t_car(字符串)

二、错误含义

这条 SQL 中表名使用了 ? 占位符,但在 SQL 语法中,表名、列名等数据库标识符不能使用预编译占位符(?。MyBatis 会将 ? 替换为带单引号的字符串 't_car',导致 SQL 变成:

select ... from 't_car'

在 MySQL 中,'t_car' 被视为字符串字面量,而不是表名标识符,因此报语法错误。

根本原因:在 MyBatis 的 XML 映射文件中,表名使用了 #{}(预编译占位符),而表名应该是静态的 SQL 文本或使用 ${} 进行字符串替换

三、错误代码示例

假设你的 CarMapper.xml 中有类似以下写法:

<select id="selectAllCar" resultType="com.xie.entity.Car">
    select id, car_num, brand, guide_price, produce_time, car_type 
    from #{tableName}
</select>

<select id="selectAllCar" resultType="com.xie.entity.Car">
    select id, car_num, brand, guide_price, produce_time, car_type 
    from ${tableName}
</select>

第一种写法(#{})是错误的,因为 #{} 会生成 ? 占位符,然后作为字符串值传入,导致表名被引号包裹。

第二种写法(${})是正确的,但存在 SQL 注入风险,需要谨慎处理。

四、解决方案

方案一:直接写死表名(推荐,如果表名固定)

如果你查询的表名是固定的,比如一直是 t_car,那么直接硬编码表名即可:

<select id="selectAllCar" resultType="com.xie.entity.Car">
    select id, car_num, brand, guide_price, produce_time, car_type 
    from t_car
</select>

无需使用任何占位符。

方案二:使用${}传递动态表名(仅在必要时)

如果表名确实需要动态传入(例如分表场景),则必须使用 ${}

<select id="selectAllCar" resultType="com.xie.entity.Car">
    select id, car_num, brand, guide_price, produce_time, car_type 
    from ${tableName}
</select>

对应的接口方法:

List<Car> selectAllCar(@Param("tableName") String tableName);

调用时传入表名:

List<Car> cars = carMapper.selectAllCar("t_car");

⚠️ 安全警告${} 会直接拼接字符串,存在SQL注入风险。如果 tableName 来自用户输入,攻击者可能传入恶意值(如 t_car; DROP TABLE t_car; --)。因此,必须对表名进行白名单校验

方案三:安全地使用${}(推荐做法)

在调用 Mapper 方法之前,对表名进行合法性验证:

public List<Car> getCars(String tableName) {
    // 白名单校验
    Set<String> validTables = Set.of("t_car", "t_car_2023", "t_car_2024");
    if (!validTables.contains(tableName)) {
        throw new IllegalArgumentException("非法表名: " + tableName);
    }
    return carMapper.selectAllCar(tableName);
}

或者使用枚举限制:

public enum CarTable {
    T_CAR("t_car"),
    T_CAR_2023("t_car_2023");
    private String name;
    // 构造器、getter
}

五、关于#{}与${}的适用场景总结

占位符 用途 是否能用于表名/列名 SQL注入风险
#{} 传递字段值(如 where id = #{id} 不能 ✅ 安全(预编译)
${} 传递数据库对象名(表名、列名、排序字段) ❌ 高风险(需白名单)

六、从日志看参数绑定过程

从日志可以看到:

==>  Preparing: select id,car_num,brand,guide_price,produce_time,car_type from ?
==> Parameters: t_car(String)

这说明 MyBatis 将 #{} 转换成了 ?,并将 t_car 作为字符串参数绑定。当 PreparedStatement 执行时,会生成类似 from 't_car' 的 SQL,从而导致语法错误。

七、最终修正建议

如果你只是希望查询 t_car 表的所有数据,最稳妥的做法是:

1. 修改 XML,硬编码表名

<select id="selectAllCar" resultType="com.xie.entity.Car">
    select id, car_num, brand, guide_price, produce_time, car_type 
    from t_car
</select>

2. 接口方法无需参数

List<Car> selectAllCar();

3. 测试代码直接调用

List<Car> cars = carMapper.selectAllCar();

这样既简单又安全,且性能最优。

八、总结

错误类型 原因 解决方案
SQLSyntaxErrorException near 't_car' 表名使用了 #{} 占位符,被错误地当作字符串值处理 #{} 改为 ${}(动态表名)或直接硬编码表名(固定表名)
SQL注入风险 使用 ${} 时未做校验 对传入的表名进行白名单验证

记住:表名、列名、ORDER BY 字段等 SQL 结构部分,必须使用 ${} 动态拼接,但务必做好安全校验;而字段值(WHEREINSERTUPDATE 的值)则永远使用 #{},安全且高效。

以上就是MyBatis SQL语法错误:from ?导致SQLSyntaxErrorException详解与解决方案的详细内容,更多关于MyBatis SQL语法错误SQLSyntaxErrorException的资料请关注其它相关文章!

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章