遇到的问题
java
List<Order> selectByCriteria(@Param("orderDto") OrderDto orderDto, @Param("pageSize")Integer pageSize, @Param("offset")Integer offset);
xml文件的SQL语句已经指定jdbcType=VARCHAR:
xml
<if test="orderDto.cityName != null and orderDto.cityName != ''">
and f_sa_name like concat('%', #{orderDto.saName,javaType=String,jdbcType=VARCHAR}, '%')
</if>,
查询PostgreSQL时依然报错:
nested exception is org.postgresql.util.PSQLException: 错误: 无法确定参数 $1 的数据类型"
错误原因分析
分析错误发生的位置和时序
bash
// 1. MyBatis 准备参数(这一步正常)
MyBatis: 将 #{saName, jdbcType=VARCHAR} 转换为 JDBC 参数
MyBatis: 设置参数值 "淄博市" 到 PreparedStatement 的第1个位置
↓ 成功
// 2. PostgreSQL 执行 SQL(这一步出错)
PostgreSQL: 收到 SQL: SELECT * FROM t_order WHERE f_sa_name like concat('%s', $1, '%s')
PostgreSQL: 尝试推断 $1 的类型
PostgreSQL: ❌ 错误!无法确定 $1 的数据类型
jdbcType是MyBatis、JDBC驱动和数据库之间的"翻译官",确保三方对数据类型有一致的理解。更准确的讲,jdbcType是给JDBC的,不是给PostgreSQL 解析器的。PostgreSQL在解析SQL时,按顺序处理参数。如果参数的第一次出现是在无法推断类型的位置(如OR条件左侧、函数参数、CASE语句等),即使指定了jdbcType,PostgreSQL也无法确定类型。
验证错误归属的测试
java
public class ErrorAttributionTest {
public static void main(String[] args) throws SQLException {
String url = "jdbc:postgresql://localhost:5432/test";
String user = "postgres";
String password = "******";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
// 测试1:使用明确的类型 - 成功
String sql1 = "SELECT * FROM users WHERE name = ?::text";
try (PreparedStatement pstmt = conn.prepareStatement(sql1)) {
pstmt.setString(1, "test"); // 明确设置类型
pstmt.executeQuery();
System.out.println("测试1成功:明确类型");
}
// 测试2:类型不明确 - 失败
String sql2 = "SELECT * FROM users WHERE (? IS NULL OR name = ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql2)) {
pstmt.setString(1, "test");
pstmt.setString(2, "test");
try {
pstmt.executeQuery();
} catch (PSQLException e) {
System.out.println("测试2失败:" + e.getMessage());
System.out.println("这是 PostgreSQL 错误,不是 MyBatis 错误");
}
}
// 测试3:使用 JDBC 直接设置参数类型
String sql3 = "SELECT * FROM users WHERE name = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql3)) {
// JDBC 明确设置类型
pstmt.setObject(1, "test", Types.VARCHAR);
pstmt.executeQuery();
System.out.println("测试3成功:JDBC 明确类型");
}
}
}
}
常见问题场景和修复方案
场景1:参数在 OR 条件的左侧
xml
<!-- ❌ 错误示例:$1 在 OR 左侧,无法推断类型 -->
<select id="findOrders" resultType="Order">
SELECT * FROM orders
<where>
<if test="saName != null">
AND (#{saName, jdbcType=VARCHAR} IS NULL OR sa_name = #{saName, jdbcType=VARCHAR})
</if>
</where>
</select>
<!-- ✅ 修复:交换顺序,让有类型的字段在左侧 -->
<select id="findOrders" resultType="Order">
SELECT * FROM orders
<where>
<if test="saName != null">
AND (sa_name = #{saName, jdbcType=VARCHAR} OR #{saName, jdbcType=VARCHAR} IS NULL)
</if>
</where>
</select>
场景2:参数在函数内部
xml
<!-- ❌ 错误示例:参数在 CONCAT 函数内部 -->
<select id="searchOrders" resultType="Order">
SELECT * FROM orders
WHERE order_no LIKE CONCAT('%', #{orderNo, jdbcType=VARCHAR}, '%')
</select>
<!-- ✅ 修复1:使用类型转换 -->
<select id="searchOrders" resultType="Order">
SELECT * FROM orders
WHERE order_no LIKE CONCAT('%', #{orderNo, jdbcType=VARCHAR}::text, '%')
</select>
<!-- ✅ 修复2:使用 || 操作符(PostgreSQL 字符串连接) -->
<select id="searchOrders" resultType="Order">
SELECT * FROM orders
WHERE order_no LIKE '%' || #{orderNo, jdbcType=VARCHAR} || '%'
</select>
<!-- ✅ 修复3:使用位置参数 -->
<select id="searchOrders" resultType="Order">
SELECT * FROM orders
WHERE order_no LIKE '%' || ? || '%'
</select>
场景3:参数在 CASE 语句中
xml
<!-- ❌ 错误示例:参数在 CASE 的 WHEN 部分 -->
<select id="getOrderStatus" resultType="String">
SELECT
CASE
WHEN #{status, jdbcType=INTEGER} = 1 THEN '待处理'
WHEN #{status, jdbcType=INTEGER} = 2 THEN '处理中'
WHEN #{status, jdbcType=INTEGER} = 3 THEN '已完成'
ELSE '未知'
END as status_name
FROM orders
</select>
<!-- ✅ 修复:使用子查询或临时表 -->
<select id="getOrderStatus" resultType="String">
WITH status_param AS (
SELECT #{status, jdbcType=INTEGER}::integer as status_val
)
SELECT
CASE
WHEN status_val = 1 THEN '待处理'
WHEN status_val = 2 THEN '处理中'
WHEN status_val = 3 THEN '已完成'
ELSE '未知'
END as status_name
FROM status_param
</select>
场景4:参数在子查询中
xml
<!-- ❌ 错误示例:参数在子查询中且位置不佳 -->
<select id="findByDept" resultType="Order">
SELECT * FROM orders
WHERE dept_id IN (
SELECT id FROM department
WHERE name LIKE CONCAT('%', #{deptName, jdbcType=VARCHAR}, '%')
)
</select>
<!-- ✅ 修复:使用 JOIN 代替子查询 -->
<select id="findByDept" resultType="Order">
SELECT o.* FROM orders o
JOIN department d ON o.dept_id = d.id
WHERE d.name LIKE '%' || #{deptName, jdbcType=VARCHAR} || '%'
</select>
场景5:多个参数在复杂表达式中
xml
<!-- ❌ 错误示例:多个参数相互影响 -->
<select id="findByDateRange" resultType="Order">
SELECT * FROM orders
WHERE
(#{startDate, jdbcType=TIMESTAMP} IS NULL OR create_time >= #{startDate, jdbcType=TIMESTAMP})
AND (#{endDate, jdbcType=TIMESTAMP} IS NULL OR create_time <= #{endDate, jdbcType=TIMESTAMP})
</select>
<!-- ✅ 修复:使用 COALESCE 或 CASE -->
<select id="findByDateRange" resultType="Order">
SELECT * FROM orders
WHERE
create_time >= COALESCE(#{startDate, jdbcType=TIMESTAMP}::timestamp, '1970-01-01')
AND create_time <= COALESCE(#{endDate, jdbcType=TIMESTAMP}::timestamp, '9999-12-31')
</select>
终极解决方案:使用类型包装器
如果上述方法都不行,可以创建一个专门处理 PostgreSQL 类型问题的包装器:
java
public class PostgreSQLTypeHelper {
/**
* 包装参数,确保 PostgreSQL 能推断类型
*/
public static String wrapParam(String paramName, String jdbcType) {
return "#{" + paramName + ", jdbcType=" + jdbcType + "}::" +
mapJdbcTypeToPostgresType(jdbcType);
}
private static String mapJdbcTypeToPostgresType(String jdbcType) {
switch (jdbcType) {
case "VARCHAR": return "text";
case "INTEGER": return "integer";
case "TIMESTAMP": return "timestamp";
case "DATE": return "date";
case "BOOLEAN": return "boolean";
case "BIGINT": return "bigint";
case "DECIMAL": return "numeric";
default: return "text";
}
}
}
在 XML 中使用:
xml
<select id="safeQuery" resultType="Order">
SELECT * FROM orders
WHERE
-- 强制类型转换
sa_name = #{saName, jdbcType=VARCHAR}::text
AND status = #{status, jdbcType=INTEGER}::integer
AND create_time >= #{startDate, jdbcType=TIMESTAMP}::timestamp
</select>
调试技巧
1、查看实际发送的 SQL:
yaml
logging:
level:
org.postgresql: DEBUG
com.your.mapper: TRACE
2、使用 PostgreSQL 的 PREPARE 语句测试:
sql
-- 在 PostgreSQL 中手动测试
PREPARE test_stmt(text) AS
SELECT * FROM orders WHERE sa_name = $1;
EXECUTE test_stmt('test');
DEALLOCATE test_stmt;
同样的 MyBatis 配置在不同数据库的表现:
xml
<select id="test" resultType="map">
SELECT * FROM users WHERE (? IS NULL OR name = ?)
</select>
数据库 结果 原因
PostgreSQL ❌ 报错 需要明确类型
MySQL ✅ 成功 弱类型,自动转换
Oracle ✅ 成功 有默认类型推断机制